diff options
Diffstat (limited to 'tools')
62 files changed, 2694 insertions, 545 deletions
diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp index d346731..1a5d512 100644 --- a/tools/aapt/AaptAssets.cpp +++ b/tools/aapt/AaptAssets.cpp @@ -1048,8 +1048,23 @@ ssize_t AaptAssets::slurpFromArgs(Bundle* bundle) goto bail; } totalCount += count; - } - else { + } else if (type == kFileTypeRegular) { + ZipFile* zip = new ZipFile; + status_t err = zip->open(String8(res), ZipFile::kOpenReadOnly); + if (err != NO_ERROR) { + fprintf(stderr, "error opening zip file %s\n", res); + delete zip; + totalCount = -1; + goto bail; + } + + count = current->slurpResourceZip(bundle, zip, res); + delete zip; + if (count < 0) { + totalCount = count; + goto bail; + } + } else { fprintf(stderr, "ERROR: '%s' is not a directory\n", res); return UNKNOWN_ERROR; } @@ -1214,96 +1229,90 @@ bail: } ssize_t -AaptAssets::slurpResourceZip(Bundle* /* bundle */, const char* filename) +AaptAssets::slurpResourceZip(Bundle* bundle, ZipFile* zip, const char* fullZipPath) { + status_t err = NO_ERROR; int count = 0; SortedVector<AaptGroupEntry> entries; - ZipFile* zip = new ZipFile; - status_t err = zip->open(filename, ZipFile::kOpenReadOnly); - if (err != NO_ERROR) { - fprintf(stderr, "error opening zip file %s\n", filename); - count = err; - delete zip; - return -1; - } - const int N = zip->getNumEntries(); for (int i=0; i<N; i++) { ZipEntry* entry = zip->getEntryByIndex(i); - if (entry->getDeleted()) { + + if (!isEntryValid(bundle, entry)) { continue; } - String8 entryName(entry->getFileName()); + String8 entryName(entry->getFileName()); //ex: /res/drawable/foo.png + String8 entryLeaf = entryName.getPathLeaf(); //ex: foo.png + String8 entryDirFull = entryName.getPathDir(); //ex: res/drawable + String8 entryDir = entryDirFull.getPathLeaf(); //ex: drawable - String8 dirName = entryName.getPathDir(); - sp<AaptDir> dir = dirName == "" ? this : makeDir(dirName); + err = addEntry(entryName, entryLeaf, entryDirFull, entryDir, String8(fullZipPath), 0); + if (err) continue; - String8 resType; - AaptGroupEntry kind; + count++; + } - String8 remain; - if (entryName.walkPath(&remain) == kResourceDir) { - // these are the resources, pull their type out of the directory name - kind.initFromDirName(remain.walkPath().string(), &resType); - } else { - // these are untyped and don't have an AaptGroupEntry - } - if (entries.indexOf(kind) < 0) { - entries.add(kind); - mGroupEntries.add(kind); - } + return count; +} - // use the one from the zip file if they both exist. - dir->removeFile(entryName.getPathLeaf()); +status_t +AaptAssets::addEntry(const String8& entryName, const String8& entryLeaf, + const String8& /* entryDirFull */, const String8& entryDir, + const String8& zipFile, int compressionMethod) +{ + AaptGroupEntry group; + String8 resType; + bool b = group.initFromDirName(entryDir, &resType); + if (!b) { + fprintf(stderr, "invalid resource directory name: %s\n", entryDir.string()); + return -1; + } - sp<AaptFile> file = new AaptFile(entryName, kind, resType); - status_t err = dir->addLeafFile(entryName.getPathLeaf(), file); - if (err != NO_ERROR) { - fprintf(stderr, "err=%s entryName=%s\n", strerror(err), entryName.string()); - count = err; - goto bail; - } - file->setCompressionMethod(entry->getCompressionMethod()); + //This will do a cached lookup as well + sp<AaptDir> dir = makeDir(resType); //Does lookup as well on mdirs + sp<AaptFile> file = new AaptFile(entryName, group, resType, zipFile); + file->setCompressionMethod(compressionMethod); -#if 0 - if (entryName == "AndroidManifest.xml") { - printf("AndroidManifest.xml\n"); - } - printf("\n\nfile: %s\n", entryName.string()); -#endif - - size_t len = entry->getUncompressedLen(); - void* data = zip->uncompress(entry); - void* buf = file->editData(len); - memcpy(buf, data, len); - -#if 0 - const int OFF = 0; - const unsigned char* p = (unsigned char*)data; - const unsigned char* end = p+len; - p += OFF; - for (int i=0; i<32 && p < end; i++) { - printf("0x%03x ", i*0x10 + OFF); - for (int j=0; j<0x10 && p < end; j++) { - printf(" %02x", *p); - p++; - } - printf("\n"); - } -#endif + dir->addLeafFile(entryLeaf, file); - free(data); + sp<AaptDir> rdir = resDir(resType); + if (rdir == NULL) { + mResDirs.add(dir); + } - count++; + return NO_ERROR; +} + +bool AaptAssets::isEntryValid(Bundle* bundle, ZipEntry* entry) { + if (entry == NULL) { + return false; } -bail: - delete zip; - return count; + if (entry->getDeleted()) { + return false; + } + + // Entries that are not inside the internal zip path can be ignored + if (bundle->getInternalZipPath()) { + bool prefixed = (strncmp(entry->getFileName(), + bundle->getInternalZipPath(), + strlen(bundle->getInternalZipPath())) == 0); + if (!prefixed) { + return false; + } + } + + //Do not process directories + if (String8(entry->getFileName()).size() == 0) { + return false; + } + + return true; } + status_t AaptAssets::filter(Bundle* bundle) { WeakResourceFilter reqFilter; @@ -1530,7 +1539,7 @@ status_t AaptAssets::buildIncludedResources(Bundle* bundle) printf("Including resources from package: %s\n", includes[i].string()); } - if (!mIncludedAssets.addAssetPath(includes[i], NULL)) { + if (!mIncludedAssets.addAssetPath(includes[i], 0)) { fprintf(stderr, "ERROR: Asset package include '%s' not found.\n", includes[i].string()); return UNKNOWN_ERROR; diff --git a/tools/aapt/AaptAssets.h b/tools/aapt/AaptAssets.h index 4fdc964..5b66e4e 100644 --- a/tools/aapt/AaptAssets.h +++ b/tools/aapt/AaptAssets.h @@ -27,6 +27,8 @@ using namespace android; extern const char * const gDefaultIgnoreAssets; extern const char * gUserIgnoreAssets; +extern bool endsWith(const char* haystack, const char* needle); + bool valid_symbol_name(const String8& str); class AaptAssets; @@ -146,7 +148,7 @@ class AaptFile : public RefBase { public: AaptFile(const String8& sourceFile, const AaptGroupEntry& groupEntry, - const String8& resType) + const String8& resType, const String8& zipFile=String8("")) : mGroupEntry(groupEntry) , mResourceType(resType) , mSourceFile(sourceFile) @@ -154,9 +156,11 @@ public: , mDataSize(0) , mBufferSize(0) , mCompression(ZipEntry::kCompressStored) + , mZipFile(zipFile) { //printf("new AaptFile created %s\n", (const char*)sourceFile); } + virtual ~AaptFile() { free(mData); } @@ -188,6 +192,12 @@ public: // no compression is ZipEntry::kCompressStored. int getCompressionMethod() const { return mCompression; } void setCompressionMethod(int c) { mCompression = c; } + + // ZIP support. In this case the sourceFile is the zip entry name + // and zipFile is the path to the zip File. + // example: sourceFile = drawable-hdpi/foo.png, zipFile = res.zip + const String8& getZipFile() const { return mZipFile; } + private: friend class AaptGroup; @@ -199,6 +209,7 @@ private: size_t mDataSize; size_t mBufferSize; int mCompression; + String8 mZipFile; }; /** @@ -540,6 +551,8 @@ public: void addGroupEntry(const AaptGroupEntry& entry) { mGroupEntries.add(entry); } ssize_t slurpFromArgs(Bundle* bundle); + ssize_t slurpResourceZip(Bundle* bundle, ZipFile* zip, const char* fullZipPath); + bool isEntryValid(Bundle* bundle, ZipEntry* entry); sp<AaptSymbols> getSymbolsFor(const String8& name); @@ -593,7 +606,11 @@ private: const bool overwrite=false); ssize_t slurpResourceTree(Bundle* bundle, const String8& srcDir); - ssize_t slurpResourceZip(Bundle* bundle, const char* filename); + + + status_t addEntry(const String8& entryName, const String8& entryLeaf, + const String8& entryDirFull, const String8& entryDir, + const String8& zipFile, int compressionMethod); status_t filter(Bundle* bundle); diff --git a/tools/aapt/AaptConfig.cpp b/tools/aapt/AaptConfig.cpp index b12867a..fc42b8c 100644 --- a/tools/aapt/AaptConfig.cpp +++ b/tools/aapt/AaptConfig.cpp @@ -224,9 +224,20 @@ bool parse(const String8& str, ConfigDescription* out) { success: if (out != NULL) { +#ifndef HAVE_ANDROID_OS applyVersionForCompatibility(&config); +#else + // Calling applyVersionForCompatibility when compiling a theme can cause + // the path to be changed by AAPT which results in the themed assets not being + // loaded. The only time (as of right now) that aapt is run on an android device + // is when it is being used for themes, so this should be the correct behavior + // in this case. If AAPT is ever used on an android device for some other reason, + // we will need to change this. + printf("AAPT is running on Android, skipping applyVersionForCompatibility"); +#endif *out = config; } + return true; } diff --git a/tools/aapt/Android.mk b/tools/aapt/Android.mk index 08cbd5a..ee26729 100644 --- a/tools/aapt/Android.mk +++ b/tools/aapt/Android.mk @@ -133,5 +133,30 @@ LOCAL_STATIC_LIBRARIES += libaapt $(aaptHostStaticLibs) include $(BUILD_HOST_NATIVE_TEST) +# ========================================================== +# Build the device executable: aapt +# ========================================================== +ifneq ($(SDK_ONLY),true) +include $(CLEAR_VARS) + +LOCAL_MODULE := aapt +LOCAL_CFLAGS += $(aaptCFlags) +LOCAL_SRC_FILES := $(aaptSources) $(aaptMain) +LOCAL_C_INCLUDES += \ + $(aaptCIncludes) \ + bionic +LOCAL_SHARED_LIBRARIES := \ + libandroidfw \ + libutils \ + libcutils \ + libpng \ + liblog \ + libz +LOCAL_STATIC_LIBRARIES := \ + libexpat_static + +include $(BUILD_EXECUTABLE) + +endif # Not SDK_ONLY endif # No TARGET_BUILD_APPS or TARGET_BUILD_PDK diff --git a/tools/aapt/Bundle.h b/tools/aapt/Bundle.h index cbe7c5d..145eb64 100644 --- a/tools/aapt/Bundle.h +++ b/tools/aapt/Bundle.h @@ -1,5 +1,6 @@ // // Copyright 2006 The Android Open Source Project +// This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc. // // State bundle. Used to pass around stuff like command-line args. // @@ -49,7 +50,7 @@ public: Bundle(void) : mCmd(kCommandUnknown), mVerbose(false), mAndroidList(false), mForce(false), mGrayscaleTolerance(0), mMakePackageDirs(false), - mUpdate(false), mExtending(false), + mUpdate(false), mExtending(false), mExtendedPackageId(0), mRequireLocalization(false), mPseudolocalize(NO_PSEUDOLOCALIZATION), mWantUTF16(false), mValues(false), mIncludeMetaData(false), mCompressionMethod(0), mJunkPath(false), mOutputAPKFile(NULL), @@ -65,6 +66,8 @@ public: mProduct(NULL), mUseCrunchCache(false), mErrorOnFailedInsert(false), mErrorOnMissingConfigEntry(false), mOutputTextSymbols(NULL), mSingleCrunchInputFile(NULL), mSingleCrunchOutputFile(NULL), + mOutputResourcesApkFile(NULL), + mInternalZipPath(NULL), mInputAPKFile(NULL), mBuildSharedLibrary(false), mArgc(0), mArgv(NULL) {} @@ -94,6 +97,8 @@ public: void setUpdate(bool val) { mUpdate = val; } bool getExtending(void) const { return mExtending; } void setExtending(bool val) { mExtending = val; } + int getExtendedPackageId(void) const { return mExtendedPackageId; } + void setExtendedPackageId(int val) { mExtendedPackageId = val; } bool getRequireLocalization(void) const { return mRequireLocalization; } void setRequireLocalization(bool val) { mRequireLocalization = val; } short getPseudolocalize(void) const { return mPseudolocalize; } @@ -109,6 +114,10 @@ public: void setJunkPath(bool val) { mJunkPath = val; } const char* getOutputAPKFile() const { return mOutputAPKFile; } void setOutputAPKFile(const char* val) { mOutputAPKFile = val; } + const char* getOutputResApk() { return mOutputResourcesApkFile; } + const char* getInputAPKFile() { return mInputAPKFile; } + void setInputAPKFile(const char* val) { mInputAPKFile = val; } + void setOutputResApk(const char* val) { mOutputResourcesApkFile = val; } const char* getManifestPackageNameOverride() const { return mManifestPackageNameOverride; } void setManifestPackageNameOverride(const char * val) { mManifestPackageNameOverride = val; } const char* getInstrumentationPackageNameOverride() const { return mInstrumentationPackageNameOverride; } @@ -204,6 +213,8 @@ public: void setSingleCrunchInputFile(const char* val) { mSingleCrunchInputFile = val; } const char* getSingleCrunchOutputFile() const { return mSingleCrunchOutputFile; } void setSingleCrunchOutputFile(const char* val) { mSingleCrunchOutputFile = val; } + void setInternalZipPath(const char* val) { mInternalZipPath = val; } + const char* getInternalZipPath() const { return mInternalZipPath; } bool getBuildSharedLibrary() const { return mBuildSharedLibrary; } void setBuildSharedLibrary(bool val) { mBuildSharedLibrary = val; } void setNoVersionVectors(bool val) { mNoVersionVectors = val; } @@ -275,6 +286,7 @@ private: bool mMakePackageDirs; bool mUpdate; bool mExtending; + int mExtendedPackageId; bool mRequireLocalization; short mPseudolocalize; bool mWantUTF16; @@ -326,6 +338,9 @@ private: const char* mOutputTextSymbols; const char* mSingleCrunchInputFile; const char* mSingleCrunchOutputFile; + const char* mOutputResourcesApkFile; + const char* mInternalZipPath; + const char* mInputAPKFile; bool mBuildSharedLibrary; android::String8 mPlatformVersionCode; android::String8 mPlatformVersionName; diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp index 8a0a39c..58c65db 100644 --- a/tools/aapt/Command.cpp +++ b/tools/aapt/Command.cpp @@ -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,12 +473,20 @@ 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; @@ -497,18 +515,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)); + } + + ImpliedFeature* feature = &impliedFeatures->editValueAt(idx); + + // A non-sdk 23 implied feature takes precedence. + if (feature->impliedBySdk23 && !sdk23) { + feature->impliedBySdk23 = false; } - impliedFeatures->editValueAt(idx).reasons.add(String8(reason)); + 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) { @@ -536,9 +560,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,6 +578,15 @@ 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") { @@ -572,6 +607,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. */ @@ -712,7 +813,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 +837,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 +1023,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 +1055,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 +1067,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 +1100,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 +1123,8 @@ int doDump(Bundle* bundle) !actHostApduService && !actOffHostApduService && !actNotificationListenerService); } else if (withinProvider) { - hasDocumentsProvider |= actDocumentsProvider && hasRequiredSafAttributes; + hasDocumentsProvider |= + actDocumentsProvider && hasRequiredSafAttributes; } } withinIntentFilter = false; @@ -1125,7 +1265,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 +1276,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 +1320,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 +1349,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; } @@ -1297,90 +1442,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; + } - printUsesPermission(name, - AaptXml::getIntegerAttribute(tree, REQUIRED_ATTR, 1) == 0, - AaptXml::getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR)); - } else { + if (name == "") { + fprintf(stderr, "ERROR: missing 'android:name' for uses-permission\n"); + goto bail; + } + + 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 +1535,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 +1599,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 +1644,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 +1664,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 +1689,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 +1698,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; } @@ -1661,8 +1789,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 +1805,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 +1861,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 +1948,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 @@ -2439,21 +2573,48 @@ int doPackage(Bundle* bundle) goto bail; } - // Write the apk - if (outputAPKFile) { + if (outputAPKFile || bundle->getOutputResApk()) { // Gather all resources and add them to the APK Builder. The builder will then // figure out which Split they belong in. err = addResourcesToBuilder(assets, builder); if (err != NO_ERROR) { goto bail; } + } + + //Write the res apk + if (bundle->getOutputResApk()) { + const char* resPath = bundle->getOutputResApk(); + char *endptr; + int resApk_fd = strtol(resPath, &endptr, 10); + + if (*endptr == '\0') { + //OutputResDir was a file descriptor + //Assume there is only one set of assets, when we deal with actual split apks this may have to change + err = writeAPK(bundle, resApk_fd, builder->getBaseSplit(), true); + } else { + //Assume there is only one set of assets, when we deal with actual split apks this may have to change + err = writeAPK(bundle, String8(bundle->getOutputResApk()), builder->getBaseSplit(), true); + } + + if (err != NO_ERROR) { + fprintf(stderr, "ERROR: writing '%s' failed\n", resPath); + goto bail; + } + } + + // Write the apk + if (outputAPKFile) { + if (err != NO_ERROR) { + goto bail; + } const Vector<sp<ApkSplit> >& splits = builder->getSplits(); const size_t numSplits = splits.size(); for (size_t i = 0; i < numSplits; i++) { const sp<ApkSplit>& split = splits[i]; String8 outputPath = buildApkName(String8(outputAPKFile), split); - err = writeAPK(bundle, outputPath, split); + err = writeAPK(bundle, outputPath, split, false); if (err != NO_ERROR) { fprintf(stderr, "ERROR: packaging of '%s' failed\n", outputPath.string()); goto bail; diff --git a/tools/aapt/Images.cpp b/tools/aapt/Images.cpp index e4738f5..6c29ad9 100644 --- a/tools/aapt/Images.cpp +++ b/tools/aapt/Images.cpp @@ -27,6 +27,15 @@ png_write_aapt_file(png_structp png_ptr, png_bytep data, png_size_t length) } } +static void +png_read_mem_file(png_structp png_ptr, png_bytep data, png_size_t length) +{ + PngMemoryFile* pngFile = (PngMemoryFile*) png_get_io_ptr(png_ptr); + status_t err = pngFile->read(data, length); + if (err != NO_ERROR) { + png_error(png_ptr, "Read Error"); + } +} static void png_flush_aapt_file(png_structp /* png_ptr */) @@ -1269,43 +1278,51 @@ static bool write_png_protected(png_structp write_ptr, String8& printableName, p return true; } -status_t preProcessImage(const Bundle* bundle, const sp<AaptAssets>& /* assets */, +status_t preProcessImage(const Bundle* bundle, const sp<AaptAssets>& /* assets */, //non-theme path const sp<AaptFile>& file, String8* /* outNewLeafName */) { String8 ext(file->getPath().getPathExtension()); + bool isImageInZip = !file->getZipFile().isEmpty(); // We currently only process PNG images. if (strcmp(ext.string(), ".png") != 0) { return NO_ERROR; } + String8 printableName(file->getPrintableSource()); + + // We currently only process nine patch PNG images when building a theme apk. + Bundle* b = const_cast<Bundle*>(bundle); + if (!endsWith(printableName.string(), ".9.png") && b->getOutputResApk() != NULL) { + if (bundle->getVerbose()) { + printf("Skipping image: %s\n", file->getPrintableSource().string()); + } + return NO_ERROR; + } + // Example of renaming a file: //*outNewLeafName = file->getPath().getBasePath().getFileName(); //outNewLeafName->append(".nupng"); - String8 printableName(file->getPrintableSource()); - if (bundle->getVerbose()) { printf("Processing image: %s\n", printableName.string()); } png_structp read_ptr = NULL; png_infop read_info = NULL; - FILE* fp; + FILE* fp = NULL; image_info imageInfo; png_structp write_ptr = NULL; png_infop write_info = NULL; - status_t error = UNKNOWN_ERROR; + PngMemoryFile* pmf = NULL; + ZipFile* zip = NULL; - fp = fopen(file->getSourceFile().string(), "rb"); - if (fp == NULL) { - fprintf(stderr, "%s: ERROR: Unable to open PNG file\n", printableName.string()); - goto bail; - } + status_t error = UNKNOWN_ERROR; + const size_t nameLen = file->getPath().length(); read_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, (png_error_ptr)NULL, (png_error_ptr)NULL); if (!read_ptr) { @@ -1317,8 +1334,46 @@ status_t preProcessImage(const Bundle* bundle, const sp<AaptAssets>& /* assets * goto bail; } - if (!read_png_protected(read_ptr, printableName, read_info, file, fp, &imageInfo)) { - goto bail; + if (isImageInZip) { + pmf = new PngMemoryFile(); + zip = new ZipFile; + status_t err = zip->open(file->getZipFile(), ZipFile::kOpenReadOnly); + if (NO_ERROR != err) { + fprintf(stderr, "ERROR: Unable to open %s\n", file->getZipFile().string()); + goto bail; + } + + ZipEntry* entry = zip->getEntryByName(file->getSourceFile().string()); + size_t len = entry->getUncompressedLen(); + void* data = zip->uncompress(entry); + void* buf = file->editData(len); + memcpy(buf, data, len); + free(data); + + pmf->setDataSource((const char*)file->getData(), file->getSize()); + png_set_read_fn(read_ptr, pmf, png_read_mem_file); + read_png(printableName.string(), read_ptr, read_info, &imageInfo); + if (nameLen > 6) { + const char* name = file->getPath().string(); + if (name[nameLen-5] == '9' && name[nameLen-6] == '.') { + if (do_9patch(printableName.string(), &imageInfo) != NO_ERROR) { + goto bail; + } + } + } + } else { + fp = fopen(file->getSourceFile().string(), "rb"); + if (fp == NULL) { + fprintf(stderr, "%s: ERROR: Unable to open PNG file\n", printableName.string()); + goto bail; + } + if (!read_png_protected(read_ptr, printableName, read_info, file, fp, &imageInfo)) { + goto bail; + } + } + + if (isImageInZip) { + file->clearData(); } write_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, (png_error_ptr)NULL, @@ -1343,13 +1398,15 @@ status_t preProcessImage(const Bundle* bundle, const sp<AaptAssets>& /* assets * error = NO_ERROR; - if (bundle->getVerbose()) { + if (bundle->getVerbose() && !isImageInZip) { fseek(fp, 0, SEEK_END); size_t oldSize = (size_t)ftell(fp); size_t newSize = file->getSize(); float factor = ((float)newSize)/oldSize; int percent = (int)(factor*100); printf(" (processed image %s: %d%% size of source)\n", printableName.string(), percent); + } else if (bundle->getVerbose() && isImageInZip) { + printf(" (processed image %s)\n", printableName.string()); } bail: @@ -1362,6 +1419,12 @@ bail: if (write_ptr) { png_destroy_write_struct(&write_ptr, &write_info); } + if (zip) { + delete zip; + } + if (pmf) { + delete pmf; + } if (error != NO_ERROR) { fprintf(stderr, "ERROR: Failure processing PNG image %s\n", @@ -1375,7 +1438,7 @@ status_t preProcessImageToCache(const Bundle* bundle, const String8& source, con png_structp read_ptr = NULL; png_infop read_info = NULL; - FILE* fp; + FILE*volatile fp; image_info imageInfo; @@ -1511,3 +1574,17 @@ status_t postProcessImage(const Bundle* bundle, const sp<AaptAssets>& assets, return NO_ERROR; } + +status_t PngMemoryFile::read(png_bytep data, png_size_t length) { + if (data == NULL) + return -1; + + if ((mIndex + length) >= mDataSize) { + length = mDataSize - mIndex; + } + + memcpy(data, mData + mIndex, length); + mIndex += length; + + return NO_ERROR; +} diff --git a/tools/aapt/Images.h b/tools/aapt/Images.h index a0a94f8..3230ddc 100644 --- a/tools/aapt/Images.h +++ b/tools/aapt/Images.h @@ -10,6 +10,8 @@ #include "ResourceTable.h" #include "Bundle.h" +#include <png.h> + #include <utils/String8.h> #include <utils/RefBase.h> @@ -23,4 +25,18 @@ status_t preProcessImageToCache(const Bundle* bundle, const String8& source, con status_t postProcessImage(const Bundle* bundle, const sp<AaptAssets>& assets, ResourceTable* table, const sp<AaptFile>& file); +class PngMemoryFile { +public: + PngMemoryFile(void) + : mData(NULL), mDataSize(0), mIndex(0) + {} + void setDataSource(const char* data, uint32_t size) { mData = data; mDataSize = size; mIndex = 0; } + status_t read(png_bytep data, png_size_t length); + +private: + const char* mData; + uint32_t mDataSize; + uint32_t mIndex; +}; + #endif diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp index f832c60..aa67480 100644 --- a/tools/aapt/Main.cpp +++ b/tools/aapt/Main.cpp @@ -1,5 +1,6 @@ // // Copyright 2006 The Android Open Source Project +// This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc. // // Android Asset Packaging Tool main entry point. // @@ -14,6 +15,7 @@ #include <cstdlib> #include <getopt.h> #include <cassert> +#include <ctype.h> using namespace android; @@ -56,7 +58,7 @@ void usage(void) " xmltree Print the compiled xmls in the given assets.\n" " xmlstrings Print the strings of the given compiled xml assets.\n\n", gProgName); fprintf(stderr, - " %s p[ackage] [-d][-f][-m][-u][-v][-x][-z][-M AndroidManifest.xml] \\\n" + " %s p[ackage] [-d][-f][-m][-u][-v][-x[ extending-resource-id]][-z][-M AndroidManifest.xml] \\\n" " [-0 extension [-0 extension ...]] [-g tolerance] [-j jarfile] \\\n" " [--debug-mode] [--min-sdk-version VAL] [--target-sdk-version VAL] \\\n" " [--app-version VAL] [--app-version-name TEXT] [--custom-package VAL] \\\n" @@ -114,7 +116,7 @@ void usage(void) " -m make package directories under location specified by -J\n" " -u update existing packages (add new, replace older, remove deleted files)\n" " -v verbose output\n" - " -x create extending (non-application) resource IDs\n" + " -x either create or assign (if specified) extending (non-application) resource IDs\n" " -z require localization of resource attributes marked with\n" " localization=\"suggested\"\n" " -A additional directory in which to find raw asset files\n" @@ -347,6 +349,14 @@ int main(int argc, char* const argv[]) break; case 'x': bundle.setExtending(true); + argc--; + argv++; + if (!argc || !isdigit(argv[0][0])) { + argc++; + argv--; + } else { + bundle.setExtendedPackageId(atoi(argv[0])); + } break; case 'z': bundle.setRequireLocalization(true); @@ -428,6 +438,17 @@ int main(int argc, char* const argv[]) convertPath(argv[0]); bundle.setAndroidManifestFile(argv[0]); break; + case 'X': + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '-X' option\n"); + wantUsage = true; + goto bail; + } + convertPath(argv[0]); + bundle.setInternalZipPath(argv[0]); + break; case 'P': argc--; argv++; @@ -497,6 +518,28 @@ int main(int argc, char* const argv[]) bundle.setCompressionMethod(ZipEntry::kCompressStored); } break; + case 'Z': + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '-Z' option\n"); + wantUsage = true; + goto bail; + } + convertPath(argv[0]); + bundle.setInputAPKFile(argv[0]); + break; + case 'r': + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '-r' option\n"); + wantUsage = true; + goto bail; + } + convertPath(argv[0]); + bundle.setOutputResApk(argv[0]); + break; case '-': if (strcmp(cp, "-debug-mode") == 0) { bundle.setDebugMode(true); diff --git a/tools/aapt/Main.h b/tools/aapt/Main.h index e84c4c5..0b8adbe 100644 --- a/tools/aapt/Main.h +++ b/tools/aapt/Main.h @@ -42,7 +42,14 @@ extern int calcPercent(long uncompressedLen, long compressedLen); extern android::status_t writeAPK(Bundle* bundle, const android::String8& outputFile, - const android::sp<OutputSet>& outputSet); + const android::sp<OutputSet>& outputSet, + bool isOverlay); +extern android::status_t writeAPK(Bundle* bundle, + int fd, + const android::sp<OutputSet>& outputSet, + bool isOverlay); +extern android::status_t writeResFile(FILE* fp, const sp<AaptAssets>& assets, sp<ApkBuilder>& builder); +extern sp<AaptFile> getResourceFile(const sp<AaptAssets>& assets, bool makeIfNecessary=true); extern android::status_t updatePreProcessedCache(Bundle* bundle); diff --git a/tools/aapt/Package.cpp b/tools/aapt/Package.cpp index cb244ec..3daf644 100644 --- a/tools/aapt/Package.cpp +++ b/tools/aapt/Package.cpp @@ -37,8 +37,10 @@ static const char* kNoCompressExt[] = { }; /* fwd decls, so I can write this downward */ -ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<const OutputSet>& outputSet); bool processFile(Bundle* bundle, ZipFile* zip, String8 storageName, const sp<const AaptFile>& file); +ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<const OutputSet>& outputSet, bool isOverlay); +bool processOverlayFile(Bundle* bundle, ZipFile* zip, + String8 storageName, const sp<const AaptFile>& file); bool okayToCompress(Bundle* bundle, const String8& pathName); ssize_t processJarFiles(Bundle* bundle, ZipFile* zip); @@ -49,7 +51,81 @@ ssize_t processJarFiles(Bundle* bundle, ZipFile* zip); * On success, "bundle->numPackages" will be the number of Zip packages * we created. */ -status_t writeAPK(Bundle* bundle, const String8& outputFile, const sp<OutputSet>& outputSet) +status_t writeAPK(Bundle* bundle, ZipFile* zip, const char* outputFileName, + const sp<OutputSet>& outputSet, bool isOverlay) +{ + status_t result = NO_ERROR; + int count; + + if (bundle->getVerbose()) { + printf("Writing all files...\n"); + } + + count = processAssets(bundle, zip, outputSet, isOverlay); + if (count < 0) { + fprintf(stderr, "ERROR: unable to process assets while packaging '%s'\n", + outputFileName); + result = count; + goto bail; + } + + if (bundle->getVerbose()) { + printf("Generated %d file%s\n", count, (count==1) ? "" : "s"); + } + + if (!isOverlay) { + count = processJarFiles(bundle, zip); + if (count < 0) { + fprintf(stderr, "ERROR: unable to process jar files while packaging '%s'\n", + outputFileName); + result = count; + goto bail; + } + + if (bundle->getVerbose()) + printf("Included %d file%s from jar/zip files.\n", count, (count==1) ? "" : "s"); + } + + result = NO_ERROR; + + /* + * Check for cruft. We set the "marked" flag on all entries we created + * or decided not to update. If the entry isn't already slated for + * deletion, remove it now. + */ + { + if (bundle->getVerbose()) + printf("Checking for deleted files\n"); + int i, removed = 0; + for (i = 0; i < zip->getNumEntries(); i++) { + ZipEntry* entry = zip->getEntryByIndex(i); + + if (!entry->getMarked() && entry->getDeleted()) { + if (bundle->getVerbose()) { + printf(" (removing crufty '%s')\n", + entry->getFileName()); + } + zip->remove(entry); + removed++; + } + } + if (bundle->getVerbose() && removed > 0) + printf("Removed %d file%s\n", removed, (removed==1) ? "" : "s"); + } + + /* tell Zip lib to process deletions and other pending changes */ + result = zip->flush(); + if (result != NO_ERROR) { + fprintf(stderr, "ERROR: Zip flush failed, archive may be hosed\n"); + goto bail; + } + +bail: + return result; +} + +status_t writeAPK(Bundle* bundle, const String8& outputFile, + const sp<OutputSet>& outputSet, bool isOverlay) { #if BENCHMARK fprintf(stdout, "BENCHMARK: Starting APK Bundling \n"); @@ -58,7 +134,6 @@ status_t writeAPK(Bundle* bundle, const String8& outputFile, const sp<OutputSet> status_t result = NO_ERROR; ZipFile* zip = NULL; - int count; //bundle->setPackageCount(0); @@ -105,64 +180,10 @@ status_t writeAPK(Bundle* bundle, const String8& outputFile, const sp<OutputSet> goto bail; } - if (bundle->getVerbose()) { - printf("Writing all files...\n"); - } - - count = processAssets(bundle, zip, outputSet); - if (count < 0) { - fprintf(stderr, "ERROR: unable to process assets while packaging '%s'\n", - outputFile.string()); - result = count; - goto bail; - } - - if (bundle->getVerbose()) { - printf("Generated %d file%s\n", count, (count==1) ? "" : "s"); - } - - count = processJarFiles(bundle, zip); - if (count < 0) { - fprintf(stderr, "ERROR: unable to process jar files while packaging '%s'\n", - outputFile.string()); - result = count; - goto bail; - } - - if (bundle->getVerbose()) - printf("Included %d file%s from jar/zip files.\n", count, (count==1) ? "" : "s"); - - result = NO_ERROR; + result = writeAPK(bundle, zip, outputFile.string(), outputSet, isOverlay); - /* - * Check for cruft. We set the "marked" flag on all entries we created - * or decided not to update. If the entry isn't already slated for - * deletion, remove it now. - */ - { - if (bundle->getVerbose()) - printf("Checking for deleted files\n"); - int i, removed = 0; - for (i = 0; i < zip->getNumEntries(); i++) { - ZipEntry* entry = zip->getEntryByIndex(i); - - if (!entry->getMarked() && entry->getDeleted()) { - if (bundle->getVerbose()) { - printf(" (removing crufty '%s')\n", - entry->getFileName()); - } - zip->remove(entry); - removed++; - } - } - if (bundle->getVerbose() && removed > 0) - printf("Removed %d file%s\n", removed, (removed==1) ? "" : "s"); - } - - /* tell Zip lib to process deletions and other pending changes */ - result = zip->flush(); if (result != NO_ERROR) { - fprintf(stderr, "ERROR: Zip flush failed, archive may be hosed\n"); + fprintf(stderr, "ERROR: Writing apk failed\n"); goto bail; } @@ -215,7 +236,98 @@ bail: return result; } -ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<const OutputSet>& outputSet) +/* + * The directory hierarchy looks like this: + * "outputDir" and "assetRoot" are existing directories. + * + * On success, "bundle->numPackages" will be the number of Zip packages + * we created. + */ +status_t writeAPK(Bundle* bundle, int fd, const sp<OutputSet>& outputSet, bool isOverlay) +{ + #if BENCHMARK + fprintf(stdout, "BENCHMARK: Starting APK Bundling \n"); + long startAPKTime = clock(); + #endif /* BENCHMARK */ + + status_t result = NO_ERROR; + ZipFile* zip = NULL; + + status_t status; + zip = new ZipFile; + status = zip->openfd(fd, ZipFile::kOpenReadWrite); + if (status != NO_ERROR) { + fprintf(stderr, "ERROR: unable to open file as Zip file for writing\n"); + result = PERMISSION_DENIED; + goto bail; + } + + result = writeAPK(bundle, zip, "file_descriptor", outputSet, isOverlay); + + if (result != NO_ERROR) { + fprintf(stderr, "ERROR: Writing apk failed\n"); + goto bail; + } + + /* anything here? */ + if (zip->getNumEntries() == 0) { + if (bundle->getVerbose()) { + printf("Archive is empty -- removing\n"); + } + delete zip; // close the file so we can remove it in Win32 + zip = NULL; + close(fd); + } + + assert(result == NO_ERROR); + +bail: + delete zip; // must close before remove in Win32 + close(fd); + if (result != NO_ERROR) { + if (bundle->getVerbose()) { + printf("Removing archive due to earlier failures\n"); + } + } + + if (result == NO_ERROR && bundle->getVerbose()) + printf("Done!\n"); + + #if BENCHMARK + fprintf(stdout, "BENCHMARK: End APK Bundling. Time Elapsed: %f ms \n",(clock() - startAPKTime)/1000.0); + #endif /* BENCHMARK */ + return result; +} + +status_t writeResFile(FILE* fp, const sp<AaptAssets>& /* assets */, sp<ApkBuilder>& builder) { + if (fp == NULL) { + fprintf(stderr, "Unable to open resFile for writing resTable\n"); + return PERMISSION_DENIED; + } + + sp<ApkSplit> split = builder->getBaseSplit(); + const std::set<OutputEntry>& entries = split->getEntries(); + std::set<OutputEntry>::const_iterator iter = entries.begin(); + for (; iter != entries.end(); iter++) { + const OutputEntry& entry = *iter; + + if (entry.getPath() == String8("resources.arsc")) { + sp<const AaptFile> resFile = entry.getFile(); + + int count = 0; + count = fwrite(resFile->getData(), 1, resFile->getSize(), fp); + + if (count == 0) { + fprintf(stderr, "Nothing written to resFile\n"); + } + } + } + + return NO_ERROR; +} + +ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<const OutputSet>& outputSet, + bool isOverlay) { ssize_t count = 0; const std::set<OutputEntry>& entries = outputSet->getEntries(); @@ -227,7 +339,9 @@ ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<const OutputSet>& o } else { String8 storagePath(entry.getPath()); storagePath.convertToResPath(); - if (!processFile(bundle, zip, storagePath, entry.getFile())) { + bool ret = isOverlay ? processOverlayFile(bundle, zip, storagePath, entry.getFile()) + : processFile(bundle, zip, storagePath, entry.getFile()); + if (!ret) { return UNKNOWN_ERROR; } count++; @@ -360,6 +474,76 @@ bool processFile(Bundle* bundle, ZipFile* zip, } /* + * Process a regular file, adding it to the archive if appropriate. + * + * This function is intended for use when creating a cached overlay package. + * Only xml and .9.png files are processed and added to the package. + * + * If we're in "update" mode, and the file already exists in the archive, + * delete the existing entry before adding the new one. + */ +bool processOverlayFile(Bundle* bundle, ZipFile* zip, + String8 storageName, const sp<const AaptFile>& file) +{ + const bool hasData = file->hasData(); + + storageName.convertToResPath(); + ZipEntry* entry; + bool fromGzip = false; + status_t result; + + if (strcasecmp(storageName.getPathExtension().string(), ".gz") == 0) { + fromGzip = true; + storageName = storageName.getBasePath(); + } + + if (bundle->getUpdate()) { + entry = zip->getEntryByName(storageName.string()); + if (entry != NULL) { + /* file already exists in archive; there can be only one */ + if (entry->getMarked()) { + fprintf(stderr, + "ERROR: '%s' exists twice (check for with & w/o '.gz'?)\n", + file->getPrintableSource().string()); + return false; + } + zip->remove(entry); + } + } + + if (hasData) { + const char* name = storageName.string(); + if (endsWith(name, ".9.png") || endsWith(name, ".xml") || endsWith(name, ".arsc")) { + result = zip->add(file->getData(), file->getSize(), storageName.string(), + file->getCompressionMethod(), &entry); + if (result == NO_ERROR) { + if (bundle->getVerbose()) { + printf(" '%s'%s", storageName.string(), fromGzip ? " (from .gz)" : ""); + if (entry->getCompressionMethod() == ZipEntry::kCompressStored) { + printf(" (not compressed)\n"); + } else { + printf(" (compressed %d%%)\n", calcPercent(entry->getUncompressedLen(), + entry->getCompressedLen())); + } + } + entry->setMarked(true); + } else { + if (result == ALREADY_EXISTS) { + fprintf(stderr, " Unable to add '%s': file already in archive (try '-u'?)\n", + file->getPrintableSource().string()); + } else { + fprintf(stderr, " Unable to add '%s': Zip add failed\n", + file->getPrintableSource().string()); + } + return false; + } + } + } + + return true; +} + +/* * Determine whether or not we want to try to compress this file based * on the file extension. */ diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp index 5d20815..b796b27 100644 --- a/tools/aapt/Resource.cpp +++ b/tools/aapt/Resource.cpp @@ -221,6 +221,24 @@ bool isValidResourceType(const String8& type) || type == "color" || type == "menu" || type == "mipmap"; } +sp<AaptFile> getResourceFile(const sp<AaptAssets>& assets, bool makeIfNecessary) +{ + sp<AaptGroup> group = assets->getFiles().valueFor(String8("resources.arsc")); + sp<AaptFile> file; + if (group != NULL) { + file = group->getFiles().valueFor(AaptGroupEntry()); + if (file != NULL) { + return file; + } + } + + if (!makeIfNecessary) { + return NULL; + } + return assets->addFile(String8("resources.arsc"), AaptGroupEntry(), String8(), + NULL, String8()); +} + static status_t parsePackage(Bundle* bundle, const sp<AaptAssets>& assets, const sp<AaptGroup>& grp) { @@ -1170,7 +1188,9 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuil packageType = ResourceTable::AppFeature; } - ResourceTable table(bundle, String16(assets->getPackage()), packageType); + int extendedPackageId = bundle->getExtendedPackageId(); + + ResourceTable table(bundle, String16(assets->getPackage()), packageType, extendedPackageId); err = table.addIncludedResources(bundle, assets); if (err != NO_ERROR) { return err; @@ -1252,7 +1272,7 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuil bool hasErrors = false; if (drawables != NULL) { - if (bundle->getOutputAPKFile() != NULL) { + if (bundle->getOutputAPKFile() != NULL || bundle->getOutputResApk()) { err = preProcessImages(bundle, assets, drawables, "drawable"); } if (err == NO_ERROR) { @@ -2523,7 +2543,7 @@ static status_t writeSymbolClass( fprintf(fp, "%s/** %s\n", getIndentSpace(indent), cmt.string()); - } else if (sym.isPublic && !includePrivate) { + } else if (sym.isPublic && !includePrivate && kIsDebug) { sym.sourcePos.warning("No comment for public symbol %s:%s/%s", assets->getPackage().string(), className.string(), String8(sym.name).string()); @@ -2569,7 +2589,7 @@ static status_t writeSymbolClass( "%s */\n", getIndentSpace(indent), cmt.string(), getIndentSpace(indent)); - } else if (sym.isPublic && !includePrivate) { + } else if (sym.isPublic && !includePrivate && kIsDebug) { sym.sourcePos.warning("No comment for public symbol %s:%s/%s", assets->getPackage().string(), className.string(), String8(sym.name).string()); diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp index d5a09d8..fb0299e 100644 --- a/tools/aapt/ResourceTable.cpp +++ b/tools/aapt/ResourceTable.cpp @@ -1467,6 +1467,11 @@ status_t compileResourceFile(Bundle* bundle, } } } else if (strcmp16(block.getElementName(&len), string_array16.string()) == 0) { + // Note the existence and locale of every string array we process + char rawLocale[RESTABLE_MAX_LOCALE_LEN]; + curParams.getBcp47Locale(rawLocale); + String8 locale(rawLocale); + String16 name; // Check whether these strings need valid formats. // (simplified form of what string16 does above) bool isTranslatable = false; @@ -1477,7 +1482,9 @@ status_t compileResourceFile(Bundle* bundle, for (size_t i = 0; i < n; i++) { size_t length; const char16_t* attr = block.getAttributeName(i, &length); - if (strcmp16(attr, formatted16.string()) == 0) { + if (strcmp16(attr, name16.string()) == 0) { + name.setTo(block.getAttributeStringValue(i, &length)); + } else if (strcmp16(attr, formatted16.string()) == 0) { const char16_t* value = block.getAttributeStringValue(i, &length); if (strcmp16(value, false16.string()) == 0) { curIsFormatted = false; @@ -1486,6 +1493,15 @@ status_t compileResourceFile(Bundle* bundle, const char16_t* value = block.getAttributeStringValue(i, &length); if (strcmp16(value, false16.string()) == 0) { isTranslatable = false; + // Untranslatable string arrays must only exist + // in the default [empty] locale + if (locale.size() > 0) { + SourcePos(in->getPrintableSource(), block.getLineNumber()).warning( + "string-array '%s' marked untranslatable but exists" + " in locale '%s'\n", String8(name).string(), + locale.string()); + // hasErrors = localHasErrors = true; + } } } } @@ -1753,7 +1769,7 @@ status_t compileResourceFile(Bundle* bundle, return hasErrors ? STATUST(UNKNOWN_ERROR) : NO_ERROR; } -ResourceTable::ResourceTable(Bundle* bundle, const String16& assetsPackage, ResourceTable::PackageType type) +ResourceTable::ResourceTable(Bundle* bundle, const String16& assetsPackage, ResourceTable::PackageType type, ssize_t pkgIdOverride) : mAssetsPackage(assetsPackage) , mPackageType(type) , mTypeIdOffset(0) @@ -1779,6 +1795,11 @@ ResourceTable::ResourceTable(Bundle* bundle, const String16& assetsPackage, Reso assert(0); break; } + + if (pkgIdOverride != 0) { + packageId = pkgIdOverride; + } + sp<Package> package = new Package(mAssetsPackage, packageId); mPackages.add(assetsPackage, package); mOrderedPackages.add(package); @@ -2825,8 +2846,9 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<const ResourceFilter>& for (size_t i = 0; i < basePackageCount; i++) { size_t packageId = table.getBasePackageId(i); String16 packageName(table.getBasePackageName(i)); - if (packageId > 0x01 && packageId != 0x7f && - packageName != String16("android")) { + if (packageId > 0x01 && packageId != 0x7f && packageId != 0x3f && + packageName != String16("android") + && packageName != String16("cyanogenmod.platform")) { libraryPackages.add(sp<Package>(new Package(packageName, packageId))); } } diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h index c4bdf09..2c2df19 100644 --- a/tools/aapt/ResourceTable.h +++ b/tools/aapt/ResourceTable.h @@ -109,7 +109,8 @@ public: const ConfigDescription& sourceConfig, const int sdkVersionToGenerate); - ResourceTable(Bundle* bundle, const String16& assetsPackage, PackageType type); + ResourceTable(Bundle* bundle, const String16& assetsPackage, PackageType type, + ssize_t pkgIdOverride); const String16& getAssetsPackage() const { return mAssetsPackage; diff --git a/tools/aapt/StringPool.cpp b/tools/aapt/StringPool.cpp index 9908c44..0d59fae 100644 --- a/tools/aapt/StringPool.cpp +++ b/tools/aapt/StringPool.cpp @@ -127,7 +127,7 @@ int StringPool::entry::compare(const entry& o) const { } StringPool::StringPool(bool utf8) : - mUTF8(utf8), mValues(-1) + mUTF8(utf8) { } @@ -144,8 +144,8 @@ ssize_t StringPool::add(const String16& value, const Vector<entry_style_span>& s ssize_t StringPool::add(const String16& value, bool mergeDuplicates, const String8* configTypeName, const ResTable_config* config) { - ssize_t vidx = mValues.indexOfKey(value); - ssize_t pos = vidx >= 0 ? mValues.valueAt(vidx) : -1; + auto it = mValues.find(value); + ssize_t pos = it != mValues.end() ? it->second : -1; ssize_t eidx = pos >= 0 ? mEntryArray.itemAt(pos) : -1; if (eidx < 0) { eidx = mEntries.add(entry(value)); @@ -192,21 +192,21 @@ ssize_t StringPool::add(const String16& value, } } - const bool first = vidx < 0; + const bool first = (it == mValues.end()); const bool styled = (pos >= 0 && (size_t)pos < mEntryStyleArray.size()) ? mEntryStyleArray[pos].spans.size() : 0; if (first || styled || !mergeDuplicates) { pos = mEntryArray.add(eidx); if (first) { - vidx = mValues.add(value, pos); + mValues[value] = pos; } entry& ent = mEntries.editItemAt(eidx); ent.indices.add(pos); } if (kIsDebug) { - printf("Adding string %s to pool: pos=%zd eidx=%zd vidx=%zd\n", - String8(value).string(), SSIZE(pos), SSIZE(eidx), SSIZE(vidx)); + printf("Adding string %s to pool: pos=%zd eidx=%zd\n", + String8(value).string(), SSIZE(pos), SSIZE(eidx)); } return pos; @@ -349,14 +349,18 @@ void StringPool::sortByConfig() // Now trim any entries at the end of the new style array that are // not needed. - for (ssize_t i=newEntryStyleArray.size()-1; i>=0; i--) { + ssize_t i; + for (i=newEntryStyleArray.size()-1; i>=0; i--) { const entry_style& style = newEntryStyleArray[i]; if (style.spans.size() > 0) { // That's it. break; } - // This one is not needed; remove. - newEntryStyleArray.removeAt(i); + } + + ssize_t nToRemove=newEntryStyleArray.size()-(i+1); + if (nToRemove) { + newEntryStyleArray.removeItemsAt(i+1, nToRemove); } // All done, install the new data structures and upate mValues with @@ -367,7 +371,7 @@ void StringPool::sortByConfig() mValues.clear(); for (size_t i=0; i<mEntries.size(); i++) { const entry& ent = mEntries[i]; - mValues.add(ent.value, ent.indices[0]); + mValues[ent.value] = ent.indices[0]; } #if 0 @@ -610,9 +614,10 @@ ssize_t StringPool::offsetForString(const String16& val) const const Vector<size_t>* StringPool::offsetsForString(const String16& val) const { - ssize_t pos = mValues.valueFor(val); - if (pos < 0) { + auto it = mValues.find(val); + if (it == mValues.end()) { return NULL; } + ssize_t pos = it->second; return &mEntries[mEntryArray[pos]].indices; } diff --git a/tools/aapt/StringPool.h b/tools/aapt/StringPool.h index dbe8c85..3014a3b 100644 --- a/tools/aapt/StringPool.h +++ b/tools/aapt/StringPool.h @@ -19,6 +19,7 @@ #include <fcntl.h> #include <ctype.h> #include <errno.h> +#include <map> #include <libexpat/expat.h> @@ -175,7 +176,7 @@ private: // Unique set of all the strings added to the pool, mapped to // the first index of mEntryArray where the value was added. - DefaultKeyedVector<String16, ssize_t> mValues; + std::map<String16, ssize_t> mValues; // This array maps from the original position a string was placed at // in mEntryArray to its new position after being sorted with sortByConfig(). Vector<size_t> mOriginalPosToNewPos; diff --git a/tools/aapt/XMLNode.cpp b/tools/aapt/XMLNode.cpp index ca3f687..6ab55d5 100644 --- a/tools/aapt/XMLNode.cpp +++ b/tools/aapt/XMLNode.cpp @@ -11,6 +11,7 @@ #include <utils/ByteOrder.h> #include <errno.h> #include <string.h> +#include <androidfw/AssetManager.h> #ifndef HAVE_MS_C_RUNTIME #define O_BINARY 0 @@ -583,9 +584,51 @@ status_t parseXMLResource(const sp<AaptFile>& file, ResXMLTree* outTree, return NO_ERROR; } +sp<XMLNode> XMLNode::parseFromZip(const sp<AaptFile>& file) { + AssetManager assets; + int32_t cookie; + + if (!assets.addAssetPath(file->getZipFile(), &cookie)) { + fprintf(stderr, "Error: Could not open path %s\n", file->getZipFile().string()); + return NULL; + } + + Asset* asset = assets.openNonAsset(cookie, file->getSourceFile(), Asset::ACCESS_BUFFER); + ssize_t len = asset->getLength(); + const void* buf = asset->getBuffer(false); + + XML_Parser parser = XML_ParserCreateNS(NULL, 1); + ParseState state; + state.filename = file->getPrintableSource(); + state.parser = parser; + XML_SetUserData(parser, &state); + XML_SetElementHandler(parser, startElement, endElement); + XML_SetNamespaceDeclHandler(parser, startNamespace, endNamespace); + XML_SetCharacterDataHandler(parser, characterData); + XML_SetCommentHandler(parser, commentData); + + bool done = true; + if (XML_Parse(parser, (char*) buf, len, done) == XML_STATUS_ERROR) { + SourcePos(file->getSourceFile(), (int)XML_GetCurrentLineNumber(parser)).error( + "Error parsing XML: %s\n", XML_ErrorString(XML_GetErrorCode(parser))); + return NULL; + } + XML_ParserFree(parser); + if (state.root == NULL) { + SourcePos(file->getSourceFile(), -1).error("No XML data generated when parsing"); + } + return state.root; +} + sp<XMLNode> XMLNode::parse(const sp<AaptFile>& file) { char buf[16384]; + + //Check for zip first + if (file->getZipFile().length() > 0) { + return parseFromZip(file); + } + int fd = open(file->getSourceFile().string(), O_RDONLY | O_BINARY); if (fd < 0) { SourcePos(file->getSourceFile(), -1).error("Unable to open file for read: %s", diff --git a/tools/aapt/XMLNode.h b/tools/aapt/XMLNode.h index 3161f65..905c6fd 100644 --- a/tools/aapt/XMLNode.h +++ b/tools/aapt/XMLNode.h @@ -188,6 +188,9 @@ private: status_t flatten_node(const StringPool& strings, const sp<AaptFile>& dest, bool stripComments, bool stripRawValues) const; + static sp<XMLNode> parseFromZip(const sp<AaptFile>& file); + static sp<XMLNode> parseFromAsset(const Asset& asset); + String16 mNamespacePrefix; String16 mNamespaceUri; String16 mElementName; diff --git a/tools/aapt/ZipFile.cpp b/tools/aapt/ZipFile.cpp index 36f4e73..8a4eef5 100644 --- a/tools/aapt/ZipFile.cpp +++ b/tools/aapt/ZipFile.cpp @@ -130,6 +130,71 @@ status_t ZipFile::open(const char* zipFileName, int flags) } /* + * Open a file and parse its guts. + */ +status_t ZipFile::openfd(int fd, int flags) +{ + bool newArchive = true; + + 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 + + /* 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 = fdopen(fd, openflags); + if (mZipFp == NULL) { + int err = errno; + ALOGD("fdopen failed: %s\n", strerror(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 diff --git a/tools/aapt/ZipFile.h b/tools/aapt/ZipFile.h index 7877550..d5abbf1 100644 --- a/tools/aapt/ZipFile.h +++ b/tools/aapt/ZipFile.h @@ -64,6 +64,7 @@ public: kOpenTruncate = 0x08, // if it exists, empty it }; status_t open(const char* zipFileName, int flags); + status_t openfd(int fd, int flags); /* * Add a file to the end of the archive. Specify whether you want the diff --git a/tools/aapt/tests/ZipReading_test.cpp b/tools/aapt/tests/ZipReading_test.cpp new file mode 100644 index 0000000..4b4f2da --- /dev/null +++ b/tools/aapt/tests/ZipReading_test.cpp @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * 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 <utils/String8.h> +#include <gtest/gtest.h> +#include <gmock/gmock.h> +#include <utils/KeyedVector.h> + +#include "mocks/MockZipFile.h" +#include "mocks/MockZipEntry.h" + +#include "AaptConfig.h" +#include "ConfigDescription.h" +#include "TestHelper.h" + +#include "AaptAssets.h" + +using android::String8; +using namespace testing; + +// A path to an apk that would be considered valid +#define VALID_APK_FILE "/valid/valid.apk" + +// Internal zip path to a dir that aapt is being asked to compile +#define COMPILING_OVERLAY_DIR "/assets/overlays/com.interesting.app" + +// Internal zip path to a valid resource. aapt is expected to compile this resource. +#define COMPILING_OVERLAY_FILE COMPILING_OVERLAY_DIR "/res/drawable-xxhdpi/foo.png"; + +// Internal zip path to another overlay dir that is NOT being compiled +#define NOT_COMPILING_OVERLAY_DIR "/assets/overlays/com.boring.app" + +// Internal zip path to a resource for an overlay that is NOT compiling. aapt is expected to ignore +#define NOT_COMPILING_OVERLAY_FILE COMPILING_OVERLAY_DIR "/assets/overlays/com.boring.app" + +static ::testing::AssertionResult TestParse(const String8& input, ConfigDescription* config=NULL) { + if (AaptConfig::parse(String8(input), config)) { + return ::testing::AssertionSuccess() << input << " was successfully parsed"; + } + return ::testing::AssertionFailure() << input << " could not be parsed"; +} + +static ::testing::AssertionResult TestParse(const char* input, ConfigDescription* config=NULL) { + return TestParse(String8(input), config); +} + +TEST(ZipReadingTest, TestValidZipEntryIsAdded) { + MockZipFile zip; + MockZipEntry entry1; + const char* zipFile = VALID_APK_FILE; + const char* validFilename = COMPILING_OVERLAY_FILE; + + EXPECT_CALL(entry1, getFileName()) + .WillRepeatedly(Return(validFilename)); + + EXPECT_CALL(zip, getNumEntries()) + .Times(1) + .WillRepeatedly(Return(1)); + + EXPECT_CALL(zip, getEntryByIndex(_)) + .Times(1) + .WillOnce(Return(&entry1)); + + sp<AaptAssets> assets = new AaptAssets(); + Bundle bundle; + bundle.setInternalZipPath(COMPILING_OVERLAY_DIR); + ssize_t count = assets->slurpResourceZip(&bundle, &zip, zipFile); + + Vector<sp<AaptDir> > dirs = assets->resDirs(); + EXPECT_EQ(1, dirs.size()); + EXPECT_EQ(1, count); +} + +TEST(ZipReadingTest, TestDifferentThemeEntryNotAdded) { + MockZipFile zip; + MockZipEntry entry1; + const char* zipFile = VALID_APK_FILE; + const char* invalidFile = NOT_COMPILING_OVERLAY_FILE; + + EXPECT_CALL(entry1, getFileName()) + .WillRepeatedly(Return(invalidFile)); + + EXPECT_CALL(zip, getNumEntries()) + .WillRepeatedly(Return(1)); + + EXPECT_CALL(zip, getEntryByIndex(_)) + .Times(1) + .WillOnce(Return(&entry1)); + + sp<AaptAssets> assets = new AaptAssets(); + Bundle bundle; + bundle.setInternalZipPath(COMPILING_OVERLAY_DIR); + ssize_t count = assets->slurpResourceZip(&bundle, &zip, zipFile); + + Vector<sp<AaptDir> > dirs = assets->resDirs(); + EXPECT_EQ(0, dirs.size()); + EXPECT_EQ(0, count); +} + +TEST(ZipReadingTest, TestOutsideEntryMarkedInvalid) { + Bundle bundle; + bundle.setInternalZipPath("VALID_OVERLAY_DIR"); + MockZipEntry invalidEntry; + const char* invalidFile = NOT_COMPILING_OVERLAY_FILE; + + EXPECT_CALL(invalidEntry, getFileName()) + .WillRepeatedly(Return(invalidFile)); + + sp<AaptAssets> assets = new AaptAssets(); + bool result = assets->isEntryValid(&bundle, &invalidEntry); + + EXPECT_FALSE(result); +} + +TEST(ZipReadingTest, TestNullEntryIsInvalid) { + Bundle bundle; + bundle.setInternalZipPath(COMPILING_OVERLAY_DIR); + MockZipEntry invalidEntry; + const char* invalidFile = NOT_COMPILING_OVERLAY_FILE; + + EXPECT_CALL(invalidEntry, getFileName()) + .WillRepeatedly(Return(invalidFile)); + + sp<AaptAssets> assets = new AaptAssets(); + bool result = assets->isEntryValid(&bundle, NULL); + + EXPECT_FALSE(result); +} + +TEST(ZipReadingTest, TestDirectoryEntryMarkedInvalid) { + Bundle bundle; + bundle.setInternalZipPath(COMPILING_OVERLAY_DIR); + MockZipEntry invalidEntry2; + // Add a "/" signifying this is a dir entry not a file entry. + const char* dir2 = COMPILING_OVERLAY_DIR"/"; + EXPECT_CALL(invalidEntry2, getFileName()) + .WillRepeatedly(Return(dir2)); + + sp<AaptAssets> assets = new AaptAssets(); + bool result2 = assets->isEntryValid(&bundle, &invalidEntry2); + + EXPECT_FALSE(result2); +} diff --git a/tools/aapt/tests/mocks/MockZipEntry.h b/tools/aapt/tests/mocks/MockZipEntry.h new file mode 100644 index 0000000..eef07f9 --- /dev/null +++ b/tools/aapt/tests/mocks/MockZipEntry.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * 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 ANDROID_MOCK_ZIP_ENTRY +#define ANDROID_MOCK_ZIP_ENTRY + +#include "ZipFile.h" +#include "gmock/gmock.h" + +class MockZipEntry : public android::ZipEntry { +public: + MOCK_CONST_METHOD0(getCompressionMethod, int()); + MOCK_CONST_METHOD0(getFileName, const char* ()); +}; + +#endif // ANDROID_MOCK_ZIP_ENTRY diff --git a/tools/aapt/tests/mocks/MockZipFile.h b/tools/aapt/tests/mocks/MockZipFile.h new file mode 100644 index 0000000..0394ce7 --- /dev/null +++ b/tools/aapt/tests/mocks/MockZipFile.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2014 The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * 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 ANDROID_MOCK_ZIP_FILE +#define ANDROID_MOCK_ZIP_FILE + +#include "ZipFile.h" +#include "gmock/gmock.h" + +class MockZipFile : public android::ZipFile { +public: + MOCK_CONST_METHOD0(getNumEntries, int()); + MOCK_CONST_METHOD1(getEntryByIndex, android::ZipEntry* (int idx)); +}; + +#endif // ANDROID_MOCK_ZIP_FILE diff --git a/tools/aidl/aidl.cpp b/tools/aidl/aidl.cpp index 14c9f95..d37a946 100644 --- a/tools/aidl/aidl.cpp +++ b/tools/aidl/aidl.cpp @@ -345,6 +345,13 @@ gather_types(const char* filename, document_item_type* items) name, Type::GENERATED, false, false, false, filename, c->name.lineno); NAMES.Add(proxy); + + name = c->name.data; + name += ".NoOp"; + Type* noOp = new Type(c->package ? c->package : "", + name, Type::GENERATED, false, false, false, + filename, c->name.lineno); + NAMES.Add(noOp); } else if (items->item_type == INTERFACE_TYPE_RPC) { // for interfaces, also add the service base type, we don't @@ -569,12 +576,19 @@ check_types(const char* filename, document_item_type* items) if (methodNames.find(m->name.data) == methodNames.end()) { methodNames[m->name.data] = m; } else { - fprintf(stderr,"%s:%d attempt to redefine method %s,\n", + if (m->hasId) { + fprintf(stderr, "%s:%d redefining 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; + m->deduplicate = true; + 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; @@ -1014,9 +1028,6 @@ compile_aidl(Options& options) 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; @@ -1028,6 +1039,9 @@ compile_aidl(Options& options) ((interface_type*)mainDoc)->interface_items); } + // check the referenced types in mainDoc to make sure we've imported them + err |= check_types(options.inputFileName.c_str(), mainDoc); + // after this, there shouldn't be any more errors because of the // input. if (err != 0 || mainDoc == NULL) { @@ -1057,8 +1071,13 @@ compile_aidl(Options& options) // make sure the folders of the output file all exists check_outputFilePath(options.outputFileName); + int flags = 0; + if (options.generateNoOpMethods) { + flags |= GENERATE_NO_OP_CLASS; + } + err = generate_java(options.outputFileName, options.inputFileName.c_str(), - (interface_type*)mainDoc); + (interface_type*)mainDoc, flags); return err; } diff --git a/tools/aidl/aidl_language.h b/tools/aidl/aidl_language.h index de1370c..f3c850e 100644 --- a/tools/aidl/aidl_language.h +++ b/tools/aidl/aidl_language.h @@ -64,6 +64,7 @@ typedef struct method_type { buffer_type semicolon_token; buffer_type* comments_token; // points into this structure, DO NOT DELETE int assigned_id; + bool deduplicate; } method_type; enum { diff --git a/tools/aidl/generate_java.cpp b/tools/aidl/generate_java.cpp index 9e57407..8817461 100644 --- a/tools/aidl/generate_java.cpp +++ b/tools/aidl/generate_java.cpp @@ -59,12 +59,12 @@ append(const char* a, const char* b) // ================================================= int generate_java(const string& filename, const string& originalSrc, - interface_type* iface) + interface_type* iface, int flags) { Class* cl; if (iface->document_item.item_type == INTERFACE_TYPE_BINDER) { - cl = generate_binder_interface_class(iface); + cl = generate_binder_interface_class(iface, flags); } else if (iface->document_item.item_type == INTERFACE_TYPE_RPC) { cl = generate_rpc_interface_class(iface); diff --git a/tools/aidl/generate_java.h b/tools/aidl/generate_java.h index 4bfcfeb..45b2703 100644 --- a/tools/aidl/generate_java.h +++ b/tools/aidl/generate_java.h @@ -9,9 +9,9 @@ using namespace std; int generate_java(const string& filename, const string& originalSrc, - interface_type* iface); + interface_type* iface, int flags); -Class* generate_binder_interface_class(const interface_type* iface); +Class* generate_binder_interface_class(const interface_type* iface, int flags); Class* generate_rpc_interface_class(const interface_type* iface); string gather_comments(extra_text_type* extra); @@ -29,5 +29,8 @@ private: int m_index; }; +//Set of flags that can be passed to generate_java +#define GENERATE_NO_OP_CLASS 1 << 0 + #endif // GENERATE_JAVA_H diff --git a/tools/aidl/generate_java_binder.cpp b/tools/aidl/generate_java_binder.cpp index f291ceb..48ac027 100644 --- a/tools/aidl/generate_java_binder.cpp +++ b/tools/aidl/generate_java_binder.cpp @@ -194,6 +194,36 @@ ProxyClass::~ProxyClass() } // ================================================= +class DefaultNoOpClass : public Class { +public: + DefaultNoOpClass(Type* type, InterfaceType* interfaceType); + virtual ~DefaultNoOpClass(); +}; + +DefaultNoOpClass::DefaultNoOpClass(Type* type, InterfaceType* interfaceType) + :Class() +{ + this->comment = "/** No-Op implementation */"; + this->comment += "\n/** @hide */"; + this->modifiers = PUBLIC | STATIC; + this->what = Class::CLASS; + this->type = type; + this->interfaces.push_back(interfaceType); + + // 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(NULL_VALUE)); + this->elements.push_back(asBinder); +} + +DefaultNoOpClass::~DefaultNoOpClass() { +} + +// ================================================= static void generate_new_array(Type* t, StatementBlock* addTo, Variable* v, Variable* parcel) @@ -245,10 +275,24 @@ generate_read_from_parcel(Type* t, StatementBlock* addTo, Variable* v, } } +static bool +is_numeric_java_type(const char* str) { + static const char* KEYWORDS[] = { "int", "byte", "char", "float", "double", + "short", "long", NULL }; + const char** k = KEYWORDS; + while (*k) { + if (0 == strcmp(str, *k)) { + return true; + } + k++; + } + return false; +} static void generate_method(const method_type* method, Class* interface, - StubClass* stubClass, ProxyClass* proxyClass, int index) + StubClass* stubClass, ProxyClass* proxyClass, DefaultNoOpClass* noOpClass, + int index) { arg_type* arg; int i; @@ -260,6 +304,12 @@ generate_method(const method_type* method, Class* interface, string transactCodeName = "TRANSACTION_"; transactCodeName += method->name.data; + if (method->deduplicate) { + char tmp[16]; + sprintf(tmp, "_%d", index); + transactCodeName += tmp; + } + char transactCodeValue[60]; sprintf(transactCodeValue, "(android.os.IBinder.FIRST_CALL_TRANSACTION + %d)", index); @@ -288,6 +338,39 @@ generate_method(const method_type* method, Class* interface, interface->elements.push_back(decl); + // == the no-op method =================================================== + if (noOpClass != NULL) { + Method* noOpMethod = new Method; + noOpMethod->comment = gather_comments(method->comments_token->extra); + noOpMethod->modifiers = OVERRIDE | PUBLIC; + noOpMethod->returnType = NAMES.Search(method->type.type.data); + noOpMethod->returnTypeDimension = method->type.dimension; + noOpMethod->name = method->name.data; + noOpMethod->statements = new StatementBlock; + + arg = method->args; + while (arg != NULL) { + noOpMethod->parameters.push_back(new Variable( + NAMES.Search(arg->type.type.data), arg->name.data, + arg->type.dimension)); + arg = arg->next; + } + + if (0 != strcmp(method->type.type.data, "void")) { + bool isNumeric = is_numeric_java_type(method->type.type.data); + bool isBoolean = 0 == strcmp(method->type.type.data, "boolean"); + + if (isNumeric && method->type.dimension == 0) { + noOpMethod->statements->Add(new ReturnStatement(new LiteralExpression("0"))); + } else if (isBoolean && method->type.dimension == 0) { + noOpMethod->statements->Add(new ReturnStatement(FALSE_VALUE)); + } else { + noOpMethod->statements->Add(new ReturnStatement(NULL_VALUE)); + } + } + noOpMethod->exceptions.push_back(REMOTE_EXCEPTION_TYPE); + noOpClass->elements.push_back(noOpMethod); + } // == the stub method ==================================================== Case* c = new Case(transactCodeName); @@ -514,7 +597,7 @@ generate_interface_descriptors(StubClass* stub, ProxyClass* proxy) } Class* -generate_binder_interface_class(const interface_type* iface) +generate_binder_interface_class(const interface_type* iface, int flags) { InterfaceType* interfaceType = static_cast<InterfaceType*>( NAMES.Find(iface->package, iface->name.data)); @@ -527,6 +610,15 @@ generate_binder_interface_class(const interface_type* iface) interface->type = interfaceType; interface->interfaces.push_back(IINTERFACE_TYPE); + // the No-Op inner class + DefaultNoOpClass* noOpClass = NULL; + if ((flags & GENERATE_NO_OP_CLASS) != 0) { + noOpClass = new DefaultNoOpClass( + NAMES.Find(iface->package, append(iface->name.data, ".NoOp").c_str()), + interfaceType); + interface->elements.push_back(noOpClass); + } + // the stub inner class StubClass* stub = new StubClass( NAMES.Find(iface->package, append(iface->name.data, ".Stub").c_str()), @@ -549,7 +641,8 @@ generate_binder_interface_class(const interface_type* iface) 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); + generate_method(method_item, interface, stub, proxy, noOpClass, + method_item->assigned_id); } item = item->next; index++; diff --git a/tools/aidl/options.cpp b/tools/aidl/options.cpp index 7b2daeb..9de1957 100644 --- a/tools/aidl/options.cpp +++ b/tools/aidl/options.cpp @@ -51,6 +51,7 @@ parse_options(int argc, const char* const* argv, Options *options) options->task = COMPILE_AIDL; options->failOnParcelable = false; options->autoDepFile = false; + options->generateNoOpMethods = false; // OPTIONS while (i < argc) { @@ -97,6 +98,9 @@ parse_options(int argc, const char* const* argv, Options *options) else if (len == 2 && s[1] == 'b') { options->failOnParcelable = true; } + else if (s[1] == 'n') { + options->generateNoOpMethods = true; + } else { // s[1] is not known fprintf(stderr, "unknown option (%d): %s\n", i, s); diff --git a/tools/aidl/options.h b/tools/aidl/options.h index 387e37d..969cc1c 100644 --- a/tools/aidl/options.h +++ b/tools/aidl/options.h @@ -24,6 +24,7 @@ struct Options string outputBaseFolder; string depFileName; bool autoDepFile; + bool generateNoOpMethods; vector<string> filesToPreprocess; }; diff --git a/tools/layoutlib/.idea/encodings.xml b/tools/layoutlib/.idea/encodings.xml index e206d70..f758959 100644 --- a/tools/layoutlib/.idea/encodings.xml +++ b/tools/layoutlib/.idea/encodings.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <project version="4"> - <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" /> -</project> - + <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false"> + <file url="PROJECT" charset="UTF-8" /> + </component> +</project>
\ No newline at end of file diff --git a/tools/layoutlib/bridge/src/android/animation/AnimatorInflater_Delegate.java b/tools/layoutlib/bridge/src/android/animation/AnimatorInflater_Delegate.java deleted file mode 100644 index 4475fa4..0000000 --- a/tools/layoutlib/bridge/src/android/animation/AnimatorInflater_Delegate.java +++ /dev/null @@ -1,59 +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.animation; - -import com.android.tools.layoutlib.annotations.LayoutlibDelegate; - -import android.content.Context; -import android.content.res.Resources; -import android.content.res.Resources.NotFoundException; -import android.content.res.Resources.Theme; -import android.util.AttributeSet; - -/** - * Delegate providing alternate implementation to static methods in {@link AnimatorInflater}. - */ -public class AnimatorInflater_Delegate { - - @LayoutlibDelegate - /*package*/ static Animator loadAnimator(Context context, int id) - throws NotFoundException { - return loadAnimator(context.getResources(), context.getTheme(), id); - } - - @LayoutlibDelegate - /*package*/ static Animator loadAnimator(Resources resources, Theme theme, int id) - throws NotFoundException { - return loadAnimator(resources, theme, id, 1); - } - - @LayoutlibDelegate - /*package*/ static Animator loadAnimator(Resources resources, Theme theme, int id, - float pathErrorScale) throws NotFoundException { - // This is a temporary fix to http://b.android.com/77865. This skips loading the - // animation altogether. - // TODO: Remove this override when Path.approximate() is supported. - return new FakeAnimator(); - } - - @LayoutlibDelegate - /*package*/ static ValueAnimator loadAnimator(Resources res, Theme theme, - AttributeSet attrs, ValueAnimator anim, float pathErrorScale) - throws NotFoundException { - return AnimatorInflater.loadAnimator_Original(res, theme, attrs, anim, pathErrorScale); - } -} diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java b/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java index 163fbcb..0e39243 100644 --- a/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java +++ b/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java @@ -18,6 +18,7 @@ package android.content.res; import com.android.SdkConstants; 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.ResourceValue; @@ -48,9 +49,6 @@ import java.io.FileNotFoundException; import java.io.InputStream; import java.util.Iterator; -/** - * - */ public final class BridgeResources extends Resources { private BridgeContext mContext; @@ -278,7 +276,7 @@ public final class BridgeResources extends Resources { * always Strings. The ideal signature for the method should be <T super String>, but java * generics don't support it. */ - private <T extends CharSequence> T[] fillValues(ArrayResourceValue resValue, T[] values) { + <T extends CharSequence> T[] fillValues(ArrayResourceValue resValue, T[] values) { int i = 0; for (Iterator<String> iterator = resValue.iterator(); iterator.hasNext(); i++) { @SuppressWarnings("unchecked") @@ -404,7 +402,7 @@ public final class BridgeResources extends Resources { if (xml.isFile()) { // we need to create a pull parser around the layout XML file, and then // give that to our XmlBlockParser - parser = ParserFactory.create(xml); + parser = ParserFactory.create(xml, true); } } @@ -664,13 +662,18 @@ public final class BridgeResources extends Resources { Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag); if (value != null) { - String v = value.getSecond().getValue(); + ResourceValue resVal = value.getSecond(); + String v = resVal.getValue(); if (v != null) { if (ResourceHelper.parseFloatAttribute(value.getFirst(), v, outValue, false /*requireUnit*/)) { return; } + if (resVal instanceof DensityBasedResourceValue) { + outValue.density = + ((DensityBasedResourceValue) resVal).getResourceDensity().getDpiValue(); + } // else it's a string outValue.type = TypedValue.TYPE_STRING; diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java index 6a61090..31dd3d9 100644 --- a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java +++ b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java @@ -16,6 +16,7 @@ package android.content.res; +import com.android.ide.common.rendering.api.ArrayResourceValue; import com.android.ide.common.rendering.api.AttrResourceValue; import com.android.ide.common.rendering.api.LayoutLog; import com.android.ide.common.rendering.api.RenderResources; @@ -33,6 +34,7 @@ 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; import android.graphics.drawable.Drawable; import android.util.DisplayMetrics; @@ -740,12 +742,20 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public CharSequence[] getTextArray(int index) { - String value = getString(index); - if (value != null) { - return new CharSequence[] { value }; + if (!hasValue(index)) { + return null; } - - return null; + ResourceValue resVal = mResourceData[index]; + if (resVal instanceof ArrayResourceValue) { + ArrayResourceValue array = (ArrayResourceValue) resVal; + int count = array.getElementCount(); + return count >= 0 ? mBridgeResources.fillValues(array, new CharSequence[count]) : null; + } + int id = getResourceId(index, 0); + String resIdMessage = id > 0 ? " (resource id 0x" + Integer.toHexString(id) + ')' : ""; + throw new NotFoundException( + String.format("%1$s in %2$s%3$s is not a valid array resource.", + resVal.getValue(), mNames[index], resIdMessage)); } @Override diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java index d858953..60514b6 100644 --- a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java @@ -59,6 +59,7 @@ import java.util.Set; if (opts.inPremultiplied) { bitmapCreateFlags.add(BitmapCreateFlags.PREMULTIPLIED); } + opts.inScaled = false; } try { diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java index f8b3739..64cd503 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java @@ -35,6 +35,8 @@ import java.awt.RenderingHints; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Arc2D; +import java.awt.geom.Path2D; +import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; @@ -707,6 +709,12 @@ public final class Canvas_Delegate { @Override public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) { Shape shape = pathDelegate.getJavaShape(); + Rectangle2D bounds = shape.getBounds2D(); + if (bounds.isEmpty()) { + // Apple JRE 1.6 doesn't like drawing empty shapes. + // http://b.android.com/178278 + return; + } int style = paintDelegate.getStyle(); if (style == Paint.Style.FILL.nativeInt || diff --git a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java index 857e6d0..c7b24bc 100644 --- a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java @@ -178,7 +178,9 @@ public class FontFamily_Delegate { desiredStyle.mIsItalic = isItalic; FontInfo bestFont = null; int bestMatch = Integer.MAX_VALUE; - for (FontInfo font : mFonts) { + //noinspection ForLoopReplaceableByForEach (avoid iterator instantiation) + for (int i = 0, n = mFonts.size(); i < n; i++) { + FontInfo font = mFonts.get(i); int match = computeMatch(font, desiredStyle); if (match < bestMatch) { bestMatch = match; @@ -415,7 +417,9 @@ public class FontFamily_Delegate { boolean isItalic = fontInfo.mIsItalic; // The list is usually just two fonts big. So iterating over all isn't as bad as it looks. // It's biggest for roboto where the size is 12. - for (FontInfo font : mFonts) { + //noinspection ForLoopReplaceableByForEach (avoid iterator instantiation) + for (int i = 0, n = mFonts.size(); i < n; i++) { + FontInfo font = mFonts.get(i); if (font.mWeight == weight && font.mIsItalic == isItalic) { return false; } diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java index 65b65ec..a545283 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java @@ -480,8 +480,10 @@ public class Paint_Delegate { return; } - delegate.mTextSize = textSize; - delegate.updateFontObject(); + if (delegate.mTextSize != textSize) { + delegate.mTextSize = textSize; + delegate.updateFontObject(); + } } @LayoutlibDelegate @@ -503,8 +505,10 @@ public class Paint_Delegate { return; } - delegate.mTextScaleX = scaleX; - delegate.updateFontObject(); + if (delegate.mTextScaleX != scaleX) { + delegate.mTextScaleX = scaleX; + delegate.updateFontObject(); + } } @LayoutlibDelegate @@ -526,8 +530,10 @@ public class Paint_Delegate { return; } - delegate.mTextSkewX = skewX; - delegate.updateFontObject(); + if (delegate.mTextSkewX != skewX) { + delegate.mTextSkewX = skewX; + delegate.updateFontObject(); + } } @LayoutlibDelegate @@ -897,9 +903,12 @@ public class Paint_Delegate { return 0; } - delegate.mTypeface = Typeface_Delegate.getDelegate(typeface); - delegate.mNativeTypeface = typeface; - delegate.updateFontObject(); + Typeface_Delegate typefaceDelegate = Typeface_Delegate.getDelegate(typeface); + if (delegate.mTypeface != typefaceDelegate || delegate.mNativeTypeface != typeface) { + delegate.mTypeface = Typeface_Delegate.getDelegate(typeface); + delegate.mNativeTypeface = typeface; + delegate.updateFontObject(); + } return typeface; } @@ -1214,13 +1223,31 @@ public class Paint_Delegate { mCap = paint.mCap; mJoin = paint.mJoin; mTextAlign = paint.mTextAlign; - mTypeface = paint.mTypeface; - mNativeTypeface = paint.mNativeTypeface; + + boolean needsFontUpdate = false; + if (mTypeface != paint.mTypeface || mNativeTypeface != paint.mNativeTypeface) { + mTypeface = paint.mTypeface; + mNativeTypeface = paint.mNativeTypeface; + needsFontUpdate = true; + } + + if (mTextSize != paint.mTextSize) { + mTextSize = paint.mTextSize; + needsFontUpdate = true; + } + + if (mTextScaleX != paint.mTextScaleX) { + mTextScaleX = paint.mTextScaleX; + needsFontUpdate = true; + } + + if (mTextSkewX != paint.mTextSkewX) { + mTextSkewX = paint.mTextSkewX; + needsFontUpdate = true; + } + mStrokeWidth = paint.mStrokeWidth; mStrokeMiter = paint.mStrokeMiter; - mTextSize = paint.mTextSize; - mTextScaleX = paint.mTextScaleX; - mTextSkewX = paint.mTextSkewX; mXfermode = paint.mXfermode; mColorFilter = paint.mColorFilter; mShader = paint.mShader; @@ -1228,7 +1255,10 @@ public class Paint_Delegate { mMaskFilter = paint.mMaskFilter; mRasterizer = paint.mRasterizer; mHintingMode = paint.mHintingMode; - updateFontObject(); + + if (needsFontUpdate) { + updateFontObject(); + } } private void reset() { @@ -1264,10 +1294,18 @@ public class Paint_Delegate { // Get the fonts from the TypeFace object. List<Font> fonts = mTypeface.getFonts(mFontVariant); + if (fonts.isEmpty()) { + mFonts = Collections.emptyList(); + return; + } + // create new font objects as well as FontMetrics, based on the current text size // and skew info. - ArrayList<FontInfo> infoList = new ArrayList<FontInfo>(fonts.size()); - for (Font font : fonts) { + int nFonts = fonts.size(); + ArrayList<FontInfo> infoList = new ArrayList<FontInfo>(nFonts); + //noinspection ForLoopReplaceableByForEach (avoid iterator instantiation) + for (int i = 0; i < nFonts; i++) { + Font font = fonts.get(i); if (font == null) { // If the font is null, add null to infoList. When rendering the text, if this // null is reached, a warning will be logged. diff --git a/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java new file mode 100644 index 0000000..dd2978f --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java @@ -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. + */ + +package android.graphics; + +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.awt.geom.PathIterator; +import java.awt.geom.Point2D; + +/** + * Delegate implementing the native methods of {@link android.graphics.PathMeasure} + * <p/> + * Through the layoutlib_create tool, the original native methods of PathMeasure have been + * replaced by + * calls to methods of the same name in this delegate class. + * <p/> + * 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 PathMeasure class. + * + * @see DelegateManager + */ +public final class PathMeasure_Delegate { + // ---- delegate manager ---- + private static final DelegateManager<PathMeasure_Delegate> sManager = + new DelegateManager<PathMeasure_Delegate>(PathMeasure_Delegate.class); + + // ---- delegate data ---- + // This governs how accurate the approximation of the Path is. + private static final float PRECISION = 0.002f; + + /** + * Array containing the path points components. There are three components for each point: + * <ul> + * <li>Fraction along the length of the path that the point resides</li> + * <li>The x coordinate of the point</li> + * <li>The y coordinate of the point</li> + * </ul> + */ + private float mPathPoints[]; + private long mNativePath; + + private PathMeasure_Delegate(long native_path, boolean forceClosed) { + mNativePath = native_path; + if (forceClosed && mNativePath != 0) { + // Copy the path and call close + mNativePath = Path_Delegate.init2(native_path); + Path_Delegate.native_close(mNativePath); + } + + mPathPoints = + mNativePath != 0 ? Path_Delegate.native_approximate(mNativePath, PRECISION) : null; + } + + @LayoutlibDelegate + /*package*/ static long native_create(long native_path, boolean forceClosed) { + return sManager.addNewDelegate(new PathMeasure_Delegate(native_path, forceClosed)); + } + + @LayoutlibDelegate + /*package*/ static void native_destroy(long native_instance) { + sManager.removeJavaReferenceFor(native_instance); + } + + @LayoutlibDelegate + /*package*/ static boolean native_getPosTan(long native_instance, float distance, float pos[], + float tan[]) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "PathMeasure.getPostTan is not supported.", null, null); + return false; + } + + @LayoutlibDelegate + /*package*/ static boolean native_getMatrix(long native_instance, float distance, long + native_matrix, int flags) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "PathMeasure.getMatrix is not supported.", null, null); + return false; + } + + @LayoutlibDelegate + /*package*/ static boolean native_nextContour(long native_instance) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "PathMeasure.nextContour is not supported.", null, null); + return false; + } + + @LayoutlibDelegate + /*package*/ static void native_setPath(long native_instance, long native_path, boolean + forceClosed) { + PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance); + assert pathMeasure != null; + + if (forceClosed && native_path != 0) { + // Copy the path and call close + native_path = Path_Delegate.init2(native_path); + Path_Delegate.native_close(native_path); + } + pathMeasure.mNativePath = native_path; + pathMeasure.mPathPoints = Path_Delegate.native_approximate(native_path, PRECISION); + } + + @LayoutlibDelegate + /*package*/ static float native_getLength(long native_instance) { + PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance); + assert pathMeasure != null; + + if (pathMeasure.mPathPoints == null) { + return 0; + } + + float length = 0; + int nPoints = pathMeasure.mPathPoints.length / 3; + for (int i = 1; i < nPoints; i++) { + length += Point2D.distance( + pathMeasure.mPathPoints[(i - 1) * 3 + 1], + pathMeasure.mPathPoints[(i - 1) * 3 + 2], + pathMeasure.mPathPoints[i*3 + 1], + pathMeasure.mPathPoints[i*3 + 2]); + } + + return length; + } + + @LayoutlibDelegate + /*package*/ static boolean native_isClosed(long native_instance) { + PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance); + assert pathMeasure != null; + + Path_Delegate path = Path_Delegate.getDelegate(pathMeasure.mNativePath); + if (path == null) { + return false; + } + + PathIterator pathIterator = path.getJavaShape().getPathIterator(null); + + int type = 0; + float segment[] = new float[6]; + while (!pathIterator.isDone()) { + type = pathIterator.currentSegment(segment); + pathIterator.next(); + } + + // A path is a closed path if the last element is SEG_CLOSE + return type == PathIterator.SEG_CLOSE; + } + + @LayoutlibDelegate + /*package*/ static boolean native_getSegment(long native_instance, float startD, float stopD, + long native_dst_path, boolean startWithMoveTo) { + if (startD < 0) { + startD = 0; + } + + if (startD >= stopD) { + return false; + } + + PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance); + assert pathMeasure != null; + + if (pathMeasure.mPathPoints == null) { + return false; + } + + float accLength = 0; + boolean isZeroLength = true; // Whether the output has zero length or not + int nPoints = pathMeasure.mPathPoints.length / 3; + for (int i = 0; i < nPoints; i++) { + float x = pathMeasure.mPathPoints[i * 3 + 1]; + float y = pathMeasure.mPathPoints[i * 3 + 2]; + if (accLength >= startD && accLength <= stopD) { + if (startWithMoveTo) { + startWithMoveTo = false; + Path_Delegate.native_moveTo(native_dst_path, x, y); + } else { + isZeroLength = false; + Path_Delegate.native_lineTo(native_dst_path, x, y); + } + } + + if (i > 0) { + accLength += Point2D.distance( + pathMeasure.mPathPoints[(i - 1) * 3 + 1], + pathMeasure.mPathPoints[(i - 1) * 3 + 2], + pathMeasure.mPathPoints[i * 3 + 1], + pathMeasure.mPathPoints[i * 3 + 2]); + } + } + + return !isZeroLength; + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java index 3c9a062..a2a53fe 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java @@ -36,6 +36,7 @@ import java.awt.geom.PathIterator; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.geom.RoundRectangle2D; +import java.util.ArrayList; /** * Delegate implementing the native methods of android.graphics.Path @@ -173,11 +174,8 @@ public final class Path_Delegate { @LayoutlibDelegate /*package*/ static boolean native_isEmpty(long nPath) { Path_Delegate pathDelegate = sManager.getDelegate(nPath); - if (pathDelegate == null) { - return true; - } + return pathDelegate == null || pathDelegate.isEmpty(); - return pathDelegate.isEmpty(); } @LayoutlibDelegate @@ -488,54 +486,44 @@ public final class Path_Delegate { @LayoutlibDelegate /*package*/ static float[] native_approximate(long nPath, float error) { - Bridge.getLog().warning(LayoutLog.TAG_UNSUPPORTED, "Path.approximate() not fully supported", - null); Path_Delegate pathDelegate = sManager.getDelegate(nPath); if (pathDelegate == null) { return null; } - PathIterator pathIterator = pathDelegate.mPath.getPathIterator(null); - float[] tmp = new float[6]; - float[] coords = new float[6]; - boolean isFirstPoint = true; - while (!pathIterator.isDone()) { - int type = pathIterator.currentSegment(tmp); - switch (type) { - case PathIterator.SEG_MOVETO: - case PathIterator.SEG_LINETO: - store(tmp, coords, 1, isFirstPoint); - break; - case PathIterator.SEG_QUADTO: - store(tmp, coords, 2, isFirstPoint); - break; - case PathIterator.SEG_CUBICTO: - store(tmp, coords, 3, isFirstPoint); - break; - case PathIterator.SEG_CLOSE: - // No points returned. + // Get a FlatteningIterator + PathIterator iterator = pathDelegate.getJavaShape().getPathIterator(null, error); + + float segment[] = new float[6]; + float totalLength = 0; + ArrayList<Point2D.Float> points = new ArrayList<Point2D.Float>(); + Point2D.Float previousPoint = null; + while (!iterator.isDone()) { + int type = iterator.currentSegment(segment); + Point2D.Float currentPoint = new Point2D.Float(segment[0], segment[1]); + // MoveTo shouldn't affect the length + if (previousPoint != null && type != PathIterator.SEG_MOVETO) { + totalLength += currentPoint.distance(previousPoint); } - isFirstPoint = false; - pathIterator.next(); + previousPoint = currentPoint; + points.add(currentPoint); + iterator.next(); } - if (isFirstPoint) { - // No points found - return new float[0]; - } else { - return coords; - } - } - private static void store(float[] src, float[] dst, int count, boolean isFirst) { - if (isFirst) { - dst[0] = 0; // fraction - dst[1] = src[0]; // abscissa - dst[2] = src[1]; // ordinate - } - if (count > 1 || !isFirst) { - dst[3] = 1; - dst[4] = src[2 * count - 2]; - dst[5] = src[2 * count - 1]; + int nPoints = points.size(); + float[] result = new float[nPoints * 3]; + previousPoint = null; + for (int i = 0; i < nPoints; i++) { + Point2D.Float point = points.get(i); + float distance = previousPoint != null ? (float) previousPoint.distance(point) : .0f; + result[i * 3] = distance / totalLength; + result[i * 3 + 1] = point.x; + result[i * 3 + 2] = point.y; + + totalLength += distance; + previousPoint = point; } + + return result; } // ---- Private helper methods ---- @@ -735,6 +723,9 @@ public final class Path_Delegate { */ private void cubicTo(float x1, float y1, float x2, float y2, float x3, float y3) { + if (isEmpty()) { + mPath.moveTo(0, 0); + } mPath.curveTo(x1, y1, x2, y2, mLastX = x3, mLastY = y3); } diff --git a/tools/layoutlib/bridge/src/android/graphics/drawable/GradientDrawable_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/drawable/GradientDrawable_Delegate.java new file mode 100644 index 0000000..a3ad2aa --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/drawable/GradientDrawable_Delegate.java @@ -0,0 +1,73 @@ +/* + * 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.graphics.drawable; + +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.graphics.Path; +import android.graphics.drawable.GradientDrawable.GradientState; + +import java.lang.reflect.Field; + +/** + * Delegate implementing the native methods of {@link GradientDrawable} + * + * Through the layoutlib_create tool, the original native methods of GradientDrawable have been + * replaced by calls to methods of the same name in this delegate class. + */ +public class GradientDrawable_Delegate { + + /** + * The ring can be built either by drawing full circles, or by drawing arcs in case the + * circle isn't complete. LayoutLib cannot handle drawing full circles (requires path + * subtraction). So, if we need to draw full circles, we switch to drawing 99% circle. + */ + @LayoutlibDelegate + /*package*/ static Path buildRing(GradientDrawable thisDrawable, GradientState st) { + boolean useLevel = st.mUseLevelForShape; + int level = thisDrawable.getLevel(); + // 10000 is the max level. See android.graphics.drawable.Drawable#getLevel() + float sweep = useLevel ? (360.0f * level / 10000.0f) : 360f; + Field mLevel = null; + if (sweep >= 360 || sweep <= -360) { + st.mUseLevelForShape = true; + // Use reflection to set the value of the field to prevent setting the drawable to + // dirty again. + try { + mLevel = Drawable.class.getDeclaredField("mLevel"); + mLevel.setAccessible(true); + mLevel.setInt(thisDrawable, 9999); // set to one less than max. + } catch (NoSuchFieldException e) { + // The field has been removed in a recent framework change. Fall back to old + // buggy behaviour. + } catch (IllegalAccessException e) { + // We've already set the field to be accessible. + assert false; + } + } + Path path = thisDrawable.buildRing_Original(st); + st.mUseLevelForShape = useLevel; + if (mLevel != null) { + try { + mLevel.setInt(thisDrawable, level); + } catch (IllegalAccessException e) { + assert false; + } + } + return path; + } +} diff --git a/tools/layoutlib/bridge/src/android/preference/Preference_Delegate.java b/tools/layoutlib/bridge/src/android/preference/Preference_Delegate.java index 49ee642..2e44a77 100644 --- a/tools/layoutlib/bridge/src/android/preference/Preference_Delegate.java +++ b/tools/layoutlib/bridge/src/android/preference/Preference_Delegate.java @@ -29,9 +29,6 @@ import android.view.View; import android.view.ViewGroup; import android.widget.ListView; -import java.util.HashMap; -import java.util.Map; - /** * Delegate that provides implementation for native methods in {@link Preference} * <p/> @@ -59,9 +56,9 @@ public class Preference_Delegate { */ public static View inflatePreference(Context context, XmlPullParser parser, ViewGroup root) { PreferenceManager pm = new PreferenceManager(context); - PreferenceScreen ps = pm.getPreferenceScreen(); PreferenceInflater inflater = new BridgePreferenceInflater(context, pm); - ps = (PreferenceScreen) inflater.inflate(parser, ps, true); + PreferenceScreen ps = (PreferenceScreen) inflater.inflate(parser, null, true); + pm.setPreferences(ps); ListView preferenceView = createContainerView(context, root); ps.bind(preferenceView); return preferenceView; diff --git a/tools/layoutlib/bridge/src/android/text/Hyphenator_Delegate.java b/tools/layoutlib/bridge/src/android/text/Hyphenator_Delegate.java index 5a59597..44ce731 100644 --- a/tools/layoutlib/bridge/src/android/text/Hyphenator_Delegate.java +++ b/tools/layoutlib/bridge/src/android/text/Hyphenator_Delegate.java @@ -20,9 +20,10 @@ import com.android.layoutlib.bridge.impl.DelegateManager; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; import java.io.File; +import java.nio.ByteBuffer; /** - * Delegate that overrides implementation for certain methods in {@link android.text.StaticLayout} + * 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 * by calls to methods of the same name in this delegate class. @@ -38,7 +39,7 @@ public class Hyphenator_Delegate { return null; } - /*package*/ static long loadHyphenator(String patternData) { + /*package*/ static long loadHyphenator(ByteBuffer buf, int offset) { return sDelegateManager.addNewDelegate(new Hyphenator_Delegate()); } } diff --git a/tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java b/tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java index 1b0ba51..65c0a07 100644 --- a/tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java +++ b/tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java @@ -13,6 +13,7 @@ import android.icu.util.ULocale; import android.text.Primitive.PrimitiveType; import android.text.StaticLayout.LineBreaks; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -52,8 +53,8 @@ public class StaticLayout_Delegate { } @LayoutlibDelegate - /*package*/ static long nLoadHyphenator(String patternData) { - return Hyphenator_Delegate.loadHyphenator(patternData); + /*package*/ static long nLoadHyphenator(ByteBuffer buf, int offset) { + return Hyphenator_Delegate.loadHyphenator(buf, offset); } @LayoutlibDelegate diff --git a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java index 1e33e3a..723e827 100644 --- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java +++ b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java @@ -23,6 +23,7 @@ import com.android.ide.common.rendering.api.ResourceReference; import com.android.ide.common.rendering.api.ResourceValue; import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.BridgeConstants; +import com.android.layoutlib.bridge.MockView; import com.android.layoutlib.bridge.android.BridgeContext; import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; import com.android.layoutlib.bridge.android.support.DrawerLayoutUtil; @@ -36,6 +37,7 @@ import org.xmlpull.v1.XmlPullParser; import android.annotation.NonNull; import android.content.Context; +import android.content.res.TypedArray; import android.util.AttributeSet; import java.io.File; @@ -54,6 +56,9 @@ public final class BridgeInflater extends LayoutInflater { private ResourceReference mResourceReference; private Map<View, String> mOpenDrawerLayouts; + // Keep in sync with the same value in LayoutInflater. + private static final int[] ATTRS_THEME = new int[] {com.android.internal.R.attr.theme }; + /** * List of class prefixes which are tried first by default. * <p/> @@ -122,6 +127,9 @@ public final class BridgeInflater extends LayoutInflater { if (view == null) { view = loadCustomView(name, attrs); } + } catch (InflateException e) { + // Don't catch the InflateException below as that results in hiding the real cause. + throw e; } catch (Exception e) { // Wrap the real exception in a ClassNotFoundException, so that the calling method // can deal with it. @@ -135,24 +143,45 @@ public final class BridgeInflater extends LayoutInflater { @Override public View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, - boolean ignoreThemeAttrs) { + boolean ignoreThemeAttr) { View view; try { - view = super.createViewFromTag(parent, name, context, attrs, ignoreThemeAttrs); + view = super.createViewFromTag(parent, name, context, attrs, ignoreThemeAttr); } catch (InflateException e) { - // try to load the class from using the custom view loader - try { - view = loadCustomView(name, attrs); - } catch (Exception e2) { - // Wrap the real exception in an InflateException so that the calling - // method can deal with it. - InflateException exception = new InflateException(); - if (!e2.getClass().equals(ClassNotFoundException.class)) { - exception.initCause(e2); - } else { - exception.initCause(e); + // Creation of ContextThemeWrapper code is same as in the super method. + // Apply a theme wrapper, if allowed and one is specified. + if (!ignoreThemeAttr) { + final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME); + final int themeResId = ta.getResourceId(0, 0); + if (themeResId != 0) { + context = new ContextThemeWrapper(context, themeResId); + } + ta.recycle(); + } + if (!(e.getCause() instanceof ClassNotFoundException)) { + // There is some unknown inflation exception in inflating a View that was found. + view = new MockView(context, attrs); + ((MockView) view).setText(name); + Bridge.getLog().error(LayoutLog.TAG_BROKEN, e.getMessage(), e, null); + } else { + final Object lastContext = mConstructorArgs[0]; + mConstructorArgs[0] = context; + // try to load the class from using the custom view loader + try { + view = loadCustomView(name, attrs); + } catch (Exception e2) { + // Wrap the real exception in an InflateException so that the calling + // method can deal with it. + InflateException exception = new InflateException(); + if (!e2.getClass().equals(ClassNotFoundException.class)) { + exception.initCause(e2); + } else { + exception.initCause(e); + } + throw exception; + } finally { + mConstructorArgs[0] = lastContext; } - throw exception; } } @@ -188,7 +217,7 @@ public final class BridgeInflater extends LayoutInflater { File f = new File(value.getValue()); if (f.isFile()) { try { - XmlPullParser parser = ParserFactory.create(f); + XmlPullParser parser = ParserFactory.create(f, true); BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser( parser, bridgeContext, value.isFramework()); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java index 44a9aad..d392f21 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java @@ -17,39 +17,90 @@ package com.android.layoutlib.bridge; import android.content.Context; -import android.graphics.Canvas; import android.util.AttributeSet; import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; import android.widget.TextView; /** * Base class for mocked views. - * - * TODO: implement onDraw and draw a rectangle in a random color with the name of the class - * (or better the id of the view). + * <p/> + * FrameLayout with a single TextView. Doesn't allow adding any other views to itself. */ -public class MockView extends TextView { +public class MockView extends FrameLayout { + + private final TextView mView; + + public MockView(Context context) { + this(context, null); + } public MockView(Context context, AttributeSet attrs) { this(context, attrs, 0); } - public MockView(Context context, AttributeSet attrs, int defStyle) { - this(context, attrs, defStyle, 0); + public MockView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); } public MockView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - - setText(this.getClass().getSimpleName()); - setTextColor(0xFF000000); + mView = new TextView(context, attrs); + mView.setTextColor(0xFF000000); setGravity(Gravity.CENTER); + setText(getClass().getSimpleName()); + addView(mView); + setBackgroundColor(0xFF7F7F7F); + } + + // Only allow adding one TextView. + @Override + public void addView(View child) { + if (child == mView) { + super.addView(child); + } + } + + @Override + public void addView(View child, int index) { + if (child == mView) { + super.addView(child, index); + } } @Override - public void onDraw(Canvas canvas) { - canvas.drawARGB(0xFF, 0x7F, 0x7F, 0x7F); + public void addView(View child, int width, int height) { + if (child == mView) { + super.addView(child, width, height); + } + } + + @Override + public void addView(View child, ViewGroup.LayoutParams params) { + if (child == mView) { + super.addView(child, params); + } + } + + @Override + public void addView(View child, int index, ViewGroup.LayoutParams params) { + if (child == mView) { + super.addView(child, index, params); + } + } + + // The following methods are called by the IDE via reflection, and should be considered part + // of the API. + // Historically, MockView used to be a textView and had these methods. Now, we simply delegate + // them to the contained textView. + + public void setText(CharSequence text) { + mView.setText(text); + } - super.onDraw(canvas); + public void setGravity(int gravity) { + mView.setGravity(gravity); } } 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 689e359..2b83675 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 @@ -409,7 +409,7 @@ public final class BridgeContext extends Context { pushParser(blockParser); return Pair.of( mBridgeInflater.inflate(blockParser, parent, attachToRoot), - true); + Boolean.TRUE); } finally { popParser(); } @@ -436,7 +436,7 @@ public final class BridgeContext extends Context { // we need to create a pull parser around the layout XML file, and then // give that to our XmlBlockParser try { - XmlPullParser parser = ParserFactory.create(xml); + XmlPullParser parser = ParserFactory.create(xml, true); // set the resource ref to have correct view cookies mBridgeInflater.setResourceReference(resource); @@ -447,7 +447,7 @@ public final class BridgeContext extends Context { pushParser(blockParser); return Pair.of( mBridgeInflater.inflate(blockParser, parent, attachToRoot), - false); + Boolean.FALSE); } finally { popParser(); } @@ -470,7 +470,7 @@ public final class BridgeContext extends Context { resource.getName()), null); } - return Pair.of(null, false); + return Pair.of(null, Boolean.FALSE); } @SuppressWarnings("deprecation") @@ -497,6 +497,11 @@ public final class BridgeContext extends Context { } @Override + public void recreateTheme() { + throw new UnsupportedOperationException("recreateTheme is unsupported"); + } + + @Override public ClassLoader getClassLoader() { // The documentation for this method states that it should return a class loader one can // use to retrieve classes in this package. However, when called by LayoutInflater, we do 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 895f9c9..947f99c 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 @@ -155,4 +155,20 @@ public class BridgePowerManager implements IPowerManager { public boolean isScreenBrightnessBoosted() throws RemoteException { return false; } + + @Override + public void setKeyboardVisibility(boolean visible) { + // pass for now. + } + + @Override + public void cpuBoost(int duration) throws RemoteException { + // pass for now + } + + @Override + public void setKeyboardLight(boolean on, int key) { + // pass for now + } + } 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 7e5ae8d..b23d87d 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 @@ -45,6 +45,7 @@ public class WindowManagerImpl implements WindowManager { @Override public void addView(View arg0, android.view.ViewGroup.LayoutParams arg1) { + android.util.SeempLog.record_vg(383, arg1); // pass } @@ -55,6 +56,7 @@ public class WindowManagerImpl implements WindowManager { @Override public void updateViewLayout(View arg0, android.view.ViewGroup.LayoutParams arg1) { + android.util.SeempLog.record_vg(384, arg1); // pass } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java index 868c6d3..cdcf0ea 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java @@ -16,10 +16,13 @@ package com.android.layoutlib.bridge.bars; +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.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.impl.ResourceHelper; import com.android.resources.ResourceType; @@ -45,6 +48,8 @@ public class AppCompatActionBar extends BridgeActionBar { private Object mWindowDecorActionBar; private static final String WINDOW_ACTION_BAR_CLASS = "android.support.v7.internal.app.WindowDecorActionBar"; + // This is used on v23.1.1 and later. + private static final String WINDOW_ACTION_BAR_CLASS_NEW = "android.support.v7.app.WindowDecorActionBar"; private Class<?> mWindowActionBarClass; /** @@ -70,14 +75,25 @@ public class AppCompatActionBar extends BridgeActionBar { try { Class[] constructorParams = {View.class}; Object[] constructorArgs = {getDecorContent()}; - mWindowDecorActionBar = params.getLayoutlibCallback().loadView(WINDOW_ACTION_BAR_CLASS, - constructorParams, constructorArgs); + LayoutlibCallback callback = params.getLayoutlibCallback(); + + // Check if the old action bar class is present. + String actionBarClass = WINDOW_ACTION_BAR_CLASS; + try { + callback.findClass(actionBarClass); + } catch (ClassNotFoundException expected) { + // Failed to find the old class, use the newer one. + actionBarClass = WINDOW_ACTION_BAR_CLASS_NEW; + } + mWindowDecorActionBar = callback.loadView(actionBarClass, + constructorParams, constructorArgs); mWindowActionBarClass = mWindowDecorActionBar == null ? null : mWindowDecorActionBar.getClass(); setupActionBar(); } catch (Exception e) { - e.printStackTrace(); + Bridge.getLog().warning(LayoutLog.TAG_BROKEN, + "Failed to load AppCompat ActionBar with unknown error.", e); } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java index b76ec17..a6e5fb8 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java @@ -116,11 +116,11 @@ abstract class CustomBar extends LinearLayout { density = iconLoader.getDensity(); String path = iconLoader.getPath(); // look for a cached bitmap - Bitmap bitmap = Bridge.getCachedBitmap(path, true /*isFramework*/); + Bitmap bitmap = Bridge.getCachedBitmap(path, Boolean.TRUE /*isFramework*/); if (bitmap == null) { try { bitmap = Bitmap_Delegate.createBitmap(stream, false /*isMutable*/, density); - Bridge.setCachedBitmap(path, bitmap, true /*isFramework*/); + Bridge.setCachedBitmap(path, bitmap, Boolean.TRUE /*isFramework*/); } catch (IOException e) { return; } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutParserWrapper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutParserWrapper.java new file mode 100644 index 0000000..71e7fd2 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutParserWrapper.java @@ -0,0 +1,377 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.layoutlib.bridge.impl; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import android.annotation.Nullable; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * A wrapper around XmlPullParser that can peek forward to inspect if the file is a data-binding + * layout and some parts need to be stripped. + */ +public class LayoutParserWrapper implements XmlPullParser { + + // Data binding constants. + private static final String TAG_LAYOUT = "layout"; + private static final String TAG_DATA = "data"; + private static final String DEFAULT = "default="; + + private final XmlPullParser mDelegate; + + // Storage for peeked values. + private boolean mPeeked; + private int mEventType; + private int mDepth; + private int mNext; + private List<Attribute> mAttributes; + private String mText; + private String mName; + + // Used to end the document before the actual parser ends. + private int mFinalDepth = -1; + private boolean mEndNow; + + public LayoutParserWrapper(XmlPullParser delegate) { + mDelegate = delegate; + } + + public LayoutParserWrapper peekTillLayoutStart() throws IOException, XmlPullParserException { + final int STATE_LAYOUT_NOT_STARTED = 0; // <layout> tag not encountered yet. + final int STATE_ROOT_NOT_STARTED = 1; // the main view root not found yet. + final int STATE_INSIDE_DATA = 2; // START_TAG for <data> found, but not END_TAG. + + int state = STATE_LAYOUT_NOT_STARTED; + int dataDepth = -1; // depth of the <data> tag. Should be two. + while (true) { + int peekNext = peekNext(); + switch (peekNext) { + case START_TAG: + if (state == STATE_LAYOUT_NOT_STARTED) { + if (mName.equals(TAG_LAYOUT)) { + state = STATE_ROOT_NOT_STARTED; + } else { + return this; // no layout tag in the file. + } + } else if (state == STATE_ROOT_NOT_STARTED) { + if (mName.equals(TAG_DATA)) { + state = STATE_INSIDE_DATA; + dataDepth = mDepth; + } else { + mFinalDepth = mDepth; + return this; + } + } + break; + case END_TAG: + if (state == STATE_INSIDE_DATA) { + if (mDepth <= dataDepth) { + state = STATE_ROOT_NOT_STARTED; + } + } + break; + case END_DOCUMENT: + // No layout start found. + return this; + } + // consume the peeked tag. + next(); + } + } + + private int peekNext() throws IOException, XmlPullParserException { + if (mPeeked) { + return mNext; + } + mEventType = mDelegate.getEventType(); + mNext = mDelegate.next(); + if (mEventType == START_TAG) { + int count = mDelegate.getAttributeCount(); + mAttributes = count > 0 ? new ArrayList<Attribute>(count) : + Collections.<Attribute>emptyList(); + for (int i = 0; i < count; i++) { + mAttributes.add(new Attribute(mDelegate.getAttributeNamespace(i), + mDelegate.getAttributeName(i), mDelegate.getAttributeValue(i))); + } + } + mDepth = mDelegate.getDepth(); + mText = mDelegate.getText(); + mName = mDelegate.getName(); + mPeeked = true; + return mNext; + } + + private void reset() { + mAttributes = null; + mText = null; + mName = null; + mPeeked = false; + } + + @Override + public int next() throws XmlPullParserException, IOException { + int returnValue; + int depth; + if (mPeeked) { + returnValue = mNext; + depth = mDepth; + reset(); + } else if (mEndNow) { + return END_DOCUMENT; + } else { + returnValue = mDelegate.next(); + depth = getDepth(); + } + if (returnValue == END_TAG && depth <= mFinalDepth) { + mEndNow = true; + } + return returnValue; + } + + @Override + public int getEventType() throws XmlPullParserException { + return mPeeked ? mEventType : mDelegate.getEventType(); + } + + @Override + public int getDepth() { + return mPeeked ? mDepth : mDelegate.getDepth(); + } + + @Override + public String getName() { + return mPeeked ? mName : mDelegate.getName(); + } + + @Override + public String getText() { + return mPeeked ? mText : mDelegate.getText(); + } + + @Override + public String getAttributeValue(@Nullable String namespace, String name) { + String returnValue = null; + if (mPeeked) { + if (mAttributes == null) { + if (mEventType != START_TAG) { + throw new IndexOutOfBoundsException("getAttributeValue() called when not at START_TAG."); + } else { + return null; + } + } else { + for (Attribute attribute : mAttributes) { + //noinspection StringEquality for nullness check. + if (attribute.name.equals(name) && (attribute.namespace == namespace || + attribute.namespace != null && attribute.namespace.equals(namespace))) { + returnValue = attribute.value; + break; + } + } + } + } else { + returnValue = mDelegate.getAttributeValue(namespace, name); + } + // Check if the value is bound via data-binding, if yes get the default value. + if (returnValue != null && mFinalDepth >= 0 && returnValue.startsWith("@{")) { + // TODO: Improve the detection of default keyword. + int i = returnValue.lastIndexOf(DEFAULT); + return i > 0 ? returnValue.substring(i + DEFAULT.length(), returnValue.length() - 1) + : null; + } + return returnValue; + } + + private static class Attribute { + @Nullable + public final String namespace; + public final String name; + public final String value; + + public Attribute(@Nullable String namespace, String name, String value) { + this.namespace = namespace; + this.name = name; + this.value = value; + } + } + + // Not affected by peeking. + + @Override + public void setFeature(String s, boolean b) throws XmlPullParserException { + mDelegate.setFeature(s, b); + } + + @Override + public void setProperty(String s, Object o) throws XmlPullParserException { + mDelegate.setProperty(s, o); + } + + @Override + public void setInput(InputStream inputStream, String s) throws XmlPullParserException { + mDelegate.setInput(inputStream, s); + } + + @Override + public void setInput(Reader reader) throws XmlPullParserException { + mDelegate.setInput(reader); + } + + @Override + public String getInputEncoding() { + return mDelegate.getInputEncoding(); + } + + @Override + public String getNamespace(String s) { + return mDelegate.getNamespace(s); + } + + @Override + public String getPositionDescription() { + return mDelegate.getPositionDescription(); + } + + @Override + public int getLineNumber() { + return mDelegate.getLineNumber(); + } + + @Override + public String getNamespace() { + return mDelegate.getNamespace(); + } + + @Override + public int getColumnNumber() { + return mDelegate.getColumnNumber(); + } + + // -- We don't care much about the methods that follow. + + @Override + public void require(int i, String s, String s1) throws XmlPullParserException, IOException { + throw new UnsupportedOperationException("Only few parser methods are supported."); + } + + @Override + public boolean getFeature(String s) { + throw new UnsupportedOperationException("Only few parser methods are supported."); + } + + @Override + public void defineEntityReplacementText(String s, String s1) throws XmlPullParserException { + throw new UnsupportedOperationException("Only few parser methods are supported."); + } + + @Override + public Object getProperty(String s) { + throw new UnsupportedOperationException("Only few parser methods are supported."); + } + + @Override + public int nextToken() throws XmlPullParserException, IOException { + throw new UnsupportedOperationException("Only few parser methods are supported."); + } + + @Override + public int getNamespaceCount(int i) throws XmlPullParserException { + throw new UnsupportedOperationException("Only few parser methods are supported."); + } + + @Override + public String getNamespacePrefix(int i) throws XmlPullParserException { + throw new UnsupportedOperationException("Only few parser methods are supported."); + } + + @Override + public String getNamespaceUri(int i) throws XmlPullParserException { + throw new UnsupportedOperationException("Only few parser methods are supported."); + } + + @Override + public boolean isWhitespace() throws XmlPullParserException { + throw new UnsupportedOperationException("Only few parser methods are supported."); + } + + @Override + public char[] getTextCharacters(int[] ints) { + throw new UnsupportedOperationException("Only few parser methods are supported."); + } + + @Override + public String getPrefix() { + throw new UnsupportedOperationException("Only few parser methods are supported."); + } + + @Override + public boolean isEmptyElementTag() throws XmlPullParserException { + throw new UnsupportedOperationException("Only few parser methods are supported."); + } + + @Override + public int getAttributeCount() { + throw new UnsupportedOperationException("Only few parser methods are supported."); + } + + @Override + public String getAttributeNamespace(int i) { + throw new UnsupportedOperationException("Only few parser methods are supported."); + } + + @Override + public String getAttributeName(int i) { + throw new UnsupportedOperationException("Only few parser methods are supported."); + } + + @Override + public String getAttributePrefix(int i) { + throw new UnsupportedOperationException("Only few parser methods are supported."); + } + + @Override + public String getAttributeType(int i) { + throw new UnsupportedOperationException("Only few parser methods are supported."); + } + + @Override + public boolean isAttributeDefault(int i) { + throw new UnsupportedOperationException("Only few parser methods are supported."); + } + + @Override + public String getAttributeValue(int i) { + throw new UnsupportedOperationException("Only few parser methods are supported."); + } + + @Override + public String nextText() throws XmlPullParserException, IOException { + throw new UnsupportedOperationException("Only few parser methods are supported."); + } + + @Override + public int nextTag() throws XmlPullParserException, IOException { + throw new UnsupportedOperationException("Only few parser methods are supported."); + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ParserFactory.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ParserFactory.java index 6e67f59..e273b2c 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ParserFactory.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ParserFactory.java @@ -53,24 +53,35 @@ public class ParserFactory { @NonNull public static XmlPullParser create(@NonNull File f) throws XmlPullParserException, FileNotFoundException { - InputStream stream = new FileInputStream(f); - return create(stream, f.getName(), f.length()); + return create(f, false); } + public static XmlPullParser create(@NonNull File f, boolean isLayout) + throws XmlPullParserException, FileNotFoundException { + InputStream stream = new FileInputStream(f); + return create(stream, f.getName(), f.length(), isLayout); + } @NonNull public static XmlPullParser create(@NonNull InputStream stream, @Nullable String name) throws XmlPullParserException { - return create(stream, name, -1); + return create(stream, name, -1, false); } @NonNull private static XmlPullParser create(@NonNull InputStream stream, @Nullable String name, - long size) throws XmlPullParserException { + long size, boolean isLayout) throws XmlPullParserException { XmlPullParser parser = instantiateParser(name); stream = readAndClose(stream, name, size); parser.setInput(stream, ENCODING); + if (isLayout) { + try { + return new LayoutParserWrapper(parser).peekTillLayoutStart(); + } catch (IOException e) { + throw new XmlPullParserException(null, parser, e); + } + } return parser; } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java index 9588035..80d7c68 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java @@ -21,6 +21,7 @@ import com.android.layoutlib.bridge.Bridge; import android.graphics.BlendComposite; import android.graphics.BlendComposite.BlendingMode; +import android.graphics.PorterDuff; import android.graphics.PorterDuff.Mode; import android.graphics.PorterDuffColorFilter_Delegate; import android.graphics.PorterDuffXfermode_Delegate; @@ -34,6 +35,8 @@ import java.awt.Composite; */ public final class PorterDuffUtility { + private static final int MODES_COUNT = Mode.values().length; + // Make the class non-instantiable. private PorterDuffUtility() { } @@ -43,12 +46,11 @@ public final class PorterDuffUtility { * {@link Mode#SRC_OVER} for invalid modes. */ public static Mode getPorterDuffMode(int porterDuffMode) { - Mode[] values = Mode.values(); - if (porterDuffMode >= 0 && porterDuffMode < values.length) { - return values[porterDuffMode]; + if (porterDuffMode >= 0 && porterDuffMode < MODES_COUNT) { + return PorterDuff.intToMode(porterDuffMode); } Bridge.getLog().error(LayoutLog.TAG_BROKEN, - String.format("Unknown PorterDuff.Mode: %1$d", porterDuffMode), null /*data*/); + String.format("Unknown PorterDuff.Mode: %1$d", porterDuffMode), null); assert false; return Mode.SRC_OVER; } 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 ac7c409..0ffa357 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 @@ -1051,11 +1051,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { } if (scrollPos != 0) { view.scrollBy(0, scrollPos); - } else { - view.scrollBy(0, scrollPos); } - } else { - view.scrollBy(0, scrollPos); } if (!(view instanceof ViewGroup)) { diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/impl/LayoutParserWrapperTest.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/impl/LayoutParserWrapperTest.java new file mode 100644 index 0000000..2c33862 --- /dev/null +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/impl/LayoutParserWrapperTest.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.layoutlib.bridge.impl; + +import org.junit.Test; +import org.kxml2.io.KXmlParser; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.StringReader; + +import static com.android.SdkConstants.NS_RESOURCES; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; +import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; +import static org.xmlpull.v1.XmlPullParser.END_TAG; +import static org.xmlpull.v1.XmlPullParser.START_TAG; + + +public class LayoutParserWrapperTest { + @Test + @SuppressWarnings("StatementWithEmptyBody") // some for loops need to be empty statements. + public void testDataBindingLayout() throws Exception { + LayoutParserWrapper parser = getParserFromString(sDataBindingLayout); + parser.peekTillLayoutStart(); + assertEquals("Expected START_TAG", START_TAG, parser.next()); + assertEquals("RelativeLayout", parser.getName()); + for (int next = parser.next(); next != START_TAG && next != END_DOCUMENT; + next = parser.next()); + assertEquals("Expected START_TAG", START_TAG, parser.getEventType()); + assertEquals("TextView", parser.getName()); + assertEquals("layout_width incorrect for first text view.", "wrap_content", + parser.getAttributeValue(NS_RESOURCES, "layout_width")); + // Ensure that data-binding part is stripped. + assertEquals("Bound attribute android:text incorrect", "World", + parser.getAttributeValue(NS_RESOURCES, "text")); + assertEquals("resource attribute 'id' for first text view incorrect.", "@+id/first", + parser.getAttributeValue(NS_RESOURCES, "id")); + for (int next = parser.next(); + (next != END_TAG || !"RelativeLayout".equals(parser.getName())) && next != END_DOCUMENT; + next = parser.next()); + assertNotSame("Unexpected end of document", END_DOCUMENT, parser.getEventType()); + assertEquals("Document didn't end when expected.", END_DOCUMENT, parser.next()); + } + + @Test + @SuppressWarnings("StatementWithEmptyBody") + public void testNonDataBindingLayout() throws Exception { + LayoutParserWrapper parser = getParserFromString(sNonDataBindingLayout); + parser.peekTillLayoutStart(); + assertEquals("Expected START_TAG", START_TAG, parser.next()); + assertEquals("RelativeLayout", parser.getName()); + for (int next = parser.next(); next != START_TAG && next != END_DOCUMENT; + next = parser.next()); + assertEquals("Expected START_TAG", START_TAG, parser.getEventType()); + assertEquals("TextView", parser.getName()); + assertEquals("layout_width incorrect for first text view.", "wrap_content", + parser.getAttributeValue(NS_RESOURCES, "layout_width")); + // Ensure that value isn't modified. + assertEquals("Bound attribute android:text incorrect", "@{user.firstName,default=World}", + parser.getAttributeValue(NS_RESOURCES, "text")); + assertEquals("resource attribute 'id' for first text view incorrect.", "@+id/first", + parser.getAttributeValue(NS_RESOURCES, "id")); + for (int next = parser.next(); + (next != END_TAG || !"RelativeLayout".equals(parser.getName())) && next != END_DOCUMENT; + next = parser.next()); + assertNotSame("Unexpected end of document", END_DOCUMENT, parser.getEventType()); + assertEquals("Document didn't end when expected.", END_DOCUMENT, parser.next()); + } + + private static LayoutParserWrapper getParserFromString(String layoutContent) throws + XmlPullParserException { + XmlPullParser parser = new KXmlParser(); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); + parser.setInput(new StringReader(layoutContent)); + return new LayoutParserWrapper(parser); + } + + private static final String sDataBindingLayout = + //language=XML + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + + "<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + + " xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n" + + " xmlns:tools=\"http://schemas.android.com/tools\"\n" + + " tools:context=\".MainActivity\"\n" + + " tools:showIn=\"@layout/activity_main\">\n" + + "\n" + + " <data>\n" + + "\n" + + " <variable\n" + + " name=\"user\"\n" + + " type=\"com.example.User\" />\n" + + " <variable\n" + + " name=\"activity\"\n" + + " type=\"com.example.MainActivity\" />\n" + + " </data>\n" + + "\n" + + " <RelativeLayout\n" + + " android:layout_width=\"match_parent\"\n" + + " android:layout_height=\"match_parent\"\n" + + " android:paddingBottom=\"@dimen/activity_vertical_margin\"\n" + + " android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n" + + " android:paddingRight=\"@dimen/activity_horizontal_margin\"\n" + + " android:paddingTop=\"@dimen/activity_vertical_margin\"\n" + + " app:layout_behavior=\"@string/appbar_scrolling_view_behavior\"\n" + + " >\n" + + "\n" + + " <TextView\n" + + " android:id=\"@+id/first\"\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_alignParentStart=\"true\"\n" + + " android:layout_alignParentLeft=\"true\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:text=\"@{user.firstName,default=World}\" />\n" + + "\n" + + " <TextView\n" + + " android:id=\"@+id/last\"\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:layout_toEndOf=\"@id/first\"\n" + + " android:layout_toRightOf=\"@id/first\"\n" + + " android:text=\"@{user.lastName,default=Hello}\" />\n" + + "\n" + + " <Button\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:layout_below=\"@id/last\"\n" + + " android:text=\"Submit\"\n" + + " android:onClick=\"@{activity.onClick}\"/>\n" + + " </RelativeLayout>\n" + + "</layout>"; + + private static final String sNonDataBindingLayout = + //language=XML + "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + + " xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n" + + " android:layout_width=\"match_parent\"\n" + + " android:layout_height=\"match_parent\"\n" + + " android:paddingBottom=\"@dimen/activity_vertical_margin\"\n" + + " android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n" + + " android:paddingRight=\"@dimen/activity_horizontal_margin\"\n" + + " android:paddingTop=\"@dimen/activity_vertical_margin\"\n" + + " app:layout_behavior=\"@string/appbar_scrolling_view_behavior\"\n" + + ">\n" + + "\n" + + " <TextView\n" + + " android:id=\"@+id/first\"\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_alignParentStart=\"true\"\n" + + " android:layout_alignParentLeft=\"true\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:text=\"@{user.firstName,default=World}\" />\n" + + "\n" + + " <TextView\n" + + " android:id=\"@+id/last\"\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:layout_toEndOf=\"@id/first\"\n" + + " android:layout_toRightOf=\"@id/first\"\n" + + " android:text=\"@{user.lastName,default=Hello}\" />\n" + + "\n" + + " <Button\n" + + " android:layout_width=\"wrap_content\"\n" + + " android:layout_height=\"wrap_content\"\n" + + " android:layout_below=\"@id/last\"\n" + + " android:text=\"Submit\"\n" + + " android:onClick=\"@{activity.onClick}\"/>\n" + + "</RelativeLayout>"; +} 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 484240f..c9bc62e 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 @@ -157,7 +157,6 @@ public final class CreateInfo implements ICreateInfo { * The list of methods to rewrite as delegates. */ public final static String[] DELEGATE_METHODS = new String[] { - "android.animation.AnimatorInflater#loadAnimator", // TODO: remove when Path.approximate() is supported. "android.app.Fragment#instantiate", //(Landroid/content/Context;Ljava/lang/String;Landroid/os/Bundle;)Landroid/app/Fragment;", "android.content.res.Resources$Theme#obtainStyledAttributes", "android.content.res.Resources$Theme#resolveAttribute", @@ -167,6 +166,7 @@ public final class CreateInfo implements ICreateInfo { "android.content.res.TypedArray#getValueAt", "android.content.res.TypedArray#obtain", "android.graphics.BitmapFactory#finishDecode", + "android.graphics.drawable.GradientDrawable#buildRing", "android.graphics.Typeface#getSystemFontConfigLocation", "android.os.Handler#sendMessageAtTime", "android.os.HandlerThread#run", @@ -235,6 +235,7 @@ public final class CreateInfo implements ICreateInfo { "android.graphics.Path", "android.graphics.PathDashPathEffect", "android.graphics.PathEffect", + "android.graphics.PathMeasure", "android.graphics.PixelXorXfermode", "android.graphics.PorterDuffColorFilter", "android.graphics.PorterDuffXfermode", 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 ae4a57d..7ef7566 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 @@ -17,6 +17,7 @@ package com.android.tools.layoutlib.create; import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; @@ -40,6 +41,7 @@ public class DelegateClassAdapter extends ClassVisitor { private final String mClassName; private final Set<String> mDelegateMethods; private final Log mLog; + private boolean mIsStaticInnerClass; /** * Creates a new {@link DelegateClassAdapter} that can transform some methods @@ -62,16 +64,30 @@ public class DelegateClassAdapter extends ClassVisitor { mLog = log; mClassName = className; mDelegateMethods = delegateMethods; + // If this is an inner class, by default, we assume it's static. If it's not we will detect + // by looking at the fields (see visitField) + mIsStaticInnerClass = className.contains("$"); } //---------------------------------- // Methods from the ClassAdapter @Override + public FieldVisitor visitField(int access, String name, String desc, String signature, + Object value) { + if (mIsStaticInnerClass && "this$0".equals(name)) { + // Having a "this$0" field, proves that this class is not a static inner class. + mIsStaticInnerClass = false; + } + + return super.visitField(access, name, desc, signature, value); + } + + @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { - boolean isStatic = (access & Opcodes.ACC_STATIC) != 0; + boolean isStaticMethod = (access & Opcodes.ACC_STATIC) != 0; boolean isNative = (access & Opcodes.ACC_NATIVE) != 0; boolean useDelegate = (isNative && mDelegateMethods.contains(ALL_NATIVES)) || @@ -96,7 +112,8 @@ public class DelegateClassAdapter extends ClassVisitor { MethodVisitor mwDelegate = super.visitMethod(access, name, desc, signature, exceptions); DelegateMethodAdapter a = new DelegateMethodAdapter( - mLog, null, mwDelegate, mClassName, name, desc, isStatic); + mLog, null, mwDelegate, mClassName, name, desc, isStaticMethod, + mIsStaticInnerClass); // A native has no code to visit, so we need to generate it directly. a.generateDelegateCode(); @@ -120,6 +137,7 @@ public class DelegateClassAdapter extends ClassVisitor { desc, signature, exceptions); return new DelegateMethodAdapter( - mLog, mwOriginal, mwDelegate, mClassName, name, desc, isStatic); + mLog, mwOriginal, mwDelegate, mClassName, name, desc, isStaticMethod, + mIsStaticInnerClass); } } 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 12690db..cca9e57 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 @@ -85,6 +85,8 @@ class DelegateMethodAdapter extends MethodVisitor { private String mDesc; /** True if the original method is static. */ private final boolean mIsStatic; + /** True if the method is contained in a static inner class */ + private final boolean mIsStaticInnerClass; /** The internal class name (e.g. <code>com/android/SomeClass$InnerClass</code>.) */ private final String mClassName; /** The method name. */ @@ -120,7 +122,8 @@ class DelegateMethodAdapter extends MethodVisitor { String className, String methodName, String desc, - boolean isStatic) { + boolean isStatic, + boolean isStaticClass) { super(Opcodes.ASM4); mLog = log; mOrgWriter = mvOriginal; @@ -129,6 +132,7 @@ class DelegateMethodAdapter extends MethodVisitor { mMethodName = methodName; mDesc = desc; mIsStatic = isStatic; + mIsStaticInnerClass = isStaticClass; } /** @@ -206,7 +210,7 @@ class DelegateMethodAdapter extends MethodVisitor { // by the 'this' of any outer class, if any. if (!mIsStatic) { - if (outerType != null) { + if (outerType != null && !mIsStaticInnerClass) { // The first-level inner class has a package-protected member called 'this$0' // that points to the outer class. 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 648cea4..e37a09b 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 @@ -27,6 +27,7 @@ import static org.junit.Assert.fail; import com.android.tools.layoutlib.create.dataclass.ClassWithNative; import com.android.tools.layoutlib.create.dataclass.OuterClass; import com.android.tools.layoutlib.create.dataclass.OuterClass.InnerClass; +import com.android.tools.layoutlib.create.dataclass.OuterClass.StaticInnerClass; import org.junit.Before; import org.junit.Test; @@ -56,6 +57,8 @@ public class DelegateClassAdapterTest { 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(); @Before public void setUp() throws Exception { @@ -294,6 +297,61 @@ 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>(); + delegateMethods.add("get"); + + // Generate the delegate for the outer class. + ClassWriter cwOuter = new ClassWriter(0 /*flags*/); + String outerClassName = OUTER_CLASS_NAME.replace('.', '/'); + DelegateClassAdapter cvOuter = new DelegateClassAdapter( + mLog, cwOuter, outerClassName, delegateMethods); + ClassReader cr = new ClassReader(OUTER_CLASS_NAME); + cr.accept(cvOuter, 0 /* flags */); + + // Generate the delegate for the static inner class. + ClassWriter cwInner = new ClassWriter(0 /*flags*/); + String innerClassName = STATIC_INNER_CLASS_NAME.replace('.', '/'); + DelegateClassAdapter cvInner = new DelegateClassAdapter( + mLog, cwInner, innerClassName, delegateMethods); + cr = new ClassReader(STATIC_INNER_CLASS_NAME); + cr.accept(cvInner, 0 /* flags */); + + // Load the generated classes in a different class loader and try them + ClassLoader2 cl2 = null; + try { + cl2 = new ClassLoader2() { + @Override + public void testModifiedInstance() throws Exception { + + // Check the outer class + Class<?> outerClazz2 = loadClass(OUTER_CLASS_NAME); + Object o2 = outerClazz2.newInstance(); + assertNotNull(o2); + + // Check the inner class. Since it's not a static inner class, we need + // to use the hidden constructor that takes the outer class as first parameter. + Class<?> innerClazz2 = loadClass(STATIC_INNER_CLASS_NAME); + Constructor<?> innerCons = innerClazz2.getConstructor(); + Object i2 = innerCons.newInstance(); + assertNotNull(i2); + + // The original StaticInner.get returns 100+10+20, + // but the delegate makes it return 6+10+20 + assertEquals(6+10+20, callGet(i2, 10, 20)); + assertEquals(100+10+20, callGet_Original(i2, 10, 20)); + } + }; + cl2.add(OUTER_CLASS_NAME, cwOuter.toByteArray()); + cl2.add(STATIC_INNER_CLASS_NAME, cwInner.toByteArray()); + cl2.testModifiedInstance(); + } catch (Throwable t) { + throw dumpGeneratedClass(t, cl2); + } + } + //------- /** diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java index f083e76..6dfb816 100644 --- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java @@ -45,6 +45,16 @@ public class OuterClass { } } + public static class StaticInnerClass { + public StaticInnerClass() { + } + + // StaticInnerClass.get returns 100 + a + b + public int get(int a, long b) { + return 100 + a + (int) b; + } + } + @SuppressWarnings("unused") private String privateMethod() { return "outerPrivateMethod"; diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_StaticInnerClass_Delegate.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_StaticInnerClass_Delegate.java new file mode 100644 index 0000000..a29439e --- /dev/null +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_StaticInnerClass_Delegate.java @@ -0,0 +1,30 @@ +/* + * 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.tools.layoutlib.create.dataclass; + +import com.android.tools.layoutlib.create.DelegateClassAdapterTest; +import com.android.tools.layoutlib.create.dataclass.OuterClass.StaticInnerClass; + +/** + * Used by {@link DelegateClassAdapterTest}. + */ +public class OuterClass_StaticInnerClass_Delegate { + // The delegate override of Inner.get return 6 + a + b + public static int get(StaticInnerClass inner, int a, long b) { + return 6 + a + (int) b; + } +} |