diff options
author | Jean-Baptiste Queru <jbq@google.com> | 2009-11-12 18:45:53 -0800 |
---|---|---|
committer | Jean-Baptiste Queru <jbq@google.com> | 2009-11-13 13:53:39 -0800 |
commit | 9db3d07b9620b4269ab33f78604a36327e536ce1 (patch) | |
tree | 41e294f34b9695187af098cd42167489fb0c8fb0 /tools | |
parent | 6c63ee4fc4acae4bbbbd2a49e0a68206221f0de0 (diff) | |
download | frameworks_base-9db3d07b9620b4269ab33f78604a36327e536ce1.zip frameworks_base-9db3d07b9620b4269ab33f78604a36327e536ce1.tar.gz frameworks_base-9db3d07b9620b4269ab33f78604a36327e536ce1.tar.bz2 |
eclair snapshot
Diffstat (limited to 'tools')
63 files changed, 4907 insertions, 843 deletions
diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp index dbcef6d..c346b90 100644 --- a/tools/aapt/AaptAssets.cpp +++ b/tools/aapt/AaptAssets.cpp @@ -55,6 +55,7 @@ static bool validateFileName(const char* fileName) static bool isHidden(const char *root, const char *path) { + const char *ext = NULL; const char *type = NULL; // Skip all hidden files. @@ -83,6 +84,9 @@ static bool isHidden(const char *root, const char *path) } else if (path[strlen(path)-1] == '~') { // Skip suspected emacs backup files. type = "backup"; + } else if ((ext = strrchr(path, '.')) != NULL && strcmp(ext, ".scc") == 0) { + // Skip VisualSourceSafe files and don't chatter about it + return true; } else { // Let everything else through. return false; @@ -187,6 +191,13 @@ AaptGroupEntry::parseNamePart(const String8& part, int* axis, uint32_t* value) return 0; } + // navigation hidden + if (getNavHiddenName(part.string(), &config)) { + *axis = AXIS_NAVHIDDEN; + *value = config.inputFlags; + return 0; + } + // navigation if (getNavigationName(part.string(), &config)) { *axis = AXIS_NAVIGATION; @@ -217,7 +228,7 @@ AaptGroupEntry::initFromDirName(const char* dir, String8* resType) Vector<String8> parts; String8 mcc, mnc, loc, layoutsize, layoutlong, orient, den; - String8 touch, key, keysHidden, nav, size, vers; + String8 touch, key, keysHidden, nav, navHidden, size, vers; const char *p = dir; const char *q; @@ -393,6 +404,19 @@ AaptGroupEntry::initFromDirName(const char* dir, String8* resType) //printf("not keyboard: %s\n", part.string()); } + // navigation hidden + if (getNavHiddenName(part.string())) { + navHidden = part; + + index++; + if (index == N) { + goto success; + } + part = parts[index]; + } else { + //printf("not navHidden: %s\n", part.string()); + } + if (getNavigationName(part.string())) { nav = part; @@ -443,6 +467,7 @@ success: this->touchscreen = touch; this->keysHidden = keysHidden; this->keyboard = key; + this->navHidden = navHidden; this->navigation = nav; this->screenSize = size; this->version = vers; @@ -476,6 +501,8 @@ AaptGroupEntry::toString() const s += ","; s += keyboard; s += ","; + s += navHidden; + s += ","; s += navigation; s += ","; s += screenSize; @@ -528,6 +555,10 @@ AaptGroupEntry::toDirName(const String8& resType) const s += "-"; s += keyboard; } + if (this->navHidden != "") { + s += "-"; + s += navHidden; + } if (this->navigation != "") { s += "-"; s += navigation; @@ -852,6 +883,30 @@ bool AaptGroupEntry::getKeyboardName(const char* name, return false; } +bool AaptGroupEntry::getNavHiddenName(const char* name, + ResTable_config* out) +{ + uint8_t mask = 0; + uint8_t value = 0; + if (strcmp(name, kWildcardName) == 0) { + mask = out->MASK_NAVHIDDEN; + value = out->NAVHIDDEN_ANY; + } else if (strcmp(name, "navexposed") == 0) { + mask = out->MASK_NAVHIDDEN; + value = out->NAVHIDDEN_NO; + } else if (strcmp(name, "navhidden") == 0) { + mask = out->MASK_NAVHIDDEN; + value = out->NAVHIDDEN_YES; + } + + if (mask != 0) { + if (out) out->inputFlags = (out->inputFlags&~mask) | value; + return true; + } + + return false; +} + bool AaptGroupEntry::getNavigationName(const char* name, ResTable_config* out) { @@ -953,6 +1008,7 @@ int AaptGroupEntry::compare(const AaptGroupEntry& o) const if (v == 0) v = touchscreen.compare(o.touchscreen); if (v == 0) v = keysHidden.compare(o.keysHidden); if (v == 0) v = keyboard.compare(o.keyboard); + if (v == 0) v = navHidden.compare(o.navHidden); if (v == 0) v = navigation.compare(o.navigation); if (v == 0) v = screenSize.compare(o.screenSize); if (v == 0) v = version.compare(o.version); @@ -973,6 +1029,7 @@ ResTable_config AaptGroupEntry::toParams() const getTouchscreenName(touchscreen.string(), ¶ms); getKeysHiddenName(keysHidden.string(), ¶ms); getKeyboardName(keyboard.string(), ¶ms); + getNavHiddenName(navHidden.string(), ¶ms); getNavigationName(navigation.string(), ¶ms); getScreenSizeName(screenSize.string(), ¶ms); getVersionName(version.string(), ¶ms); @@ -1819,6 +1876,19 @@ void AaptAssets::print() const AaptDir::print(); } +sp<AaptDir> AaptAssets::resDir(const String8& name) +{ + const Vector<sp<AaptDir> >& dirs = mDirs; + const size_t N = dirs.size(); + for (size_t i=0; i<N; i++) { + const sp<AaptDir>& d = dirs.itemAt(i); + if (d->getLeaf() == name) { + return d; + } + } + return NULL; +} + bool valid_symbol_name(const String8& symbol) { diff --git a/tools/aapt/AaptAssets.h b/tools/aapt/AaptAssets.h index 63afe5c..26500a3 100644 --- a/tools/aapt/AaptAssets.h +++ b/tools/aapt/AaptAssets.h @@ -15,7 +15,7 @@ #include <utils/String8.h> #include <utils/Vector.h> #include <utils/RefBase.h> -#include <utils/ZipFile.h> +#include "ZipFile.h" #include "Bundle.h" #include "SourcePos.h" @@ -37,6 +37,7 @@ enum { AXIS_TOUCHSCREEN, AXIS_KEYSHIDDEN, AXIS_KEYBOARD, + AXIS_NAVHIDDEN, AXIS_NAVIGATION, AXIS_SCREENSIZE, AXIS_VERSION @@ -64,6 +65,7 @@ public: String8 touchscreen; String8 keysHidden; String8 keyboard; + String8 navHidden; String8 navigation; String8 screenSize; String8 version; @@ -83,6 +85,7 @@ public: static bool getKeysHiddenName(const char* name, ResTable_config* out = NULL); static bool getKeyboardName(const char* name, ResTable_config* out = NULL); static bool getNavigationName(const char* name, ResTable_config* out = NULL); + static bool getNavHiddenName(const char* name, ResTable_config* out = NULL); static bool getScreenSizeName(const char* name, ResTable_config* out = NULL); static bool getVersionName(const char* name, ResTable_config* out = NULL); @@ -131,7 +134,9 @@ public: { //printf("new AaptFile created %s\n", (const char*)sourceFile); } - virtual ~AaptFile() { } + virtual ~AaptFile() { + free(mData); + } const String8& getPath() const { return mPath; } const AaptGroupEntry& getGroupEntry() const { return mGroupEntry; } @@ -447,7 +452,13 @@ private: AaptSymbolEntry mDefSymbol; }; -class ResourceTypeSet; +class ResourceTypeSet : public RefBase, + public KeyedVector<String8,sp<AaptGroup> > +{ +public: + ResourceTypeSet(); +}; + /** * Asset hierarchy being operated on. @@ -455,8 +466,8 @@ class ResourceTypeSet; class AaptAssets : public AaptDir { public: - AaptAssets() : AaptDir(String8(), String8()), mHaveIncludedAssets(false) { } - virtual ~AaptAssets() { } + AaptAssets() : AaptDir(String8(), String8()), mHaveIncludedAssets(false), mRes(NULL) { } + virtual ~AaptAssets() { delete mRes; } const String8& getPackage() const { return mPackage; } void setPackage(const String8& package) { mPackage = package; mSymbolsPrivatePackage = package; } @@ -474,6 +485,8 @@ public: const sp<AaptFile>& file, const String8& resType); + void addGroupEntry(const AaptGroupEntry& entry) { mGroupEntries.add(entry); } + ssize_t slurpFromArgs(Bundle* bundle); virtual ssize_t slurpFullTree(Bundle* bundle, @@ -498,13 +511,14 @@ public: void print() const; inline const Vector<sp<AaptDir> >& resDirs() { return mDirs; } + sp<AaptDir> resDir(const String8& name); inline sp<AaptAssets> getOverlay() { return mOverlay; } inline void setOverlay(sp<AaptAssets>& overlay) { mOverlay = overlay; } inline KeyedVector<String8, sp<ResourceTypeSet> >* getResources() { return mRes; } inline void - setResources(KeyedVector<String8, sp<ResourceTypeSet> >* res) { mRes = res; } + setResources(KeyedVector<String8, sp<ResourceTypeSet> >* res) { delete mRes; mRes = res; } private: String8 mPackage; diff --git a/tools/aapt/Android.mk b/tools/aapt/Android.mk index fdc859c..2d8973d 100644 --- a/tools/aapt/Android.mk +++ b/tools/aapt/Android.mk @@ -17,7 +17,10 @@ LOCAL_SRC_FILES := \ ResourceTable.cpp \ Images.cpp \ Resource.cpp \ - SourcePos.cpp + SourcePos.cpp \ + ZipEntry.cpp \ + ZipFile.cpp + LOCAL_CFLAGS += -Wno-format-y2k diff --git a/tools/aapt/Bundle.h b/tools/aapt/Bundle.h index a6fedf3..1ac13f2 100644 --- a/tools/aapt/Bundle.h +++ b/tools/aapt/Bundle.h @@ -7,7 +7,10 @@ #define __BUNDLE_H #include <stdlib.h> -#include <utils.h> // android +#include <utils/Log.h> +#include <utils/threads.h> +#include <utils/List.h> +#include <utils/Errors.h> #include <utils/String8.h> #include <utils/Vector.h> @@ -36,7 +39,7 @@ public: mRequireLocalization(false), mPseudolocalize(false), mValues(false), mCompressionMethod(0), mOutputAPKFile(NULL), - mAssetSourceDir(NULL), + mAssetSourceDir(NULL), mProguardFile(NULL), mAndroidManifestFile(NULL), mPublicOutputFile(NULL), mRClassDir(NULL), mResourceIntermediatesDir(NULL), mMinSdkVersion(NULL), mTargetSdkVersion(NULL), mMaxSdkVersion(NULL), @@ -77,14 +80,18 @@ public: void setValues(bool val) { mValues = val; } int getCompressionMethod(void) const { return mCompressionMethod; } void setCompressionMethod(int val) { mCompressionMethod = val; } + bool getJunkPath(void) const { return mJunkPath; } + void setJunkPath(bool val) { mJunkPath = val; } const char* getOutputAPKFile() const { return mOutputAPKFile; } void setOutputAPKFile(const char* val) { mOutputAPKFile = val; } - /* - * Input options. + /* + * Input options. */ const char* getAssetSourceDir() const { return mAssetSourceDir; } void setAssetSourceDir(const char* dir) { mAssetSourceDir = dir; } + const char* getProguardFile() const { return mProguardFile; } + void setProguardFile(const char* file) { mProguardFile = file; } const android::Vector<const char*>& getResourceSourceDirs() const { return mResourceSourceDirs; } void addResourceSourceDir(const char* dir) { mResourceSourceDirs.insertAt(dir,0); } const char* getAndroidManifestFile() const { return mAndroidManifestFile; } @@ -114,7 +121,7 @@ public: void setVersionCode(const char* val) { mVersionCode = val; } const char* getVersionName() const { return mVersionName; } void setVersionName(const char* val) { mVersionName = val; } - + /* * Set and get the file specification. * @@ -156,8 +163,10 @@ private: bool mPseudolocalize; bool mValues; int mCompressionMethod; + bool mJunkPath; const char* mOutputAPKFile; const char* mAssetSourceDir; + const char* mProguardFile; const char* mAndroidManifestFile; const char* mPublicOutputFile; const char* mRClassDir; @@ -167,13 +176,13 @@ private: android::Vector<const char*> mJarFiles; android::Vector<const char*> mNoCompressExtensions; android::Vector<const char*> mResourceSourceDirs; - + const char* mMinSdkVersion; const char* mTargetSdkVersion; const char* mMaxSdkVersion; const char* mVersionCode; const char* mVersionName; - + /* file specification */ int mArgc; char* const* mArgv; diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp index 5f80ade..1a536d6 100644 --- a/tools/aapt/Command.cpp +++ b/tools/aapt/Command.cpp @@ -8,8 +8,10 @@ #include "ResourceTable.h" #include "XMLNode.h" -#include <utils.h> -#include <utils/ZipFile.h> +#include <utils/Log.h> +#include <utils/threads.h> +#include <utils/List.h> +#include <utils/Errors.h> #include <fcntl.h> #include <errno.h> @@ -231,7 +233,7 @@ static ssize_t indexOfAttribute(const ResXMLTree& tree, uint32_t attrRes) return -1; } -static String8 getAttribute(const ResXMLTree& tree, const char* ns, +String8 getAttribute(const ResXMLTree& tree, const char* ns, const char* attr, String8* outError) { ssize_t idx = tree.indexOfAttribute(ns, attr); @@ -330,9 +332,11 @@ enum { TARGET_SDK_VERSION_ATTR = 0x01010270, TEST_ONLY_ATTR = 0x01010272, DENSITY_ATTR = 0x0101026c, + GL_ES_VERSION_ATTR = 0x01010281, SMALL_SCREEN_ATTR = 0x01010284, NORMAL_SCREEN_ATTR = 0x01010285, LARGE_SCREEN_ATTR = 0x01010286, + REQUIRED_ATTR = 0x0101028e, }; const char *getComponentName(String8 &pkgName, String8 &componentName) { @@ -501,8 +505,25 @@ int doDump(Bundle* bundle) bool withinActivity = false; bool isMainActivity = false; bool isLauncherActivity = false; + bool isSearchable = false; bool withinApplication = false; bool withinReceiver = false; + bool withinService = false; + bool withinIntentFilter = false; + bool hasMainActivity = false; + bool hasOtherActivities = false; + bool hasOtherReceivers = false; + bool hasOtherServices = false; + bool hasWallpaperService = false; + bool hasImeService = false; + bool hasWidgetReceivers = false; + bool hasIntentFilter = false; + bool actMainActivity = false; + bool actWidgetReceivers = false; + bool actImeService = false; + bool actWallpaperService = false; + bool specCameraFeature = false; + bool hasCameraPermission = false; int targetSdk = 0; int smallScreen = 1; int normalScreen = 1; @@ -512,9 +533,48 @@ int doDump(Bundle* bundle) String8 activityLabel; String8 activityIcon; String8 receiverName; + String8 serviceName; while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { if (code == ResXMLTree::END_TAG) { depth--; + if (depth < 2) { + withinApplication = false; + } else if (depth < 3) { + if (withinActivity && isMainActivity && isLauncherActivity) { + const char *aName = getComponentName(pkg, activityName); + if (aName != NULL) { + printf("launchable activity name='%s'", aName); + } + printf("label='%s' icon='%s'\n", + activityLabel.string(), + activityIcon.string()); + } + if (!hasIntentFilter) { + hasOtherActivities |= withinActivity; + hasOtherReceivers |= withinReceiver; + hasOtherServices |= withinService; + } + withinActivity = false; + withinService = false; + withinReceiver = false; + hasIntentFilter = false; + isMainActivity = isLauncherActivity = false; + } else if (depth < 4) { + if (withinIntentFilter) { + if (withinActivity) { + hasMainActivity |= actMainActivity; + hasOtherActivities |= !actMainActivity; + } else if (withinReceiver) { + hasWidgetReceivers |= actWidgetReceivers; + hasOtherReceivers |= !actWidgetReceivers; + } else if (withinService) { + hasImeService |= actImeService; + hasWallpaperService |= actWallpaperService; + hasOtherServices |= (!actImeService && !actWallpaperService); + } + } + withinIntentFilter = false; + } continue; } if (code != ResXMLTree::START_TAG) { @@ -522,7 +582,7 @@ int doDump(Bundle* bundle) } depth++; String8 tag(tree.getElementName(&len)); - //printf("Depth %d tag %s\n", depth, tag.string()); + //printf("Depth %d, %s\n", depth, tag.string()); if (depth == 1) { if (tag != "manifest") { fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n"); @@ -650,10 +710,42 @@ int doDump(Bundle* bundle) NORMAL_SCREEN_ATTR, NULL, 1); largeScreen = getIntegerAttribute(tree, LARGE_SCREEN_ATTR, NULL, 1); + } else if (tag == "uses-feature") { + String8 name = getAttribute(tree, NAME_ATTR, &error); + + if (name != "" && error == "") { + int req = getIntegerAttribute(tree, + REQUIRED_ATTR, NULL, 1); + if (name == "android.hardware.camera") { + specCameraFeature = true; + } + printf("uses-feature%s:'%s'\n", + req ? "" : "-not-required", name.string()); + } else { + int vers = getIntegerAttribute(tree, + GL_ES_VERSION_ATTR, &error); + if (error == "") { + printf("uses-gl-es:'0x%x'\n", vers); + } + } + } else if (tag == "uses-permission") { + String8 name = getAttribute(tree, NAME_ATTR, &error); + if (name != "" && error == "") { + if (name == "android.permission.CAMERA") { + hasCameraPermission = true; + } + printf("uses-permission:'%s'\n", name.string()); + } else { + fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", + error.string()); + goto bail; + } } } else if (depth == 3 && withinApplication) { withinActivity = false; withinReceiver = false; + withinService = false; + hasIntentFilter = false; if(tag == "activity") { withinActivity = true; activityName = getAttribute(tree, NAME_ATTR, &error); @@ -679,7 +771,10 @@ int doDump(Bundle* bundle) fprintf(stderr, "ERROR getting 'android:name' attribute for uses-library: %s\n", error.string()); goto bail; } - printf("uses-library:'%s'\n", libraryName.string()); + int req = getIntegerAttribute(tree, + REQUIRED_ATTR, NULL, 1); + printf("uses-library%s:'%s'\n", + req ? "" : "-not-required", libraryName.string()); } else if (tag == "receiver") { withinReceiver = true; receiverName = getAttribute(tree, NAME_ATTR, &error); @@ -688,76 +783,97 @@ int doDump(Bundle* bundle) fprintf(stderr, "ERROR getting 'android:name' attribute for receiver: %s\n", error.string()); goto bail; } + } else if (tag == "service") { + withinService = true; + serviceName = getAttribute(tree, NAME_ATTR, &error); + + if (error != "") { + fprintf(stderr, "ERROR getting 'android:name' attribute for service: %s\n", error.string()); + goto bail; + } } - } else if (depth == 5) { - if (withinActivity) { - if (tag == "action") { - //printf("LOG: action tag\n"); - String8 action = getAttribute(tree, NAME_ATTR, &error); - if (error != "") { - fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", error.string()); - goto bail; - } + } else if ((depth == 4) && (tag == "intent-filter")) { + hasIntentFilter = true; + withinIntentFilter = true; + actMainActivity = actWidgetReceivers = actImeService = actWallpaperService = false; + } else if ((depth == 5) && withinIntentFilter){ + String8 action; + if (tag == "action") { + action = getAttribute(tree, NAME_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", error.string()); + goto bail; + } + if (withinActivity) { if (action == "android.intent.action.MAIN") { isMainActivity = true; - //printf("LOG: isMainActivity==true\n"); + actMainActivity = true; } - } else if (tag == "category") { - String8 category = getAttribute(tree, NAME_ATTR, &error); - if (error != "") { - fprintf(stderr, "ERROR getting 'name' attribute: %s\n", error.string()); - goto bail; + } else if (withinReceiver) { + if (action == "android.appwidget.action.APPWIDGET_UPDATE") { + actWidgetReceivers = true; } - if (category == "android.intent.category.LAUNCHER") { - isLauncherActivity = true; - //printf("LOG: isLauncherActivity==true\n"); + } else if (withinService) { + if (action == "android.view.InputMethod") { + actImeService = true; + } else if (action == "android.service.wallpaper.WallpaperService") { + actWallpaperService = true; } } - } else if (withinReceiver) { - if (tag == "action") { - String8 action = getAttribute(tree, NAME_ATTR, &error); - if (error != "") { - fprintf(stderr, "ERROR getting 'android:name' attribute for receiver: %s\n", error.string()); - goto bail; - } - if (action == "android.appwidget.action.APPWIDGET_UPDATE") { - const char *rName = getComponentName(pkg, receiverName); - if (rName != NULL) { - printf("gadget-receiver:'%s/%s'\n", pkg.string(), rName); - } - } + if (action == "android.intent.action.SEARCH") { + isSearchable = true; } } - } - if (depth < 2) { - withinApplication = false; - } - if (depth < 3) { - //if (withinActivity) printf("LOG: withinActivity==false\n"); - withinActivity = false; - withinReceiver = false; + if (tag == "category") { + String8 category = getAttribute(tree, NAME_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR getting 'name' attribute: %s\n", error.string()); + goto bail; + } + if (withinActivity) { + if (category == "android.intent.category.LAUNCHER") { + isLauncherActivity = true; + } + } + } } + } - if (depth < 5) { - //if (isMainActivity) printf("LOG: isMainActivity==false\n"); - //if (isLauncherActivity) printf("LOG: isLauncherActivity==false\n"); - isMainActivity = false; - isLauncherActivity = false; - } + if (!specCameraFeature && hasCameraPermission) { + // For applications that have not explicitly stated their + // camera feature requirements, but have requested the camera + // permission, we are going to give them compatibility treatment + // of requiring the equivalent to original android devices. + printf("uses-feature:'android.hardware.camera'\n"); + printf("uses-feature:'android.hardware.camera.autofocus'\n"); + } - if (withinActivity && isMainActivity && isLauncherActivity) { - printf("launchable activity:"); - const char *aName = getComponentName(pkg, activityName); - if (aName != NULL) { - printf(" name='%s'", aName); - } - printf("label='%s' icon='%s'\n", - activityLabel.string(), - activityIcon.string()); - } + if (hasMainActivity) { + printf("main\n"); } - + if (hasWidgetReceivers) { + printf("app-widget\n"); + } + if (hasImeService) { + printf("ime\n"); + } + if (hasWallpaperService) { + printf("wallpaper\n"); + } + if (hasOtherActivities) { + printf("other-activities\n"); + } + if (isSearchable) { + printf("search\n"); + } + if (hasOtherReceivers) { + printf("other-receivers\n"); + } + if (hasOtherServices) { + printf("other-services\n"); + } + // Determine default values for any unspecified screen sizes, // based on the target SDK of the package. As of 4 (donut) // the screen size support was introduced, so all default to @@ -776,7 +892,7 @@ int doDump(Bundle* bundle) if (normalScreen != 0) printf(" 'normal'"); if (largeScreen != 0) printf(" 'large'"); printf("\n"); - + printf("locales:"); Vector<String8> locales; res.getLocales(&locales); @@ -789,7 +905,7 @@ int doDump(Bundle* bundle) printf(" '%s'", localeStr); } printf("\n"); - + Vector<ResTable_config> configs; res.getConfigurations(&configs); SortedVector<int> densities; @@ -799,14 +915,14 @@ int doDump(Bundle* bundle) if (dens == 0) dens = 160; densities.add(dens); } - + printf("densities:"); const size_t ND = densities.size(); for (size_t i=0; i<ND; i++) { printf(" '%d'", densities[i]); } printf("\n"); - + AssetDir* dir = assets.openNonAssetDir(assetsCookie, "lib"); if (dir != NULL) { if (dir->getFileCount() > 0) { @@ -881,8 +997,15 @@ int doAdd(Bundle* bundle) printf(" '%s'... (from gzip)\n", fileName); result = zip->addGzip(fileName, String8(fileName).getBasePath().string(), NULL); } else { - printf(" '%s'...\n", fileName); - result = zip->add(fileName, bundle->getCompressionMethod(), NULL); + if (bundle->getJunkPath()) { + String8 storageName = String8(fileName).getPathLeaf(); + printf(" '%s' as '%s'...\n", fileName, storageName.string()); + result = zip->add(fileName, storageName.string(), + bundle->getCompressionMethod(), NULL); + } else { + printf(" '%s'...\n", fileName); + result = zip->add(fileName, bundle->getCompressionMethod(), NULL); + } } if (result != NO_ERROR) { fprintf(stderr, "Unable to add '%s' to '%s'", bundle->getFileSpecEntry(i), zipFileName); @@ -1043,6 +1166,12 @@ int doPackage(Bundle* bundle) } } + // Write out the ProGuard file + err = writeProguardFile(bundle, assets); + if (err < 0) { + goto bail; + } + // Write the apk if (outputAPKFile) { err = writeAPK(bundle, assets, String8(outputAPKFile)); diff --git a/tools/aapt/Images.cpp b/tools/aapt/Images.cpp index 0a4c68b..f2414dd 100644 --- a/tools/aapt/Images.cpp +++ b/tools/aapt/Images.cpp @@ -44,6 +44,9 @@ struct image_info } free(allocRows); } + free(info9Patch.xDivs); + free(info9Patch.yDivs); + free(info9Patch.colors); } png_uint_32 width; @@ -833,6 +836,7 @@ static void write_png(const char* imageName, int i; png_unknown_chunk unknowns[1]; + unknowns[0].data = NULL; png_bytepp outRows = (png_bytepp) malloc((int) imageInfo.height * png_sizeof(png_bytep)); if (outRows == (png_bytepp) 0) { @@ -939,6 +943,7 @@ static void write_png(const char* imageName, free(outRows[i]); } free(outRows); + free(unknowns[0].data); png_get_IHDR(write_ptr, write_info, &width, &height, &bit_depth, &color_type, &interlace_type, diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp index 12a0445..98286c0 100644 --- a/tools/aapt/Main.cpp +++ b/tools/aapt/Main.cpp @@ -6,8 +6,10 @@ #include "Main.h" #include "Bundle.h" -#include <utils.h> -#include <utils/ZipFile.h> +#include <utils/Log.h> +#include <utils/threads.h> +#include <utils/List.h> +#include <utils/Errors.h> #include <stdlib.h> #include <getopt.h> @@ -57,9 +59,9 @@ void usage(void) " [-0 extension [-0 extension ...]] [-g tolerance] [-j jarfile] \\\n" " [--min-sdk-version VAL] [--target-sdk-version VAL] \\\n" " [--max-sdk-version VAL] [--app-version VAL] \\\n" - " [--app-version-name TEXT] \\\n" + " [--app-version-name TEXT]\\\n" " [-I base-package [-I base-package ...]] \\\n" - " [-A asset-source-dir] [-P public-definitions-file] \\\n" + " [-A asset-source-dir] [-G class-list-file] [-P public-definitions-file] \\\n" " [-S resource-sources [-S resource-sources ...]] " " [-F apk-file] [-J R-file-dir] \\\n" " [raw-files-dir [raw-files-dir] ...]\n" @@ -97,6 +99,7 @@ void usage(void) " -f force overwrite of existing files\n" " -g specify a pixel tolerance to force images to grayscale, default 0\n" " -j specify a jar or zip file containing classes to include\n" + " -k junk path of file(s) added\n" " -m make package directories under location specified by -J\n" #if 0 " -p pseudolocalize the default configuration\n" @@ -107,6 +110,7 @@ void usage(void) " -z require localization of resource attributes marked with\n" " localization=\"suggested\"\n" " -A additional directory in which to find raw asset files\n" + " -G A file to output proguard options into.\n" " -F specify the apk file to output\n" " -I add an existing package to base include set\n" " -J specify where to output R.java resource constant definitions\n" @@ -233,6 +237,9 @@ int main(int argc, char* const argv[]) bundle.setGrayscaleTolerance(tolerance); printf("%s: Images with deviation <= %d will be forced to grayscale.\n", prog, tolerance); break; + case 'k': + bundle.setJunkPath(true); + break; case 'm': bundle.setMakePackageDirs(true); break; @@ -272,6 +279,17 @@ int main(int argc, char* const argv[]) convertPath(argv[0]); bundle.setAssetSourceDir(argv[0]); break; + case 'G': + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '-G' option\n"); + wantUsage = true; + goto bail; + } + convertPath(argv[0]); + bundle.setProguardFile(argv[0]); + break; case 'I': argc--; argv++; diff --git a/tools/aapt/Main.h b/tools/aapt/Main.h index 65c0a8a..3ba4f39 100644 --- a/tools/aapt/Main.h +++ b/tools/aapt/Main.h @@ -6,10 +6,13 @@ #ifndef __MAIN_H #define __MAIN_H -#include <utils.h> +#include <utils/Log.h> +#include <utils/threads.h> +#include <utils/List.h> +#include <utils/Errors.h> #include "Bundle.h" #include "AaptAssets.h" -#include <utils/ZipFile.h> +#include "ZipFile.h" extern int doVersion(Bundle* bundle); extern int doList(Bundle* bundle); @@ -30,6 +33,8 @@ extern android::status_t buildResources(Bundle* bundle, extern android::status_t writeResourceSymbols(Bundle* bundle, const sp<AaptAssets>& assets, const String8& pkgName, bool includePrivate); +extern android::status_t writeProguardFile(Bundle* bundle, const sp<AaptAssets>& assets); + extern bool isValidResourceType(const String8& type); ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptAssets>& assets); @@ -38,4 +43,7 @@ extern status_t filterResources(Bundle* bundle, const sp<AaptAssets>& assets); int dumpResources(Bundle* bundle); +String8 getAttribute(const ResXMLTree& tree, const char* ns, + const char* attr, String8* outError); + #endif // __MAIN_H diff --git a/tools/aapt/Package.cpp b/tools/aapt/Package.cpp index eb7d6f5..999a5cf 100644 --- a/tools/aapt/Package.cpp +++ b/tools/aapt/Package.cpp @@ -7,8 +7,10 @@ #include "AaptAssets.h" #include "ResourceTable.h" -#include <utils.h> -#include <utils/ZipFile.h> +#include <utils/Log.h> +#include <utils/threads.h> +#include <utils/List.h> +#include <utils/Errors.h> #include <sys/types.h> #include <dirent.h> @@ -166,7 +168,7 @@ status_t writeAPK(Bundle* bundle, const sp<AaptAssets>& assets, delete zip; // close the file so we can remove it in Win32 zip = NULL; if (unlink(outputFile.string()) != 0) { - fprintf(stderr, "WARNING: could not unlink '%s'\n", outputFile.string()); + fprintf(stderr, "warning: could not unlink '%s'\n", outputFile.string()); } } @@ -179,7 +181,7 @@ bail: printf("Removing %s due to earlier failures\n", outputFile.string()); } if (unlink(outputFile.string()) != 0) { - fprintf(stderr, "WARNING: could not unlink '%s'\n", outputFile.string()); + fprintf(stderr, "warning: could not unlink '%s'\n", outputFile.string()); } } @@ -281,7 +283,7 @@ bool processFile(Bundle* bundle, ZipFile* zip, if (fileNameLen > excludeExtensionLen && (0 == strcmp(storageName.string() + (fileNameLen - excludeExtensionLen), kExcludeExtension))) { - fprintf(stderr, "WARNING: '%s' not added to Zip\n", storageName.string()); + fprintf(stderr, "warning: '%s' not added to Zip\n", storageName.string()); return true; } diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp index 027e3ab..fdcada4 100644 --- a/tools/aapt/Resource.cpp +++ b/tools/aapt/Resource.cpp @@ -45,13 +45,6 @@ static String8 parseResourceName(const String8& leaf) } } -class ResourceTypeSet : public RefBase, - public KeyedVector<String8,sp<AaptGroup> > -{ -public: - ResourceTypeSet(); -}; - ResourceTypeSet::ResourceTypeSet() :RefBase(), KeyedVector<String8,sp<AaptGroup> >() @@ -181,7 +174,7 @@ static sp<AaptFile> getResourceFile(const sp<AaptAssets>& assets, bool makeIfNec static status_t parsePackage(const sp<AaptAssets>& assets, const sp<AaptGroup>& grp) { if (grp->getFiles().size() != 1) { - fprintf(stderr, "WARNING: Multiple AndroidManifest.xml files found, using %s\n", + fprintf(stderr, "warning: Multiple AndroidManifest.xml files found, using %s\n", grp->getFiles().valueAt(0)->getPrintableSource().string()); } @@ -279,15 +272,16 @@ static status_t preProcessImages(Bundle* bundle, const sp<AaptAssets>& assets, ResourceDirIterator it(set, String8("drawable")); Vector<sp<AaptFile> > newNameFiles; Vector<String8> newNamePaths; + bool hasErrors = false; ssize_t res; while ((res=it.next()) == NO_ERROR) { res = preProcessImage(bundle, assets, it.getFile(), NULL); - if (res != NO_ERROR) { - return res; + if (res < NO_ERROR) { + hasErrors = true; } } - return NO_ERROR; + return (hasErrors || (res < NO_ERROR)) ? UNKNOWN_ERROR : NO_ERROR; } status_t postProcessImages(const sp<AaptAssets>& assets, @@ -295,15 +289,16 @@ status_t postProcessImages(const sp<AaptAssets>& assets, const sp<ResourceTypeSet>& set) { ResourceDirIterator it(set, String8("drawable")); + bool hasErrors = false; ssize_t res; while ((res=it.next()) == NO_ERROR) { res = postProcessImage(assets, table, it.getFile()); - if (res != NO_ERROR) { - return res; + if (res < NO_ERROR) { + hasErrors = true; } } - return res < NO_ERROR ? res : (status_t)NO_ERROR; + return (hasErrors || (res < NO_ERROR)) ? UNKNOWN_ERROR : NO_ERROR; } static void collect_files(const sp<AaptDir>& dir, @@ -426,17 +421,22 @@ static void checkForIds(const String8& path, ResXMLParser& parser) if (code == ResXMLTree::START_TAG) { ssize_t index = parser.indexOfAttribute(NULL, "id"); if (index >= 0) { - fprintf(stderr, "%s:%d: WARNING: found plain 'id' attribute; did you mean the new 'android:id' name?\n", + fprintf(stderr, "%s:%d: warning: found plain 'id' attribute; did you mean the new 'android:id' name?\n", path.string(), parser.getLineNumber()); } } } } -static bool applyFileOverlay(const sp<AaptAssets>& assets, +static bool applyFileOverlay(Bundle *bundle, + const sp<AaptAssets>& assets, const sp<ResourceTypeSet>& baseSet, const char *resType) { + if (bundle->getVerbose()) { + printf("applyFileOverlay for %s\n", resType); + } + // Replace any base level files in this category with any found from the overlay // Also add any found only in the overlay. sp<AaptAssets> overlay = assets->getOverlay(); @@ -455,6 +455,9 @@ static bool applyFileOverlay(const sp<AaptAssets>& assets, // non-overlay "baseset". size_t overlayCount = overlaySet->size(); for (size_t overlayIndex=0; overlayIndex<overlayCount; overlayIndex++) { + if (bundle->getVerbose()) { + printf("trying overlaySet Key=%s\n",overlaySet->keyAt(overlayIndex).string()); + } size_t baseIndex = baseSet->indexOfKey(overlaySet->keyAt(overlayIndex)); if (baseIndex < UNKNOWN_ERROR) { // look for same flavor. For a given file (strings.xml, for example) @@ -462,30 +465,57 @@ static bool applyFileOverlay(const sp<AaptAssets>& assets, // the same flavor. sp<AaptGroup> overlayGroup = overlaySet->valueAt(overlayIndex); sp<AaptGroup> baseGroup = baseSet->valueAt(baseIndex); - - DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> > baseFiles = - baseGroup->getFiles(); - DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> > overlayFiles = + + DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> > overlayFiles = overlayGroup->getFiles(); + if (bundle->getVerbose()) { + DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> > baseFiles = + baseGroup->getFiles(); + for (size_t i=0; i < baseFiles.size(); i++) { + printf("baseFile %d has flavor %s\n", i, + baseFiles.keyAt(i).toString().string()); + } + for (size_t i=0; i < overlayFiles.size(); i++) { + printf("overlayFile %d has flavor %s\n", i, + overlayFiles.keyAt(i).toString().string()); + } + } + size_t overlayGroupSize = overlayFiles.size(); - for (size_t overlayGroupIndex = 0; - overlayGroupIndex<overlayGroupSize; + for (size_t overlayGroupIndex = 0; + overlayGroupIndex<overlayGroupSize; overlayGroupIndex++) { - size_t baseFileIndex = - baseFiles.indexOfKey(overlayFiles.keyAt(overlayGroupIndex)); + size_t baseFileIndex = + baseGroup->getFiles().indexOfKey(overlayFiles. + keyAt(overlayGroupIndex)); if(baseFileIndex < UNKNOWN_ERROR) { + if (bundle->getVerbose()) { + printf("found a match (%d) for overlay file %s, for flavor %s\n", + baseFileIndex, + overlayGroup->getLeaf().string(), + overlayFiles.keyAt(overlayGroupIndex).toString().string()); + } baseGroup->removeFile(baseFileIndex); } else { // didn't find a match fall through and add it.. } baseGroup->addFile(overlayFiles.valueAt(overlayGroupIndex)); + assets->addGroupEntry(overlayFiles.keyAt(overlayGroupIndex)); } } else { // this group doesn't exist (a file that's only in the overlay) - fprintf(stderr, "aapt: error: " - "*** Resource file '%s' exists only in an overlay\n", - overlaySet->keyAt(overlayIndex).string()); - return false; + baseSet->add(overlaySet->keyAt(overlayIndex), + overlaySet->valueAt(overlayIndex)); + // make sure all flavors are defined in the resources. + sp<AaptGroup> overlayGroup = overlaySet->valueAt(overlayIndex); + DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> > overlayFiles = + overlayGroup->getFiles(); + size_t overlayGroupSize = overlayFiles.size(); + for (size_t overlayGroupIndex = 0; + overlayGroupIndex<overlayGroupSize; + overlayGroupIndex++) { + assets->addGroupEntry(overlayFiles.keyAt(overlayGroupIndex)); + } } } // this overlay didn't have resources for this type @@ -619,13 +649,13 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) current = current->getOverlay(); } // apply the overlay files to the base set - if (!applyFileOverlay(assets, drawables, "drawable") || - !applyFileOverlay(assets, layouts, "layout") || - !applyFileOverlay(assets, anims, "anim") || - !applyFileOverlay(assets, xmls, "xml") || - !applyFileOverlay(assets, raws, "raw") || - !applyFileOverlay(assets, colors, "color") || - !applyFileOverlay(assets, menus, "menu")) { + if (!applyFileOverlay(bundle, assets, drawables, "drawable") || + !applyFileOverlay(bundle, assets, layouts, "layout") || + !applyFileOverlay(bundle, assets, anims, "anim") || + !applyFileOverlay(bundle, assets, xmls, "xml") || + !applyFileOverlay(bundle, assets, raws, "raw") || + !applyFileOverlay(bundle, assets, colors, "color") || + !applyFileOverlay(bundle, assets, menus, "menu")) { return UNKNOWN_ERROR; } @@ -1118,6 +1148,7 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) printf(" Writing public definitions to %s.\n", bundle->getPublicOutputFile()); } table.writePublicDefinitions(String16(assets->getPackage()), fp); + fclose(fp); } NOISY( @@ -1135,7 +1166,6 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) return err; } } - return err; } @@ -1234,10 +1264,16 @@ static status_t writeLayoutClasses( NA = idents.size(); + bool deprecated = false; + String16 comment = symbols->getComment(realClassName); fprintf(fp, "%s/** ", indentStr); if (comment.size() > 0) { - fprintf(fp, "%s\n", String8(comment).string()); + String8 cmt(comment); + fprintf(fp, "%s\n", cmt.string()); + if (strstr(cmt.string(), "@deprecated") != NULL) { + deprecated = true; + } } else { fprintf(fp, "Attributes that can be used with a %s.\n", nclassName.string()); } @@ -1249,10 +1285,10 @@ static status_t writeLayoutClasses( hasTable = true; fprintf(fp, "%s <p>Includes the following attributes:</p>\n" - "%s <table border=\"2\" width=\"85%%\" align=\"center\" frame=\"hsides\" rules=\"all\" cellpadding=\"5\">\n" + "%s <table>\n" "%s <colgroup align=\"left\" />\n" "%s <colgroup align=\"left\" />\n" - "%s <tr><th>Attribute<th>Summary</tr>\n", + "%s <tr><th>Attribute</th><th>Description</th></tr>\n", indentStr, indentStr, indentStr, @@ -1286,7 +1322,7 @@ static status_t writeLayoutClasses( } String16 name(name8); fixupSymbol(&name); - fprintf(fp, "%s <tr><th><code>{@link #%s_%s %s:%s}</code><td>%s</tr>\n", + fprintf(fp, "%s <tr><td><code>{@link #%s_%s %s:%s}</code></td><td>%s</td></tr>\n", indentStr, nclassName.string(), String8(name).string(), assets->getPackage().string(), @@ -1313,6 +1349,10 @@ static status_t writeLayoutClasses( } fprintf(fp, "%s */\n", getIndentSpace(indent)); + if (deprecated) { + fprintf(fp, "%s@Deprecated\n", indentStr); + } + fprintf(fp, "%spublic static final int[] %s = {\n" "%s", @@ -1361,11 +1401,17 @@ static status_t writeLayoutClasses( //printf("%s:%s/%s: 0x%08x\n", String8(package16).string(), // String8(attr16).string(), String8(name16).string(), typeSpecFlags); const bool pub = (typeSpecFlags&ResTable_typeSpec::SPEC_PUBLIC) != 0; - + + bool deprecated = false; + fprintf(fp, "%s/**\n", indentStr); if (comment.size() > 0) { + String8 cmt(comment); fprintf(fp, "%s <p>\n%s @attr description\n", indentStr, indentStr); - fprintf(fp, "%s %s\n", indentStr, String8(comment).string()); + fprintf(fp, "%s %s\n", indentStr, cmt.string()); + if (strstr(cmt.string(), "@deprecated") != NULL) { + deprecated = true; + } } else { fprintf(fp, "%s <p>This symbol is the offset where the {@link %s.R.attr#%s}\n" @@ -1377,7 +1423,11 @@ static status_t writeLayoutClasses( indentStr, nclassName.string()); } if (typeComment.size() > 0) { - fprintf(fp, "\n\n%s %s\n", indentStr, String8(typeComment).string()); + String8 cmt(typeComment); + fprintf(fp, "\n\n%s %s\n", indentStr, cmt.string()); + if (strstr(cmt.string(), "@deprecated") != NULL) { + deprecated = true; + } } if (comment.size() > 0) { if (pub) { @@ -1395,6 +1445,9 @@ static status_t writeLayoutClasses( fprintf(fp, "%s @attr name %s:%s\n", indentStr, "android", String8(name).string()); fprintf(fp, "%s*/\n", indentStr); + if (deprecated) { + fprintf(fp, "%s@Deprecated\n", indentStr); + } fprintf(fp, "%spublic static final int %s_%s = %d;\n", indentStr, nclassName.string(), @@ -1436,11 +1489,16 @@ static status_t writeSymbolClass( } String16 comment(sym.comment); bool haveComment = false; + bool deprecated = false; if (comment.size() > 0) { haveComment = true; + String8 cmt(comment); fprintf(fp, "%s/** %s\n", - getIndentSpace(indent), String8(comment).string()); + getIndentSpace(indent), cmt.string()); + if (strstr(cmt.string(), "@deprecated") != NULL) { + deprecated = true; + } } else if (sym.isPublic && !includePrivate) { sym.sourcePos.warning("No comment for public symbol %s:%s/%s", assets->getPackage().string(), className.string(), @@ -1448,20 +1506,25 @@ static status_t writeSymbolClass( } String16 typeComment(sym.typeComment); if (typeComment.size() > 0) { + String8 cmt(typeComment); if (!haveComment) { haveComment = true; fprintf(fp, - "%s/** %s\n", - getIndentSpace(indent), String8(typeComment).string()); + "%s/** %s\n", getIndentSpace(indent), cmt.string()); } else { fprintf(fp, - "%s %s\n", - getIndentSpace(indent), String8(typeComment).string()); + "%s %s\n", getIndentSpace(indent), cmt.string()); + } + if (strstr(cmt.string(), "@deprecated") != NULL) { + deprecated = true; } } if (haveComment) { fprintf(fp,"%s */\n", getIndentSpace(indent)); } + if (deprecated) { + fprintf(fp, "%s@Deprecated\n", getIndentSpace(indent)); + } fprintf(fp, "%spublic static final int %s=0x%08x;\n", getIndentSpace(indent), String8(name).string(), (int)sym.int32Val); @@ -1480,17 +1543,25 @@ static status_t writeSymbolClass( return UNKNOWN_ERROR; } String16 comment(sym.comment); + bool deprecated = false; if (comment.size() > 0) { + String8 cmt(comment); fprintf(fp, "%s/** %s\n" "%s */\n", - getIndentSpace(indent), String8(comment).string(), + getIndentSpace(indent), cmt.string(), getIndentSpace(indent)); + if (strstr(cmt.string(), "@deprecated") != NULL) { + deprecated = true; + } } else if (sym.isPublic && !includePrivate) { sym.sourcePos.warning("No comment for public symbol %s:%s/%s", assets->getPackage().string(), className.string(), String8(sym.name).string()); } + if (deprecated) { + fprintf(fp, "%s@Deprecated\n", getIndentSpace(indent)); + } fprintf(fp, "%spublic static final String %s=\"%s\";\n", getIndentSpace(indent), String8(name).string(), sym.stringVal.string()); @@ -1585,3 +1656,230 @@ status_t writeResourceSymbols(Bundle* bundle, const sp<AaptAssets>& assets, return NO_ERROR; } + + + +class ProguardKeepSet +{ +public: + // { rule --> { file locations } } + KeyedVector<String8, SortedVector<String8> > rules; + + void add(const String8& rule, const String8& where); +}; + +void ProguardKeepSet::add(const String8& rule, const String8& where) +{ + ssize_t index = rules.indexOfKey(rule); + if (index < 0) { + index = rules.add(rule, SortedVector<String8>()); + } + rules.editValueAt(index).add(where); +} + +status_t +writeProguardForAndroidManifest(ProguardKeepSet* keep, const sp<AaptAssets>& assets) +{ + status_t err; + ResXMLTree tree; + size_t len; + ResXMLTree::event_code_t code; + int depth = 0; + bool inApplication = false; + String8 error; + sp<AaptGroup> assGroup; + sp<AaptFile> assFile; + String8 pkg; + + // First, look for a package file to parse. This is required to + // be able to generate the resource information. + assGroup = assets->getFiles().valueFor(String8("AndroidManifest.xml")); + if (assGroup == NULL) { + fprintf(stderr, "ERROR: No AndroidManifest.xml file found.\n"); + return -1; + } + + if (assGroup->getFiles().size() != 1) { + fprintf(stderr, "warning: Multiple AndroidManifest.xml files found, using %s\n", + assGroup->getFiles().valueAt(0)->getPrintableSource().string()); + } + + assFile = assGroup->getFiles().valueAt(0); + + err = parseXMLResource(assFile, &tree); + if (err != NO_ERROR) { + return err; + } + + tree.restart(); + + while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { + if (code == ResXMLTree::END_TAG) { + if (/* name == "Application" && */ depth == 2) { + inApplication = false; + } + depth--; + continue; + } + if (code != ResXMLTree::START_TAG) { + continue; + } + depth++; + String8 tag(tree.getElementName(&len)); + // printf("Depth %d tag %s\n", depth, tag.string()); + if (depth == 1) { + if (tag != "manifest") { + fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n"); + return -1; + } + pkg = getAttribute(tree, NULL, "package", NULL); + } else if (depth == 2 && tag == "application") { + inApplication = true; + } + if (inApplication) { + if (tag == "application" || tag == "activity" || tag == "service" || tag == "receiver" + || tag == "provider") { + String8 name = getAttribute(tree, "http://schemas.android.com/apk/res/android", + "name", &error); + if (error != "") { + fprintf(stderr, "ERROR: %s\n", error.string()); + return -1; + } + // asdf --> package.asdf + // .asdf .a.b --> package.asdf package.a.b + // asdf.adsf --> asdf.asdf + String8 rule("-keep class "); + const char* p = name.string(); + const char* q = strchr(p, '.'); + if (p == q) { + rule += pkg; + rule += name; + } else if (q == NULL) { + rule += pkg; + rule += "."; + rule += name; + } else { + rule += name; + } + + String8 location = tag; + location += " "; + location += assFile->getSourceFile(); + char lineno[20]; + sprintf(lineno, ":%d", tree.getLineNumber()); + location += lineno; + + keep->add(rule, location); + } + } + } + + return NO_ERROR; +} + +status_t +writeProguardForLayout(ProguardKeepSet* keep, const sp<AaptFile>& layoutFile) +{ + status_t err; + ResXMLTree tree; + size_t len; + ResXMLTree::event_code_t code; + + err = parseXMLResource(layoutFile, &tree); + if (err != NO_ERROR) { + return err; + } + + tree.restart(); + + while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { + if (code != ResXMLTree::START_TAG) { + continue; + } + String8 tag(tree.getElementName(&len)); + + // If there is no '.', we'll assume that it's one of the built in names. + if (strchr(tag.string(), '.')) { + String8 rule("-keep class "); + rule += tag; + rule += " { <init>(...); }"; + + String8 location("view "); + location += layoutFile->getSourceFile(); + char lineno[20]; + sprintf(lineno, ":%d", tree.getLineNumber()); + location += lineno; + + keep->add(rule, location); + } + } + + return NO_ERROR; +} + +status_t +writeProguardForLayouts(ProguardKeepSet* keep, const sp<AaptAssets>& assets) +{ + status_t err; + sp<AaptDir> layout = assets->resDir(String8("layout")); + + if (layout != NULL) { + const KeyedVector<String8,sp<AaptGroup> > groups = layout->getFiles(); + const size_t N = groups.size(); + for (size_t i=0; i<N; i++) { + const sp<AaptGroup>& group = groups.valueAt(i); + const DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> >& files = group->getFiles(); + const size_t M = files.size(); + for (size_t j=0; j<M; j++) { + err = writeProguardForLayout(keep, files.valueAt(j)); + if (err < 0) { + return err; + } + } + } + } + return NO_ERROR; +} + +status_t +writeProguardFile(Bundle* bundle, const sp<AaptAssets>& assets) +{ + status_t err = -1; + + if (!bundle->getProguardFile()) { + return NO_ERROR; + } + + ProguardKeepSet keep; + + err = writeProguardForAndroidManifest(&keep, assets); + if (err < 0) { + return err; + } + + err = writeProguardForLayouts(&keep, assets); + if (err < 0) { + return err; + } + + FILE* fp = fopen(bundle->getProguardFile(), "w+"); + if (fp == NULL) { + fprintf(stderr, "ERROR: Unable to open class file %s: %s\n", + bundle->getProguardFile(), strerror(errno)); + return UNKNOWN_ERROR; + } + + const KeyedVector<String8, SortedVector<String8> >& rules = keep.rules; + const size_t N = rules.size(); + for (size_t i=0; i<N; i++) { + const SortedVector<String8>& locations = rules.valueAt(i); + const size_t M = locations.size(); + for (size_t j=0; j<M; j++) { + fprintf(fp, "# %s\n", locations.itemAt(j).string()); + } + fprintf(fp, "%s\n\n", rules.keyAt(i).string()); + } + fclose(fp); + + return err; +} diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp index b004664..19b9b01 100644 --- a/tools/aapt/ResourceTable.cpp +++ b/tools/aapt/ResourceTable.cpp @@ -480,22 +480,22 @@ static status_t compileAttribute(const sp<AaptFile>& in, enumOrFlagsComment.append((attr.type&ResTable_map::TYPE_ENUM) ? String16(" be one of the following constant values.") : String16(" be one or more (separated by '|') of the following constant values.")); - enumOrFlagsComment.append(String16("</p>\n<table border=\"2\" width=\"85%\" align=\"center\" frame=\"hsides\" rules=\"all\" cellpadding=\"5\">\n" + enumOrFlagsComment.append(String16("</p>\n<table>\n" "<colgroup align=\"left\" />\n" "<colgroup align=\"left\" />\n" "<colgroup align=\"left\" />\n" - "<tr><th>Constant<th>Value<th>Description</tr>")); + "<tr><th>Constant</th><th>Value</th><th>Description</th></tr>")); } - enumOrFlagsComment.append(String16("\n<tr><th><code>")); + enumOrFlagsComment.append(String16("\n<tr><td><code>")); enumOrFlagsComment.append(itemIdent); - enumOrFlagsComment.append(String16("</code><td>")); + enumOrFlagsComment.append(String16("</code></td><td>")); enumOrFlagsComment.append(value); - enumOrFlagsComment.append(String16("<td>")); + enumOrFlagsComment.append(String16("</td><td>")); if (block.getComment(&len)) { enumOrFlagsComment.append(String16(block.getComment(&len))); } - enumOrFlagsComment.append(String16("</tr>")); + enumOrFlagsComment.append(String16("</td></tr>")); err = outTable->addBag(SourcePos(in->getPrintableSource(), block.getLineNumber()), myPackage, @@ -663,6 +663,7 @@ status_t compileResourceFile(Bundle* bundle, const String16 public16("public"); const String16 public_padding16("public-padding"); const String16 private_symbols16("private-symbols"); + const String16 add_resource16("add-resource"); const String16 skip16("skip"); const String16 eat_comment16("eat-comment"); @@ -960,6 +961,36 @@ status_t compileResourceFile(Bundle* bundle, } continue; + } else if (strcmp16(block.getElementName(&len), add_resource16.string()) == 0) { + SourcePos srcPos(in->getPrintableSource(), block.getLineNumber()); + + String16 typeName; + ssize_t typeIdx = block.indexOfAttribute(NULL, "type"); + if (typeIdx < 0) { + srcPos.error("A 'type' attribute is required for <add-resource>\n"); + hasErrors = localHasErrors = true; + } + typeName = String16(block.getAttributeStringValue(typeIdx, &len)); + + String16 name; + ssize_t nameIdx = block.indexOfAttribute(NULL, "name"); + if (nameIdx < 0) { + srcPos.error("A 'name' attribute is required for <add-resource>\n"); + hasErrors = localHasErrors = true; + } + name = String16(block.getAttributeStringValue(nameIdx, &len)); + + outTable->canAddEntry(srcPos, myPackage, typeName, name); + + while ((code=block.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { + if (code == ResXMLTree::END_TAG) { + if (strcmp16(block.getElementName(&len), add_resource16.string()) == 0) { + break; + } + } + } + continue; + } else if (strcmp16(block.getElementName(&len), declare_styleable16.string()) == 0) { SourcePos srcPos(in->getPrintableSource(), block.getLineNumber()); @@ -1557,9 +1588,21 @@ status_t ResourceTable::startBag(const SourcePos& sourcePos, } #endif if (overlay && !hasBagOrEntry(package, type, name)) { - sourcePos.error("Can't add new bags in an overlay. See '%s'\n", - String8(name).string()); - return UNKNOWN_ERROR; + bool canAdd = false; + sp<Package> p = mPackages.valueFor(package); + if (p != NULL) { + sp<Type> t = p->getTypes().valueFor(type); + if (t != NULL) { + if (t->getCanAddEntries().indexOf(name) >= 0) { + canAdd = true; + } + } + } + if (!canAdd) { + sourcePos.error("Resource does not already exist in overlay at '%s'; use <add-resource> to add.\n", + String8(name).string()); + return UNKNOWN_ERROR; + } } sp<Entry> e = getEntry(package, type, name, sourcePos, overlay, params); if (e == NULL) { @@ -1724,6 +1767,15 @@ bool ResourceTable::appendTypeComment(const String16& package, return false; } +void ResourceTable::canAddEntry(const SourcePos& pos, + const String16& package, const String16& type, const String16& name) +{ + sp<Type> t = getType(package, type, pos); + if (t != NULL) { + t->canAddEntry(name); + } +} + size_t ResourceTable::size() const { return mPackages.size(); } @@ -2312,13 +2364,12 @@ ResourceTable::validateLocalizations(void) String8 region(config.string(), 2); if (configSet.find(region) == configSet.end()) { if (configSet.count(defaultLocale) == 0) { - fprintf(stdout, "aapt: error: " + fprintf(stdout, "aapt: warning: " "*** string '%s' has no default or required localization " "for '%s' in %s\n", String8(nameIter->first).string(), config.string(), mBundle->getResourceSourceDirs()[0]); - err = UNKNOWN_ERROR; } } } @@ -3215,6 +3266,11 @@ status_t ResourceTable::Type::addPublic(const SourcePos& sourcePos, return NO_ERROR; } +void ResourceTable::Type::canAddEntry(const String16& name) +{ + mCanAddEntries.add(name); +} + sp<ResourceTable::Entry> ResourceTable::Type::getEntry(const String16& entry, const SourcePos& sourcePos, const ResTable_config* config, @@ -3224,9 +3280,10 @@ sp<ResourceTable::Entry> ResourceTable::Type::getEntry(const String16& entry, int pos = -1; sp<ConfigList> c = mConfigs.valueFor(entry); if (c == NULL) { - if (overlay == true) { - sourcePos.error("Resource %s appears in overlay but not" - " in the base package.\n", String8(entry).string()); + if (overlay == true && mCanAddEntries.indexOf(entry) < 0) { + sourcePos.error("Resource at %s appears in overlay but not" + " in the base package; use <add-resource> to add.\n", + String8(entry).string()); return NULL; } c = new ConfigList(entry, sourcePos); @@ -3554,26 +3611,26 @@ sp<const ResourceTable::Entry> ResourceTable::getEntry(uint32_t resID, } if (p == NULL) { - fprintf(stderr, "WARNING: Package not found for resource #%08x\n", resID); + fprintf(stderr, "warning: Package not found for resource #%08x\n", resID); return NULL; } int tid = Res_GETTYPE(resID); if (tid < 0 || tid >= (int)p->getOrderedTypes().size()) { - fprintf(stderr, "WARNING: Type not found for resource #%08x\n", resID); + fprintf(stderr, "warning: Type not found for resource #%08x\n", resID); return NULL; } sp<Type> t = p->getOrderedTypes()[tid]; int eid = Res_GETENTRY(resID); if (eid < 0 || eid >= (int)t->getOrderedConfigs().size()) { - fprintf(stderr, "WARNING: Entry not found for resource #%08x\n", resID); + fprintf(stderr, "warning: Entry not found for resource #%08x\n", resID); return NULL; } sp<ConfigList> c = t->getOrderedConfigs()[eid]; if (c == NULL) { - fprintf(stderr, "WARNING: Entry not found for resource #%08x\n", resID); + fprintf(stderr, "warning: Entry not found for resource #%08x\n", resID); return NULL; } @@ -3581,7 +3638,7 @@ sp<const ResourceTable::Entry> ResourceTable::getEntry(uint32_t resID, if (config) cdesc = *config; sp<Entry> e = c->getEntries().valueFor(cdesc); if (c == NULL) { - fprintf(stderr, "WARNING: Entry configuration not found for resource #%08x\n", resID); + fprintf(stderr, "warning: Entry configuration not found for resource #%08x\n", resID); return NULL; } @@ -3599,7 +3656,7 @@ const ResourceTable::Item* ResourceTable::getItem(uint32_t resID, uint32_t attrI for (size_t i=0; i<N; i++) { const Item& it = e->getBag().valueAt(i); if (it.bagKeyId == 0) { - fprintf(stderr, "WARNING: ID not yet assigned to '%s' in bag '%s'\n", + fprintf(stderr, "warning: ID not yet assigned to '%s' in bag '%s'\n", String8(e->getName()).string(), String8(e->getBag().keyAt(i)).string()); } @@ -3627,7 +3684,7 @@ bool ResourceTable::getItemValue( break; } } - fprintf(stderr, "WARNING: Circular reference detected in key '%s' of bag '%s'\n", + fprintf(stderr, "warning: Circular reference detected in key '%s' of bag '%s'\n", String8(e->getName()).string(), String8(e->getBag().keyAt(i)).string()); return false; diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h index ec4331a..caa01b3 100644 --- a/tools/aapt/ResourceTable.h +++ b/tools/aapt/ResourceTable.h @@ -132,6 +132,9 @@ public: const String16& name, const String16& comment); + void canAddEntry(const SourcePos& pos, + const String16& package, const String16& type, const String16& name); + size_t size() const; size_t numLocalResources() const; bool hasResources() const; @@ -413,7 +416,9 @@ public: status_t addPublic(const SourcePos& pos, const String16& name, const uint32_t ident); - + + void canAddEntry(const String16& name); + String16 getName() const { return mName; } sp<Entry> getEntry(const String16& entry, const SourcePos& pos, @@ -435,6 +440,8 @@ public: const DefaultKeyedVector<String16, sp<ConfigList> >& getConfigs() const { return mConfigs; } const Vector<sp<ConfigList> >& getOrderedConfigs() const { return mOrderedConfigs; } + const SortedVector<String16>& getCanAddEntries() const { return mCanAddEntries; } + const SourcePos& getPos() const { return mPos; } private: String16 mName; @@ -443,6 +450,7 @@ public: SortedVector<ConfigDescription> mUniqueConfigs; DefaultKeyedVector<String16, sp<ConfigList> > mConfigs; Vector<sp<ConfigList> > mOrderedConfigs; + SortedVector<String16> mCanAddEntries; int32_t mPublicIndex; int32_t mIndex; SourcePos mPos; diff --git a/tools/aapt/SourcePos.cpp b/tools/aapt/SourcePos.cpp index 2761d18..e2a921c 100644 --- a/tools/aapt/SourcePos.cpp +++ b/tools/aapt/SourcePos.cpp @@ -86,7 +86,7 @@ ErrorPos::operator=(const ErrorPos& rhs) void ErrorPos::print(FILE* to) const { - const char* type = fatal ? "ERROR" : "WARNING"; + const char* type = fatal ? "error:" : "warning:"; if (this->line >= 0) { fprintf(to, "%s:%d: %s %s\n", this->file.string(), this->line, type, this->error.string()); diff --git a/tools/aapt/XMLNode.cpp b/tools/aapt/XMLNode.cpp index 2a85bc7..d4d2a45 100644 --- a/tools/aapt/XMLNode.cpp +++ b/tools/aapt/XMLNode.cpp @@ -219,8 +219,13 @@ moveon: } spanStack.pop(); - if (empty) { - fprintf(stderr, "%s:%d: WARNING: empty '%s' span found in text '%s'\n", + /* + * This warning seems to be just an irritation to most people, + * since it is typically introduced by translators who then never + * see the warning. + */ + if (0 && empty) { + fprintf(stderr, "%s:%d: warning: empty '%s' span found in text '%s'\n", fileName, inXml->getLineNumber(), String8(spanTag).string(), String8(*outString).string()); @@ -486,6 +491,7 @@ XMLNode::XMLNode(const String8& filename, const String16& s1, const String16& s2 XMLNode::XMLNode(const String8& filename) : mFilename(filename) { + memset(&mCharsValue, 0, sizeof(mCharsValue)); } XMLNode::type XMLNode::getType() const diff --git a/tools/aapt/ZipEntry.cpp b/tools/aapt/ZipEntry.cpp new file mode 100644 index 0000000..a0b54c2 --- /dev/null +++ b/tools/aapt/ZipEntry.cpp @@ -0,0 +1,696 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// +// Access to entries in a Zip archive. +// + +#define LOG_TAG "zip" + +#include "ZipEntry.h" +#include <utils/Log.h> + +#include <stdio.h> +#include <string.h> +#include <assert.h> + +using namespace android; + +/* + * Initialize a new ZipEntry structure from a FILE* positioned at a + * CentralDirectoryEntry. + * + * On exit, the file pointer will be at the start of the next CDE or + * at the EOCD. + */ +status_t ZipEntry::initFromCDE(FILE* fp) +{ + status_t result; + long posn; + bool hasDD; + + //LOGV("initFromCDE ---\n"); + + /* read the CDE */ + result = mCDE.read(fp); + if (result != NO_ERROR) { + LOGD("mCDE.read failed\n"); + return result; + } + + //mCDE.dump(); + + /* using the info in the CDE, go load up the LFH */ + posn = ftell(fp); + if (fseek(fp, mCDE.mLocalHeaderRelOffset, SEEK_SET) != 0) { + LOGD("local header seek failed (%ld)\n", + mCDE.mLocalHeaderRelOffset); + return UNKNOWN_ERROR; + } + + result = mLFH.read(fp); + if (result != NO_ERROR) { + LOGD("mLFH.read failed\n"); + return result; + } + + if (fseek(fp, posn, SEEK_SET) != 0) + return UNKNOWN_ERROR; + + //mLFH.dump(); + + /* + * We *might* need to read the Data Descriptor at this point and + * integrate it into the LFH. If this bit is set, the CRC-32, + * compressed size, and uncompressed size will be zero. In practice + * these seem to be rare. + */ + hasDD = (mLFH.mGPBitFlag & kUsesDataDescr) != 0; + if (hasDD) { + // do something clever + //LOGD("+++ has data descriptor\n"); + } + + /* + * Sanity-check the LFH. Note that this will fail if the "kUsesDataDescr" + * flag is set, because the LFH is incomplete. (Not a problem, since we + * prefer the CDE values.) + */ + if (!hasDD && !compareHeaders()) { + LOGW("warning: header mismatch\n"); + // keep going? + } + + /* + * If the mVersionToExtract is greater than 20, we may have an + * issue unpacking the record -- could be encrypted, compressed + * with something we don't support, or use Zip64 extensions. We + * can defer worrying about that to when we're extracting data. + */ + + return NO_ERROR; +} + +/* + * Initialize a new entry. Pass in the file name and an optional comment. + * + * Initializes the CDE and the LFH. + */ +void ZipEntry::initNew(const char* fileName, const char* comment) +{ + assert(fileName != NULL && *fileName != '\0'); // name required + + /* most fields are properly initialized by constructor */ + mCDE.mVersionMadeBy = kDefaultMadeBy; + mCDE.mVersionToExtract = kDefaultVersion; + mCDE.mCompressionMethod = kCompressStored; + mCDE.mFileNameLength = strlen(fileName); + if (comment != NULL) + mCDE.mFileCommentLength = strlen(comment); + mCDE.mExternalAttrs = 0x81b60020; // matches what WinZip does + + if (mCDE.mFileNameLength > 0) { + mCDE.mFileName = new unsigned char[mCDE.mFileNameLength+1]; + strcpy((char*) mCDE.mFileName, fileName); + } + if (mCDE.mFileCommentLength > 0) { + /* TODO: stop assuming null-terminated ASCII here? */ + mCDE.mFileComment = new unsigned char[mCDE.mFileCommentLength+1]; + strcpy((char*) mCDE.mFileComment, comment); + } + + copyCDEtoLFH(); +} + +/* + * Initialize a new entry, starting with the ZipEntry from a different + * archive. + * + * Initializes the CDE and the LFH. + */ +status_t ZipEntry::initFromExternal(const ZipFile* pZipFile, + const ZipEntry* pEntry) +{ + /* + * Copy everything in the CDE over, then fix up the hairy bits. + */ + memcpy(&mCDE, &pEntry->mCDE, sizeof(mCDE)); + + if (mCDE.mFileNameLength > 0) { + mCDE.mFileName = new unsigned char[mCDE.mFileNameLength+1]; + if (mCDE.mFileName == NULL) + return NO_MEMORY; + strcpy((char*) mCDE.mFileName, (char*)pEntry->mCDE.mFileName); + } + if (mCDE.mFileCommentLength > 0) { + mCDE.mFileComment = new unsigned char[mCDE.mFileCommentLength+1]; + if (mCDE.mFileComment == NULL) + return NO_MEMORY; + strcpy((char*) mCDE.mFileComment, (char*)pEntry->mCDE.mFileComment); + } + if (mCDE.mExtraFieldLength > 0) { + /* we null-terminate this, though it may not be a string */ + mCDE.mExtraField = new unsigned char[mCDE.mExtraFieldLength+1]; + if (mCDE.mExtraField == NULL) + return NO_MEMORY; + memcpy(mCDE.mExtraField, pEntry->mCDE.mExtraField, + mCDE.mExtraFieldLength+1); + } + + /* construct the LFH from the CDE */ + copyCDEtoLFH(); + + /* + * The LFH "extra" field is independent of the CDE "extra", so we + * handle it here. + */ + assert(mLFH.mExtraField == NULL); + mLFH.mExtraFieldLength = pEntry->mLFH.mExtraFieldLength; + if (mLFH.mExtraFieldLength > 0) { + mLFH.mExtraField = new unsigned char[mLFH.mExtraFieldLength+1]; + if (mLFH.mExtraField == NULL) + return NO_MEMORY; + memcpy(mLFH.mExtraField, pEntry->mLFH.mExtraField, + mLFH.mExtraFieldLength+1); + } + + return NO_ERROR; +} + +/* + * Insert pad bytes in the LFH by tweaking the "extra" field. This will + * potentially confuse something that put "extra" data in here earlier, + * but I can't find an actual problem. + */ +status_t ZipEntry::addPadding(int padding) +{ + if (padding <= 0) + return INVALID_OPERATION; + + //LOGI("HEY: adding %d pad bytes to existing %d in %s\n", + // padding, mLFH.mExtraFieldLength, mCDE.mFileName); + + if (mLFH.mExtraFieldLength > 0) { + /* extend existing field */ + unsigned char* newExtra; + + newExtra = new unsigned char[mLFH.mExtraFieldLength + padding]; + if (newExtra == NULL) + return NO_MEMORY; + memset(newExtra + mLFH.mExtraFieldLength, 0, padding); + memcpy(newExtra, mLFH.mExtraField, mLFH.mExtraFieldLength); + + delete[] mLFH.mExtraField; + mLFH.mExtraField = newExtra; + mLFH.mExtraFieldLength += padding; + } else { + /* create new field */ + mLFH.mExtraField = new unsigned char[padding]; + memset(mLFH.mExtraField, 0, padding); + mLFH.mExtraFieldLength = padding; + } + + return NO_ERROR; +} + +/* + * Set the fields in the LFH equal to the corresponding fields in the CDE. + * + * This does not touch the LFH "extra" field. + */ +void ZipEntry::copyCDEtoLFH(void) +{ + mLFH.mVersionToExtract = mCDE.mVersionToExtract; + mLFH.mGPBitFlag = mCDE.mGPBitFlag; + mLFH.mCompressionMethod = mCDE.mCompressionMethod; + mLFH.mLastModFileTime = mCDE.mLastModFileTime; + mLFH.mLastModFileDate = mCDE.mLastModFileDate; + mLFH.mCRC32 = mCDE.mCRC32; + mLFH.mCompressedSize = mCDE.mCompressedSize; + mLFH.mUncompressedSize = mCDE.mUncompressedSize; + mLFH.mFileNameLength = mCDE.mFileNameLength; + // the "extra field" is independent + + delete[] mLFH.mFileName; + if (mLFH.mFileNameLength > 0) { + mLFH.mFileName = new unsigned char[mLFH.mFileNameLength+1]; + strcpy((char*) mLFH.mFileName, (const char*) mCDE.mFileName); + } else { + mLFH.mFileName = NULL; + } +} + +/* + * Set some information about a file after we add it. + */ +void ZipEntry::setDataInfo(long uncompLen, long compLen, unsigned long crc32, + int compressionMethod) +{ + mCDE.mCompressionMethod = compressionMethod; + mCDE.mCRC32 = crc32; + mCDE.mCompressedSize = compLen; + mCDE.mUncompressedSize = uncompLen; + mCDE.mCompressionMethod = compressionMethod; + if (compressionMethod == kCompressDeflated) { + mCDE.mGPBitFlag |= 0x0002; // indicates maximum compression used + } + copyCDEtoLFH(); +} + +/* + * See if the data in mCDE and mLFH match up. This is mostly useful for + * debugging these classes, but it can be used to identify damaged + * archives. + * + * Returns "false" if they differ. + */ +bool ZipEntry::compareHeaders(void) const +{ + if (mCDE.mVersionToExtract != mLFH.mVersionToExtract) { + LOGV("cmp: VersionToExtract\n"); + return false; + } + if (mCDE.mGPBitFlag != mLFH.mGPBitFlag) { + LOGV("cmp: GPBitFlag\n"); + return false; + } + if (mCDE.mCompressionMethod != mLFH.mCompressionMethod) { + LOGV("cmp: CompressionMethod\n"); + return false; + } + if (mCDE.mLastModFileTime != mLFH.mLastModFileTime) { + LOGV("cmp: LastModFileTime\n"); + return false; + } + if (mCDE.mLastModFileDate != mLFH.mLastModFileDate) { + LOGV("cmp: LastModFileDate\n"); + return false; + } + if (mCDE.mCRC32 != mLFH.mCRC32) { + LOGV("cmp: CRC32\n"); + return false; + } + if (mCDE.mCompressedSize != mLFH.mCompressedSize) { + LOGV("cmp: CompressedSize\n"); + return false; + } + if (mCDE.mUncompressedSize != mLFH.mUncompressedSize) { + LOGV("cmp: UncompressedSize\n"); + return false; + } + if (mCDE.mFileNameLength != mLFH.mFileNameLength) { + LOGV("cmp: FileNameLength\n"); + return false; + } +#if 0 // this seems to be used for padding, not real data + if (mCDE.mExtraFieldLength != mLFH.mExtraFieldLength) { + LOGV("cmp: ExtraFieldLength\n"); + return false; + } +#endif + if (mCDE.mFileName != NULL) { + if (strcmp((char*) mCDE.mFileName, (char*) mLFH.mFileName) != 0) { + LOGV("cmp: FileName\n"); + return false; + } + } + + return true; +} + + +/* + * Convert the DOS date/time stamp into a UNIX time stamp. + */ +time_t ZipEntry::getModWhen(void) const +{ + struct tm parts; + + parts.tm_sec = (mCDE.mLastModFileTime & 0x001f) << 1; + parts.tm_min = (mCDE.mLastModFileTime & 0x07e0) >> 5; + parts.tm_hour = (mCDE.mLastModFileTime & 0xf800) >> 11; + parts.tm_mday = (mCDE.mLastModFileDate & 0x001f); + parts.tm_mon = ((mCDE.mLastModFileDate & 0x01e0) >> 5) -1; + parts.tm_year = ((mCDE.mLastModFileDate & 0xfe00) >> 9) + 80; + parts.tm_wday = parts.tm_yday = 0; + parts.tm_isdst = -1; // DST info "not available" + + return mktime(&parts); +} + +/* + * Set the CDE/LFH timestamp from UNIX time. + */ +void ZipEntry::setModWhen(time_t when) +{ +#ifdef HAVE_LOCALTIME_R + struct tm tmResult; +#endif + time_t even; + unsigned short zdate, ztime; + + struct tm* ptm; + + /* round up to an even number of seconds */ + even = (time_t)(((unsigned long)(when) + 1) & (~1)); + + /* expand */ +#ifdef HAVE_LOCALTIME_R + ptm = localtime_r(&even, &tmResult); +#else + ptm = localtime(&even); +#endif + + int year; + year = ptm->tm_year; + if (year < 80) + year = 80; + + zdate = (year - 80) << 9 | (ptm->tm_mon+1) << 5 | ptm->tm_mday; + ztime = ptm->tm_hour << 11 | ptm->tm_min << 5 | ptm->tm_sec >> 1; + + mCDE.mLastModFileTime = mLFH.mLastModFileTime = ztime; + mCDE.mLastModFileDate = mLFH.mLastModFileDate = zdate; +} + + +/* + * =========================================================================== + * ZipEntry::LocalFileHeader + * =========================================================================== + */ + +/* + * Read a local file header. + * + * On entry, "fp" points to the signature at the start of the header. + * On exit, "fp" points to the start of data. + */ +status_t ZipEntry::LocalFileHeader::read(FILE* fp) +{ + status_t result = NO_ERROR; + unsigned char buf[kLFHLen]; + + assert(mFileName == NULL); + assert(mExtraField == NULL); + + if (fread(buf, 1, kLFHLen, fp) != kLFHLen) { + result = UNKNOWN_ERROR; + goto bail; + } + + if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) { + LOGD("whoops: didn't find expected signature\n"); + result = UNKNOWN_ERROR; + goto bail; + } + + mVersionToExtract = ZipEntry::getShortLE(&buf[0x04]); + mGPBitFlag = ZipEntry::getShortLE(&buf[0x06]); + mCompressionMethod = ZipEntry::getShortLE(&buf[0x08]); + mLastModFileTime = ZipEntry::getShortLE(&buf[0x0a]); + mLastModFileDate = ZipEntry::getShortLE(&buf[0x0c]); + mCRC32 = ZipEntry::getLongLE(&buf[0x0e]); + mCompressedSize = ZipEntry::getLongLE(&buf[0x12]); + mUncompressedSize = ZipEntry::getLongLE(&buf[0x16]); + mFileNameLength = ZipEntry::getShortLE(&buf[0x1a]); + mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1c]); + + // TODO: validate sizes + + /* grab filename */ + if (mFileNameLength != 0) { + mFileName = new unsigned char[mFileNameLength+1]; + if (mFileName == NULL) { + result = NO_MEMORY; + goto bail; + } + if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) { + result = UNKNOWN_ERROR; + goto bail; + } + mFileName[mFileNameLength] = '\0'; + } + + /* grab extra field */ + if (mExtraFieldLength != 0) { + mExtraField = new unsigned char[mExtraFieldLength+1]; + if (mExtraField == NULL) { + result = NO_MEMORY; + goto bail; + } + if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) { + result = UNKNOWN_ERROR; + goto bail; + } + mExtraField[mExtraFieldLength] = '\0'; + } + +bail: + return result; +} + +/* + * Write a local file header. + */ +status_t ZipEntry::LocalFileHeader::write(FILE* fp) +{ + unsigned char buf[kLFHLen]; + + ZipEntry::putLongLE(&buf[0x00], kSignature); + ZipEntry::putShortLE(&buf[0x04], mVersionToExtract); + ZipEntry::putShortLE(&buf[0x06], mGPBitFlag); + ZipEntry::putShortLE(&buf[0x08], mCompressionMethod); + ZipEntry::putShortLE(&buf[0x0a], mLastModFileTime); + ZipEntry::putShortLE(&buf[0x0c], mLastModFileDate); + ZipEntry::putLongLE(&buf[0x0e], mCRC32); + ZipEntry::putLongLE(&buf[0x12], mCompressedSize); + ZipEntry::putLongLE(&buf[0x16], mUncompressedSize); + ZipEntry::putShortLE(&buf[0x1a], mFileNameLength); + ZipEntry::putShortLE(&buf[0x1c], mExtraFieldLength); + + if (fwrite(buf, 1, kLFHLen, fp) != kLFHLen) + return UNKNOWN_ERROR; + + /* write filename */ + if (mFileNameLength != 0) { + if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength) + return UNKNOWN_ERROR; + } + + /* write "extra field" */ + if (mExtraFieldLength != 0) { + if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) + return UNKNOWN_ERROR; + } + + return NO_ERROR; +} + + +/* + * Dump the contents of a LocalFileHeader object. + */ +void ZipEntry::LocalFileHeader::dump(void) const +{ + LOGD(" LocalFileHeader contents:\n"); + LOGD(" versToExt=%u gpBits=0x%04x compression=%u\n", + mVersionToExtract, mGPBitFlag, mCompressionMethod); + LOGD(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n", + mLastModFileTime, mLastModFileDate, mCRC32); + LOGD(" compressedSize=%lu uncompressedSize=%lu\n", + mCompressedSize, mUncompressedSize); + LOGD(" filenameLen=%u extraLen=%u\n", + mFileNameLength, mExtraFieldLength); + if (mFileName != NULL) + LOGD(" filename: '%s'\n", mFileName); +} + + +/* + * =========================================================================== + * ZipEntry::CentralDirEntry + * =========================================================================== + */ + +/* + * Read the central dir entry that appears next in the file. + * + * On entry, "fp" should be positioned on the signature bytes for the + * entry. On exit, "fp" will point at the signature word for the next + * entry or for the EOCD. + */ +status_t ZipEntry::CentralDirEntry::read(FILE* fp) +{ + status_t result = NO_ERROR; + unsigned char buf[kCDELen]; + + /* no re-use */ + assert(mFileName == NULL); + assert(mExtraField == NULL); + assert(mFileComment == NULL); + + if (fread(buf, 1, kCDELen, fp) != kCDELen) { + result = UNKNOWN_ERROR; + goto bail; + } + + if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) { + LOGD("Whoops: didn't find expected signature\n"); + result = UNKNOWN_ERROR; + goto bail; + } + + mVersionMadeBy = ZipEntry::getShortLE(&buf[0x04]); + mVersionToExtract = ZipEntry::getShortLE(&buf[0x06]); + mGPBitFlag = ZipEntry::getShortLE(&buf[0x08]); + mCompressionMethod = ZipEntry::getShortLE(&buf[0x0a]); + mLastModFileTime = ZipEntry::getShortLE(&buf[0x0c]); + mLastModFileDate = ZipEntry::getShortLE(&buf[0x0e]); + mCRC32 = ZipEntry::getLongLE(&buf[0x10]); + mCompressedSize = ZipEntry::getLongLE(&buf[0x14]); + mUncompressedSize = ZipEntry::getLongLE(&buf[0x18]); + mFileNameLength = ZipEntry::getShortLE(&buf[0x1c]); + mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1e]); + mFileCommentLength = ZipEntry::getShortLE(&buf[0x20]); + mDiskNumberStart = ZipEntry::getShortLE(&buf[0x22]); + mInternalAttrs = ZipEntry::getShortLE(&buf[0x24]); + mExternalAttrs = ZipEntry::getLongLE(&buf[0x26]); + mLocalHeaderRelOffset = ZipEntry::getLongLE(&buf[0x2a]); + + // TODO: validate sizes and offsets + + /* grab filename */ + if (mFileNameLength != 0) { + mFileName = new unsigned char[mFileNameLength+1]; + if (mFileName == NULL) { + result = NO_MEMORY; + goto bail; + } + if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) { + result = UNKNOWN_ERROR; + goto bail; + } + mFileName[mFileNameLength] = '\0'; + } + + /* read "extra field" */ + if (mExtraFieldLength != 0) { + mExtraField = new unsigned char[mExtraFieldLength+1]; + if (mExtraField == NULL) { + result = NO_MEMORY; + goto bail; + } + if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) { + result = UNKNOWN_ERROR; + goto bail; + } + mExtraField[mExtraFieldLength] = '\0'; + } + + + /* grab comment, if any */ + if (mFileCommentLength != 0) { + mFileComment = new unsigned char[mFileCommentLength+1]; + if (mFileComment == NULL) { + result = NO_MEMORY; + goto bail; + } + if (fread(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength) + { + result = UNKNOWN_ERROR; + goto bail; + } + mFileComment[mFileCommentLength] = '\0'; + } + +bail: + return result; +} + +/* + * Write a central dir entry. + */ +status_t ZipEntry::CentralDirEntry::write(FILE* fp) +{ + unsigned char buf[kCDELen]; + + ZipEntry::putLongLE(&buf[0x00], kSignature); + ZipEntry::putShortLE(&buf[0x04], mVersionMadeBy); + ZipEntry::putShortLE(&buf[0x06], mVersionToExtract); + ZipEntry::putShortLE(&buf[0x08], mGPBitFlag); + ZipEntry::putShortLE(&buf[0x0a], mCompressionMethod); + ZipEntry::putShortLE(&buf[0x0c], mLastModFileTime); + ZipEntry::putShortLE(&buf[0x0e], mLastModFileDate); + ZipEntry::putLongLE(&buf[0x10], mCRC32); + ZipEntry::putLongLE(&buf[0x14], mCompressedSize); + ZipEntry::putLongLE(&buf[0x18], mUncompressedSize); + ZipEntry::putShortLE(&buf[0x1c], mFileNameLength); + ZipEntry::putShortLE(&buf[0x1e], mExtraFieldLength); + ZipEntry::putShortLE(&buf[0x20], mFileCommentLength); + ZipEntry::putShortLE(&buf[0x22], mDiskNumberStart); + ZipEntry::putShortLE(&buf[0x24], mInternalAttrs); + ZipEntry::putLongLE(&buf[0x26], mExternalAttrs); + ZipEntry::putLongLE(&buf[0x2a], mLocalHeaderRelOffset); + + if (fwrite(buf, 1, kCDELen, fp) != kCDELen) + return UNKNOWN_ERROR; + + /* write filename */ + if (mFileNameLength != 0) { + if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength) + return UNKNOWN_ERROR; + } + + /* write "extra field" */ + if (mExtraFieldLength != 0) { + if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) + return UNKNOWN_ERROR; + } + + /* write comment */ + if (mFileCommentLength != 0) { + if (fwrite(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength) + return UNKNOWN_ERROR; + } + + return NO_ERROR; +} + +/* + * Dump the contents of a CentralDirEntry object. + */ +void ZipEntry::CentralDirEntry::dump(void) const +{ + LOGD(" CentralDirEntry contents:\n"); + LOGD(" versMadeBy=%u versToExt=%u gpBits=0x%04x compression=%u\n", + mVersionMadeBy, mVersionToExtract, mGPBitFlag, mCompressionMethod); + LOGD(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n", + mLastModFileTime, mLastModFileDate, mCRC32); + LOGD(" compressedSize=%lu uncompressedSize=%lu\n", + mCompressedSize, mUncompressedSize); + LOGD(" filenameLen=%u extraLen=%u commentLen=%u\n", + mFileNameLength, mExtraFieldLength, mFileCommentLength); + LOGD(" diskNumStart=%u intAttr=0x%04x extAttr=0x%08lx relOffset=%lu\n", + mDiskNumberStart, mInternalAttrs, mExternalAttrs, + mLocalHeaderRelOffset); + + if (mFileName != NULL) + LOGD(" filename: '%s'\n", mFileName); + if (mFileComment != NULL) + LOGD(" comment: '%s'\n", mFileComment); +} + diff --git a/tools/aapt/ZipEntry.h b/tools/aapt/ZipEntry.h new file mode 100644 index 0000000..7f721b4 --- /dev/null +++ b/tools/aapt/ZipEntry.h @@ -0,0 +1,345 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// +// Zip archive entries. +// +// The ZipEntry class is tightly meshed with the ZipFile class. +// +#ifndef __LIBS_ZIPENTRY_H +#define __LIBS_ZIPENTRY_H + +#include <utils/Errors.h> + +#include <stdlib.h> +#include <stdio.h> + +namespace android { + +class ZipFile; + +/* + * ZipEntry objects represent a single entry in a Zip archive. + * + * You can use one of these to get or set information about an entry, but + * there are no functions here for accessing the data itself. (We could + * tuck a pointer to the ZipFile in here for convenience, but that raises + * the likelihood of using ZipEntry objects after discarding the ZipFile.) + * + * File information is stored in two places: next to the file data (the Local + * File Header, and possibly a Data Descriptor), and at the end of the file + * (the Central Directory Entry). The two must be kept in sync. + */ +class ZipEntry { +public: + friend class ZipFile; + + ZipEntry(void) + : mDeleted(false), mMarked(false) + {} + ~ZipEntry(void) {} + + /* + * Returns "true" if the data is compressed. + */ + bool isCompressed(void) const { + return mCDE.mCompressionMethod != kCompressStored; + } + int getCompressionMethod(void) const { return mCDE.mCompressionMethod; } + + /* + * Return the uncompressed length. + */ + off_t getUncompressedLen(void) const { return mCDE.mUncompressedSize; } + + /* + * Return the compressed length. For uncompressed data, this returns + * the same thing as getUncompresesdLen(). + */ + off_t getCompressedLen(void) const { return mCDE.mCompressedSize; } + + /* + * Return the absolute file offset of the start of the compressed or + * uncompressed data. + */ + off_t getFileOffset(void) const { + return mCDE.mLocalHeaderRelOffset + + LocalFileHeader::kLFHLen + + mLFH.mFileNameLength + + mLFH.mExtraFieldLength; + } + + /* + * Return the data CRC. + */ + unsigned long getCRC32(void) const { return mCDE.mCRC32; } + + /* + * Return file modification time in UNIX seconds-since-epoch. + */ + time_t getModWhen(void) const; + + /* + * Return the archived file name. + */ + const char* getFileName(void) const { return (const char*) mCDE.mFileName; } + + /* + * Application-defined "mark". Can be useful when synchronizing the + * contents of an archive with contents on disk. + */ + bool getMarked(void) const { return mMarked; } + void setMarked(bool val) { mMarked = val; } + + /* + * Some basic functions for raw data manipulation. "LE" means + * Little Endian. + */ + static inline unsigned short getShortLE(const unsigned char* buf) { + return buf[0] | (buf[1] << 8); + } + static inline unsigned long getLongLE(const unsigned char* buf) { + return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); + } + static inline void putShortLE(unsigned char* buf, short val) { + buf[0] = (unsigned char) val; + buf[1] = (unsigned char) (val >> 8); + } + static inline void putLongLE(unsigned char* buf, long val) { + buf[0] = (unsigned char) val; + buf[1] = (unsigned char) (val >> 8); + buf[2] = (unsigned char) (val >> 16); + buf[3] = (unsigned char) (val >> 24); + } + + /* defined for Zip archives */ + enum { + kCompressStored = 0, // no compression + // shrunk = 1, + // reduced 1 = 2, + // reduced 2 = 3, + // reduced 3 = 4, + // reduced 4 = 5, + // imploded = 6, + // tokenized = 7, + kCompressDeflated = 8, // standard deflate + // Deflate64 = 9, + // lib imploded = 10, + // reserved = 11, + // bzip2 = 12, + }; + + /* + * Deletion flag. If set, the entry will be removed on the next + * call to "flush". + */ + bool getDeleted(void) const { return mDeleted; } + +protected: + /* + * Initialize the structure from the file, which is pointing at + * our Central Directory entry. + */ + status_t initFromCDE(FILE* fp); + + /* + * Initialize the structure for a new file. We need the filename + * and comment so that we can properly size the LFH area. The + * filename is mandatory, the comment is optional. + */ + void initNew(const char* fileName, const char* comment); + + /* + * Initialize the structure with the contents of a ZipEntry from + * another file. + */ + status_t initFromExternal(const ZipFile* pZipFile, const ZipEntry* pEntry); + + /* + * Add some pad bytes to the LFH. We do this by adding or resizing + * the "extra" field. + */ + status_t addPadding(int padding); + + /* + * Set information about the data for this entry. + */ + void setDataInfo(long uncompLen, long compLen, unsigned long crc32, + int compressionMethod); + + /* + * Set the modification date. + */ + void setModWhen(time_t when); + + /* + * Return the offset of the local file header. + */ + off_t getLFHOffset(void) const { return mCDE.mLocalHeaderRelOffset; } + + /* + * Set the offset of the local file header, relative to the start of + * the current file. + */ + void setLFHOffset(off_t offset) { + mCDE.mLocalHeaderRelOffset = (long) offset; + } + + /* mark for deletion; used by ZipFile::remove() */ + void setDeleted(void) { mDeleted = true; } + +private: + /* these are private and not defined */ + ZipEntry(const ZipEntry& src); + ZipEntry& operator=(const ZipEntry& src); + + /* returns "true" if the CDE and the LFH agree */ + bool compareHeaders(void) const; + void copyCDEtoLFH(void); + + bool mDeleted; // set if entry is pending deletion + bool mMarked; // app-defined marker + + /* + * Every entry in the Zip archive starts off with one of these. + */ + class LocalFileHeader { + public: + LocalFileHeader(void) : + mVersionToExtract(0), + mGPBitFlag(0), + mCompressionMethod(0), + mLastModFileTime(0), + mLastModFileDate(0), + mCRC32(0), + mCompressedSize(0), + mUncompressedSize(0), + mFileNameLength(0), + mExtraFieldLength(0), + mFileName(NULL), + mExtraField(NULL) + {} + virtual ~LocalFileHeader(void) { + delete[] mFileName; + delete[] mExtraField; + } + + status_t read(FILE* fp); + status_t write(FILE* fp); + + // unsigned long mSignature; + unsigned short mVersionToExtract; + unsigned short mGPBitFlag; + unsigned short mCompressionMethod; + unsigned short mLastModFileTime; + unsigned short mLastModFileDate; + unsigned long mCRC32; + unsigned long mCompressedSize; + unsigned long mUncompressedSize; + unsigned short mFileNameLength; + unsigned short mExtraFieldLength; + unsigned char* mFileName; + unsigned char* mExtraField; + + enum { + kSignature = 0x04034b50, + kLFHLen = 30, // LocalFileHdr len, excl. var fields + }; + + void dump(void) const; + }; + + /* + * Every entry in the Zip archive has one of these in the "central + * directory" at the end of the file. + */ + class CentralDirEntry { + public: + CentralDirEntry(void) : + mVersionMadeBy(0), + mVersionToExtract(0), + mGPBitFlag(0), + mCompressionMethod(0), + mLastModFileTime(0), + mLastModFileDate(0), + mCRC32(0), + mCompressedSize(0), + mUncompressedSize(0), + mFileNameLength(0), + mExtraFieldLength(0), + mFileCommentLength(0), + mDiskNumberStart(0), + mInternalAttrs(0), + mExternalAttrs(0), + mLocalHeaderRelOffset(0), + mFileName(NULL), + mExtraField(NULL), + mFileComment(NULL) + {} + virtual ~CentralDirEntry(void) { + delete[] mFileName; + delete[] mExtraField; + delete[] mFileComment; + } + + status_t read(FILE* fp); + status_t write(FILE* fp); + + // unsigned long mSignature; + unsigned short mVersionMadeBy; + unsigned short mVersionToExtract; + unsigned short mGPBitFlag; + unsigned short mCompressionMethod; + unsigned short mLastModFileTime; + unsigned short mLastModFileDate; + unsigned long mCRC32; + unsigned long mCompressedSize; + unsigned long mUncompressedSize; + unsigned short mFileNameLength; + unsigned short mExtraFieldLength; + unsigned short mFileCommentLength; + unsigned short mDiskNumberStart; + unsigned short mInternalAttrs; + unsigned long mExternalAttrs; + unsigned long mLocalHeaderRelOffset; + unsigned char* mFileName; + unsigned char* mExtraField; + unsigned char* mFileComment; + + void dump(void) const; + + enum { + kSignature = 0x02014b50, + kCDELen = 46, // CentralDirEnt len, excl. var fields + }; + }; + + enum { + //kDataDescriptorSignature = 0x08074b50, // currently unused + kDataDescriptorLen = 16, // four 32-bit fields + + kDefaultVersion = 20, // need deflate, nothing much else + kDefaultMadeBy = 0x0317, // 03=UNIX, 17=spec v2.3 + kUsesDataDescr = 0x0008, // GPBitFlag bit 3 + }; + + LocalFileHeader mLFH; + CentralDirEntry mCDE; +}; + +}; // namespace android + +#endif // __LIBS_ZIPENTRY_H diff --git a/tools/aapt/ZipFile.cpp b/tools/aapt/ZipFile.cpp new file mode 100644 index 0000000..62c9383 --- /dev/null +++ b/tools/aapt/ZipFile.cpp @@ -0,0 +1,1297 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// +// Access to Zip archives. +// + +#define LOG_TAG "zip" + +#include <utils/ZipUtils.h> +#include <utils/Log.h> + +#include "ZipFile.h" + +#include <zlib.h> +#define DEF_MEM_LEVEL 8 // normally in zutil.h? + +#include <memory.h> +#include <sys/stat.h> +#include <errno.h> +#include <assert.h> + +using namespace android; + +/* + * Some environments require the "b", some choke on it. + */ +#define FILE_OPEN_RO "rb" +#define FILE_OPEN_RW "r+b" +#define FILE_OPEN_RW_CREATE "w+b" + +/* should live somewhere else? */ +static status_t errnoToStatus(int err) +{ + if (err == ENOENT) + return NAME_NOT_FOUND; + else if (err == EACCES) + return PERMISSION_DENIED; + else + return UNKNOWN_ERROR; +} + +/* + * Open a file and parse its guts. + */ +status_t ZipFile::open(const char* zipFileName, int flags) +{ + bool newArchive = false; + + assert(mZipFp == NULL); // no reopen + + if ((flags & kOpenTruncate)) + flags |= kOpenCreate; // trunc implies create + + if ((flags & kOpenReadOnly) && (flags & kOpenReadWrite)) + return INVALID_OPERATION; // not both + if (!((flags & kOpenReadOnly) || (flags & kOpenReadWrite))) + return INVALID_OPERATION; // not neither + if ((flags & kOpenCreate) && !(flags & kOpenReadWrite)) + return INVALID_OPERATION; // create requires write + + if (flags & kOpenTruncate) { + newArchive = true; + } else { + newArchive = (access(zipFileName, F_OK) != 0); + if (!(flags & kOpenCreate) && newArchive) { + /* not creating, must already exist */ + LOGD("File %s does not exist", zipFileName); + return NAME_NOT_FOUND; + } + } + + /* open the file */ + const char* openflags; + if (flags & kOpenReadWrite) { + if (newArchive) + openflags = FILE_OPEN_RW_CREATE; + else + openflags = FILE_OPEN_RW; + } else { + openflags = FILE_OPEN_RO; + } + mZipFp = fopen(zipFileName, openflags); + if (mZipFp == NULL) { + int err = errno; + LOGD("fopen failed: %d\n", err); + return errnoToStatus(err); + } + + status_t result; + if (!newArchive) { + /* + * Load the central directory. If that fails, then this probably + * isn't a Zip archive. + */ + result = readCentralDir(); + } else { + /* + * Newly-created. The EndOfCentralDir constructor actually + * sets everything to be the way we want it (all zeroes). We + * set mNeedCDRewrite so that we create *something* if the + * caller doesn't add any files. (We could also just unlink + * the file if it's brand new and nothing was added, but that's + * probably doing more than we really should -- the user might + * have a need for empty zip files.) + */ + mNeedCDRewrite = true; + result = NO_ERROR; + } + + if (flags & kOpenReadOnly) + mReadOnly = true; + else + assert(!mReadOnly); + + return result; +} + +/* + * Return the Nth entry in the archive. + */ +ZipEntry* ZipFile::getEntryByIndex(int idx) const +{ + if (idx < 0 || idx >= (int) mEntries.size()) + return NULL; + + return mEntries[idx]; +} + +/* + * Find an entry by name. + */ +ZipEntry* ZipFile::getEntryByName(const char* fileName) const +{ + /* + * Do a stupid linear string-compare search. + * + * There are various ways to speed this up, especially since it's rare + * to intermingle changes to the archive with "get by name" calls. We + * don't want to sort the mEntries vector itself, however, because + * it's used to recreate the Central Directory. + * + * (Hash table works, parallel list of pointers in sorted order is good.) + */ + int idx; + + for (idx = mEntries.size()-1; idx >= 0; idx--) { + ZipEntry* pEntry = mEntries[idx]; + if (!pEntry->getDeleted() && + strcmp(fileName, pEntry->getFileName()) == 0) + { + return pEntry; + } + } + + return NULL; +} + +/* + * Empty the mEntries vector. + */ +void ZipFile::discardEntries(void) +{ + int count = mEntries.size(); + + while (--count >= 0) + delete mEntries[count]; + + mEntries.clear(); +} + + +/* + * Find the central directory and read the contents. + * + * The fun thing about ZIP archives is that they may or may not be + * readable from start to end. In some cases, notably for archives + * that were written to stdout, the only length information is in the + * central directory at the end of the file. + * + * Of course, the central directory can be followed by a variable-length + * comment field, so we have to scan through it backwards. The comment + * is at most 64K, plus we have 18 bytes for the end-of-central-dir stuff + * itself, plus apparently sometimes people throw random junk on the end + * just for the fun of it. + * + * This is all a little wobbly. If the wrong value ends up in the EOCD + * area, we're hosed. This appears to be the way that everbody handles + * it though, so we're in pretty good company if this fails. + */ +status_t ZipFile::readCentralDir(void) +{ + status_t result = NO_ERROR; + unsigned char* buf = NULL; + off_t fileLength, seekStart; + long readAmount; + int i; + + fseek(mZipFp, 0, SEEK_END); + fileLength = ftell(mZipFp); + rewind(mZipFp); + + /* too small to be a ZIP archive? */ + if (fileLength < EndOfCentralDir::kEOCDLen) { + LOGD("Length is %ld -- too small\n", (long)fileLength); + result = INVALID_OPERATION; + goto bail; + } + + buf = new unsigned char[EndOfCentralDir::kMaxEOCDSearch]; + if (buf == NULL) { + LOGD("Failure allocating %d bytes for EOCD search", + EndOfCentralDir::kMaxEOCDSearch); + result = NO_MEMORY; + goto bail; + } + + if (fileLength > EndOfCentralDir::kMaxEOCDSearch) { + seekStart = fileLength - EndOfCentralDir::kMaxEOCDSearch; + readAmount = EndOfCentralDir::kMaxEOCDSearch; + } else { + seekStart = 0; + readAmount = (long) fileLength; + } + if (fseek(mZipFp, seekStart, SEEK_SET) != 0) { + LOGD("Failure seeking to end of zip at %ld", (long) seekStart); + result = UNKNOWN_ERROR; + goto bail; + } + + /* read the last part of the file into the buffer */ + if (fread(buf, 1, readAmount, mZipFp) != (size_t) readAmount) { + LOGD("short file? wanted %ld\n", readAmount); + result = UNKNOWN_ERROR; + goto bail; + } + + /* find the end-of-central-dir magic */ + for (i = readAmount - 4; i >= 0; i--) { + if (buf[i] == 0x50 && + ZipEntry::getLongLE(&buf[i]) == EndOfCentralDir::kSignature) + { + LOGV("+++ Found EOCD at buf+%d\n", i); + break; + } + } + if (i < 0) { + LOGD("EOCD not found, not Zip\n"); + result = INVALID_OPERATION; + goto bail; + } + + /* extract eocd values */ + result = mEOCD.readBuf(buf + i, readAmount - i); + if (result != NO_ERROR) { + LOGD("Failure reading %ld bytes of EOCD values", readAmount - i); + goto bail; + } + //mEOCD.dump(); + + if (mEOCD.mDiskNumber != 0 || mEOCD.mDiskWithCentralDir != 0 || + mEOCD.mNumEntries != mEOCD.mTotalNumEntries) + { + LOGD("Archive spanning not supported\n"); + result = INVALID_OPERATION; + goto bail; + } + + /* + * So far so good. "mCentralDirSize" is the size in bytes of the + * central directory, so we can just seek back that far to find it. + * We can also seek forward mCentralDirOffset bytes from the + * start of the file. + * + * We're not guaranteed to have the rest of the central dir in the + * buffer, nor are we guaranteed that the central dir will have any + * sort of convenient size. We need to skip to the start of it and + * read the header, then the other goodies. + * + * The only thing we really need right now is the file comment, which + * we're hoping to preserve. + */ + if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) { + LOGD("Failure seeking to central dir offset %ld\n", + mEOCD.mCentralDirOffset); + result = UNKNOWN_ERROR; + goto bail; + } + + /* + * Loop through and read the central dir entries. + */ + LOGV("Scanning %d entries...\n", mEOCD.mTotalNumEntries); + int entry; + for (entry = 0; entry < mEOCD.mTotalNumEntries; entry++) { + ZipEntry* pEntry = new ZipEntry; + + result = pEntry->initFromCDE(mZipFp); + if (result != NO_ERROR) { + LOGD("initFromCDE failed\n"); + delete pEntry; + goto bail; + } + + mEntries.add(pEntry); + } + + + /* + * If all went well, we should now be back at the EOCD. + */ + { + unsigned char checkBuf[4]; + if (fread(checkBuf, 1, 4, mZipFp) != 4) { + LOGD("EOCD check read failed\n"); + result = INVALID_OPERATION; + goto bail; + } + if (ZipEntry::getLongLE(checkBuf) != EndOfCentralDir::kSignature) { + LOGD("EOCD read check failed\n"); + result = UNKNOWN_ERROR; + goto bail; + } + LOGV("+++ EOCD read check passed\n"); + } + +bail: + delete[] buf; + return result; +} + + +/* + * Add a new file to the archive. + * + * This requires creating and populating a ZipEntry structure, and copying + * the data into the file at the appropriate position. The "appropriate + * position" is the current location of the central directory, which we + * casually overwrite (we can put it back later). + * + * If we were concerned about safety, we would want to make all changes + * in a temp file and then overwrite the original after everything was + * safely written. Not really a concern for us. + */ +status_t ZipFile::addCommon(const char* fileName, const void* data, size_t size, + const char* storageName, int sourceType, int compressionMethod, + ZipEntry** ppEntry) +{ + ZipEntry* pEntry = NULL; + status_t result = NO_ERROR; + long lfhPosn, startPosn, endPosn, uncompressedLen; + FILE* inputFp = NULL; + unsigned long crc; + time_t modWhen; + + if (mReadOnly) + return INVALID_OPERATION; + + assert(compressionMethod == ZipEntry::kCompressDeflated || + compressionMethod == ZipEntry::kCompressStored); + + /* make sure we're in a reasonable state */ + assert(mZipFp != NULL); + assert(mEntries.size() == mEOCD.mTotalNumEntries); + + /* make sure it doesn't already exist */ + if (getEntryByName(storageName) != NULL) + return ALREADY_EXISTS; + + if (!data) { + inputFp = fopen(fileName, FILE_OPEN_RO); + if (inputFp == NULL) + return errnoToStatus(errno); + } + + if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) { + result = UNKNOWN_ERROR; + goto bail; + } + + pEntry = new ZipEntry; + pEntry->initNew(storageName, NULL); + + /* + * From here on out, failures are more interesting. + */ + mNeedCDRewrite = true; + + /* + * Write the LFH, even though it's still mostly blank. We need it + * as a place-holder. In theory the LFH isn't necessary, but in + * practice some utilities demand it. + */ + lfhPosn = ftell(mZipFp); + pEntry->mLFH.write(mZipFp); + startPosn = ftell(mZipFp); + + /* + * Copy the data in, possibly compressing it as we go. + */ + if (sourceType == ZipEntry::kCompressStored) { + if (compressionMethod == ZipEntry::kCompressDeflated) { + bool failed = false; + result = compressFpToFp(mZipFp, inputFp, data, size, &crc); + if (result != NO_ERROR) { + LOGD("compression failed, storing\n"); + failed = true; + } else { + /* + * Make sure it has compressed "enough". This probably ought + * to be set through an API call, but I don't expect our + * criteria to change over time. + */ + long src = inputFp ? ftell(inputFp) : size; + long dst = ftell(mZipFp) - startPosn; + if (dst + (dst / 10) > src) { + LOGD("insufficient compression (src=%ld dst=%ld), storing\n", + src, dst); + failed = true; + } + } + + if (failed) { + compressionMethod = ZipEntry::kCompressStored; + if (inputFp) rewind(inputFp); + fseek(mZipFp, startPosn, SEEK_SET); + /* fall through to kCompressStored case */ + } + } + /* handle "no compression" request, or failed compression from above */ + if (compressionMethod == ZipEntry::kCompressStored) { + if (inputFp) { + result = copyFpToFp(mZipFp, inputFp, &crc); + } else { + result = copyDataToFp(mZipFp, data, size, &crc); + } + if (result != NO_ERROR) { + // don't need to truncate; happens in CDE rewrite + LOGD("failed copying data in\n"); + goto bail; + } + } + + // currently seeked to end of file + uncompressedLen = inputFp ? ftell(inputFp) : size; + } else if (sourceType == ZipEntry::kCompressDeflated) { + /* we should support uncompressed-from-compressed, but it's not + * important right now */ + assert(compressionMethod == ZipEntry::kCompressDeflated); + + bool scanResult; + int method; + long compressedLen; + + scanResult = ZipUtils::examineGzip(inputFp, &method, &uncompressedLen, + &compressedLen, &crc); + if (!scanResult || method != ZipEntry::kCompressDeflated) { + LOGD("this isn't a deflated gzip file?"); + result = UNKNOWN_ERROR; + goto bail; + } + + result = copyPartialFpToFp(mZipFp, inputFp, compressedLen, NULL); + if (result != NO_ERROR) { + LOGD("failed copying gzip data in\n"); + goto bail; + } + } else { + assert(false); + result = UNKNOWN_ERROR; + goto bail; + } + + /* + * We could write the "Data Descriptor", but there doesn't seem to + * be any point since we're going to go back and write the LFH. + * + * Update file offsets. + */ + endPosn = ftell(mZipFp); // seeked to end of compressed data + + /* + * Success! Fill out new values. + */ + pEntry->setDataInfo(uncompressedLen, endPosn - startPosn, crc, + compressionMethod); + modWhen = getModTime(inputFp ? fileno(inputFp) : fileno(mZipFp)); + pEntry->setModWhen(modWhen); + pEntry->setLFHOffset(lfhPosn); + mEOCD.mNumEntries++; + mEOCD.mTotalNumEntries++; + mEOCD.mCentralDirSize = 0; // mark invalid; set by flush() + mEOCD.mCentralDirOffset = endPosn; + + /* + * Go back and write the LFH. + */ + if (fseek(mZipFp, lfhPosn, SEEK_SET) != 0) { + result = UNKNOWN_ERROR; + goto bail; + } + pEntry->mLFH.write(mZipFp); + + /* + * Add pEntry to the list. + */ + mEntries.add(pEntry); + if (ppEntry != NULL) + *ppEntry = pEntry; + pEntry = NULL; + +bail: + if (inputFp != NULL) + fclose(inputFp); + delete pEntry; + return result; +} + +/* + * Add an entry by copying it from another zip file. If "padding" is + * nonzero, the specified number of bytes will be added to the "extra" + * field in the header. + * + * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. + */ +status_t ZipFile::add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry, + int padding, ZipEntry** ppEntry) +{ + ZipEntry* pEntry = NULL; + status_t result; + long lfhPosn, endPosn; + + if (mReadOnly) + return INVALID_OPERATION; + + /* make sure we're in a reasonable state */ + assert(mZipFp != NULL); + assert(mEntries.size() == mEOCD.mTotalNumEntries); + + if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) { + result = UNKNOWN_ERROR; + goto bail; + } + + pEntry = new ZipEntry; + if (pEntry == NULL) { + result = NO_MEMORY; + goto bail; + } + + result = pEntry->initFromExternal(pSourceZip, pSourceEntry); + if (result != NO_ERROR) + goto bail; + if (padding != 0) { + result = pEntry->addPadding(padding); + if (result != NO_ERROR) + goto bail; + } + + /* + * From here on out, failures are more interesting. + */ + mNeedCDRewrite = true; + + /* + * Write the LFH. Since we're not recompressing the data, we already + * have all of the fields filled out. + */ + lfhPosn = ftell(mZipFp); + pEntry->mLFH.write(mZipFp); + + /* + * Copy the data over. + * + * If the "has data descriptor" flag is set, we want to copy the DD + * fields as well. This is a fixed-size area immediately following + * the data. + */ + if (fseek(pSourceZip->mZipFp, pSourceEntry->getFileOffset(), SEEK_SET) != 0) + { + result = UNKNOWN_ERROR; + goto bail; + } + + off_t copyLen; + copyLen = pSourceEntry->getCompressedLen(); + if ((pSourceEntry->mLFH.mGPBitFlag & ZipEntry::kUsesDataDescr) != 0) + copyLen += ZipEntry::kDataDescriptorLen; + + if (copyPartialFpToFp(mZipFp, pSourceZip->mZipFp, copyLen, NULL) + != NO_ERROR) + { + LOGW("copy of '%s' failed\n", pEntry->mCDE.mFileName); + result = UNKNOWN_ERROR; + goto bail; + } + + /* + * Update file offsets. + */ + endPosn = ftell(mZipFp); + + /* + * Success! Fill out new values. + */ + pEntry->setLFHOffset(lfhPosn); // sets mCDE.mLocalHeaderRelOffset + mEOCD.mNumEntries++; + mEOCD.mTotalNumEntries++; + mEOCD.mCentralDirSize = 0; // mark invalid; set by flush() + mEOCD.mCentralDirOffset = endPosn; + + /* + * Add pEntry to the list. + */ + mEntries.add(pEntry); + if (ppEntry != NULL) + *ppEntry = pEntry; + pEntry = NULL; + + result = NO_ERROR; + +bail: + delete pEntry; + return result; +} + +/* + * Copy all of the bytes in "src" to "dst". + * + * On exit, "srcFp" will be seeked to the end of the file, and "dstFp" + * will be seeked immediately past the data. + */ +status_t ZipFile::copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32) +{ + unsigned char tmpBuf[32768]; + size_t count; + + *pCRC32 = crc32(0L, Z_NULL, 0); + + while (1) { + count = fread(tmpBuf, 1, sizeof(tmpBuf), srcFp); + if (ferror(srcFp) || ferror(dstFp)) + return errnoToStatus(errno); + if (count == 0) + break; + + *pCRC32 = crc32(*pCRC32, tmpBuf, count); + + if (fwrite(tmpBuf, 1, count, dstFp) != count) { + LOGD("fwrite %d bytes failed\n", (int) count); + return UNKNOWN_ERROR; + } + } + + return NO_ERROR; +} + +/* + * Copy all of the bytes in "src" to "dst". + * + * On exit, "dstFp" will be seeked immediately past the data. + */ +status_t ZipFile::copyDataToFp(FILE* dstFp, + const void* data, size_t size, unsigned long* pCRC32) +{ + size_t count; + + *pCRC32 = crc32(0L, Z_NULL, 0); + if (size > 0) { + *pCRC32 = crc32(*pCRC32, (const unsigned char*)data, size); + if (fwrite(data, 1, size, dstFp) != size) { + LOGD("fwrite %d bytes failed\n", (int) size); + return UNKNOWN_ERROR; + } + } + + return NO_ERROR; +} + +/* + * Copy some of the bytes in "src" to "dst". + * + * If "pCRC32" is NULL, the CRC will not be computed. + * + * On exit, "srcFp" will be seeked to the end of the file, and "dstFp" + * will be seeked immediately past the data just written. + */ +status_t ZipFile::copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length, + unsigned long* pCRC32) +{ + unsigned char tmpBuf[32768]; + size_t count; + + if (pCRC32 != NULL) + *pCRC32 = crc32(0L, Z_NULL, 0); + + while (length) { + long readSize; + + readSize = sizeof(tmpBuf); + if (readSize > length) + readSize = length; + + count = fread(tmpBuf, 1, readSize, srcFp); + if ((long) count != readSize) { // error or unexpected EOF + LOGD("fread %d bytes failed\n", (int) readSize); + return UNKNOWN_ERROR; + } + + if (pCRC32 != NULL) + *pCRC32 = crc32(*pCRC32, tmpBuf, count); + + if (fwrite(tmpBuf, 1, count, dstFp) != count) { + LOGD("fwrite %d bytes failed\n", (int) count); + return UNKNOWN_ERROR; + } + + length -= readSize; + } + + return NO_ERROR; +} + +/* + * Compress all of the data in "srcFp" and write it to "dstFp". + * + * On exit, "srcFp" will be seeked to the end of the file, and "dstFp" + * will be seeked immediately past the compressed data. + */ +status_t ZipFile::compressFpToFp(FILE* dstFp, FILE* srcFp, + const void* data, size_t size, unsigned long* pCRC32) +{ + status_t result = NO_ERROR; + const size_t kBufSize = 32768; + unsigned char* inBuf = NULL; + unsigned char* outBuf = NULL; + z_stream zstream; + bool atEof = false; // no feof() aviailable yet + unsigned long crc; + int zerr; + + /* + * Create an input buffer and an output buffer. + */ + inBuf = new unsigned char[kBufSize]; + outBuf = new unsigned char[kBufSize]; + if (inBuf == NULL || outBuf == NULL) { + result = NO_MEMORY; + goto bail; + } + + /* + * Initialize the zlib stream. + */ + memset(&zstream, 0, sizeof(zstream)); + zstream.zalloc = Z_NULL; + zstream.zfree = Z_NULL; + zstream.opaque = Z_NULL; + zstream.next_in = NULL; + zstream.avail_in = 0; + zstream.next_out = outBuf; + zstream.avail_out = kBufSize; + zstream.data_type = Z_UNKNOWN; + + zerr = deflateInit2(&zstream, Z_BEST_COMPRESSION, + Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY); + if (zerr != Z_OK) { + result = UNKNOWN_ERROR; + if (zerr == Z_VERSION_ERROR) { + LOGE("Installed zlib is not compatible with linked version (%s)\n", + ZLIB_VERSION); + } else { + LOGD("Call to deflateInit2 failed (zerr=%d)\n", zerr); + } + goto bail; + } + + crc = crc32(0L, Z_NULL, 0); + + /* + * Loop while we have data. + */ + do { + size_t getSize; + int flush; + + /* only read if the input buffer is empty */ + if (zstream.avail_in == 0 && !atEof) { + LOGV("+++ reading %d bytes\n", (int)kBufSize); + if (data) { + getSize = size > kBufSize ? kBufSize : size; + memcpy(inBuf, data, getSize); + data = ((const char*)data) + getSize; + size -= getSize; + } else { + getSize = fread(inBuf, 1, kBufSize, srcFp); + if (ferror(srcFp)) { + LOGD("deflate read failed (errno=%d)\n", errno); + goto z_bail; + } + } + if (getSize < kBufSize) { + LOGV("+++ got %d bytes, EOF reached\n", + (int)getSize); + atEof = true; + } + + crc = crc32(crc, inBuf, getSize); + + zstream.next_in = inBuf; + zstream.avail_in = getSize; + } + + if (atEof) + flush = Z_FINISH; /* tell zlib that we're done */ + else + flush = Z_NO_FLUSH; /* more to come! */ + + zerr = deflate(&zstream, flush); + if (zerr != Z_OK && zerr != Z_STREAM_END) { + LOGD("zlib deflate call failed (zerr=%d)\n", zerr); + result = UNKNOWN_ERROR; + goto z_bail; + } + + /* write when we're full or when we're done */ + if (zstream.avail_out == 0 || + (zerr == Z_STREAM_END && zstream.avail_out != (uInt) kBufSize)) + { + LOGV("+++ writing %d bytes\n", (int) (zstream.next_out - outBuf)); + if (fwrite(outBuf, 1, zstream.next_out - outBuf, dstFp) != + (size_t)(zstream.next_out - outBuf)) + { + LOGD("write %d failed in deflate\n", + (int) (zstream.next_out - outBuf)); + goto z_bail; + } + + zstream.next_out = outBuf; + zstream.avail_out = kBufSize; + } + } while (zerr == Z_OK); + + assert(zerr == Z_STREAM_END); /* other errors should've been caught */ + + *pCRC32 = crc; + +z_bail: + deflateEnd(&zstream); /* free up any allocated structures */ + +bail: + delete[] inBuf; + delete[] outBuf; + + return result; +} + +/* + * Mark an entry as deleted. + * + * We will eventually need to crunch the file down, but if several files + * are being removed (perhaps as part of an "update" process) we can make + * things considerably faster by deferring the removal to "flush" time. + */ +status_t ZipFile::remove(ZipEntry* pEntry) +{ + /* + * Should verify that pEntry is actually part of this archive, and + * not some stray ZipEntry from a different file. + */ + + /* mark entry as deleted, and mark archive as dirty */ + pEntry->setDeleted(); + mNeedCDRewrite = true; + return NO_ERROR; +} + +/* + * Flush any pending writes. + * + * In particular, this will crunch out deleted entries, and write the + * Central Directory and EOCD if we have stomped on them. + */ +status_t ZipFile::flush(void) +{ + status_t result = NO_ERROR; + long eocdPosn; + int i, count; + + if (mReadOnly) + return INVALID_OPERATION; + if (!mNeedCDRewrite) + return NO_ERROR; + + assert(mZipFp != NULL); + + result = crunchArchive(); + if (result != NO_ERROR) + return result; + + if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) + return UNKNOWN_ERROR; + + count = mEntries.size(); + for (i = 0; i < count; i++) { + ZipEntry* pEntry = mEntries[i]; + pEntry->mCDE.write(mZipFp); + } + + eocdPosn = ftell(mZipFp); + mEOCD.mCentralDirSize = eocdPosn - mEOCD.mCentralDirOffset; + + mEOCD.write(mZipFp); + + /* + * If we had some stuff bloat up during compression and get replaced + * with plain files, or if we deleted some entries, there's a lot + * of wasted space at the end of the file. Remove it now. + */ + if (ftruncate(fileno(mZipFp), ftell(mZipFp)) != 0) { + LOGW("ftruncate failed %ld: %s\n", ftell(mZipFp), strerror(errno)); + // not fatal + } + + /* should we clear the "newly added" flag in all entries now? */ + + mNeedCDRewrite = false; + return NO_ERROR; +} + +/* + * Crunch deleted files out of an archive by shifting the later files down. + * + * Because we're not using a temp file, we do the operation inside the + * current file. + */ +status_t ZipFile::crunchArchive(void) +{ + status_t result = NO_ERROR; + int i, count; + long delCount, adjust; + +#if 0 + printf("CONTENTS:\n"); + for (i = 0; i < (int) mEntries.size(); i++) { + printf(" %d: lfhOff=%ld del=%d\n", + i, mEntries[i]->getLFHOffset(), mEntries[i]->getDeleted()); + } + printf(" END is %ld\n", (long) mEOCD.mCentralDirOffset); +#endif + + /* + * Roll through the set of files, shifting them as appropriate. We + * could probably get a slight performance improvement by sliding + * multiple files down at once (because we could use larger reads + * when operating on batches of small files), but it's not that useful. + */ + count = mEntries.size(); + delCount = adjust = 0; + for (i = 0; i < count; i++) { + ZipEntry* pEntry = mEntries[i]; + long span; + + if (pEntry->getLFHOffset() != 0) { + long nextOffset; + + /* Get the length of this entry by finding the offset + * of the next entry. Directory entries don't have + * file offsets, so we need to find the next non-directory + * entry. + */ + nextOffset = 0; + for (int ii = i+1; nextOffset == 0 && ii < count; ii++) + nextOffset = mEntries[ii]->getLFHOffset(); + if (nextOffset == 0) + nextOffset = mEOCD.mCentralDirOffset; + span = nextOffset - pEntry->getLFHOffset(); + + assert(span >= ZipEntry::LocalFileHeader::kLFHLen); + } else { + /* This is a directory entry. It doesn't have + * any actual file contents, so there's no need to + * move anything. + */ + span = 0; + } + + //printf("+++ %d: off=%ld span=%ld del=%d [count=%d]\n", + // i, pEntry->getLFHOffset(), span, pEntry->getDeleted(), count); + + if (pEntry->getDeleted()) { + adjust += span; + delCount++; + + delete pEntry; + mEntries.removeAt(i); + + /* adjust loop control */ + count--; + i--; + } else if (span != 0 && adjust > 0) { + /* shuffle this entry back */ + //printf("+++ Shuffling '%s' back %ld\n", + // pEntry->getFileName(), adjust); + result = filemove(mZipFp, pEntry->getLFHOffset() - adjust, + pEntry->getLFHOffset(), span); + if (result != NO_ERROR) { + /* this is why you use a temp file */ + LOGE("error during crunch - archive is toast\n"); + return result; + } + + pEntry->setLFHOffset(pEntry->getLFHOffset() - adjust); + } + } + + /* + * Fix EOCD info. We have to wait until the end to do some of this + * because we use mCentralDirOffset to determine "span" for the + * last entry. + */ + mEOCD.mCentralDirOffset -= adjust; + mEOCD.mNumEntries -= delCount; + mEOCD.mTotalNumEntries -= delCount; + mEOCD.mCentralDirSize = 0; // mark invalid; set by flush() + + assert(mEOCD.mNumEntries == mEOCD.mTotalNumEntries); + assert(mEOCD.mNumEntries == count); + + return result; +} + +/* + * Works like memmove(), but on pieces of a file. + */ +status_t ZipFile::filemove(FILE* fp, off_t dst, off_t src, size_t n) +{ + if (dst == src || n <= 0) + return NO_ERROR; + + unsigned char readBuf[32768]; + + if (dst < src) { + /* shift stuff toward start of file; must read from start */ + while (n != 0) { + size_t getSize = sizeof(readBuf); + if (getSize > n) + getSize = n; + + if (fseek(fp, (long) src, SEEK_SET) != 0) { + LOGD("filemove src seek %ld failed\n", (long) src); + return UNKNOWN_ERROR; + } + + if (fread(readBuf, 1, getSize, fp) != getSize) { + LOGD("filemove read %ld off=%ld failed\n", + (long) getSize, (long) src); + return UNKNOWN_ERROR; + } + + if (fseek(fp, (long) dst, SEEK_SET) != 0) { + LOGD("filemove dst seek %ld failed\n", (long) dst); + return UNKNOWN_ERROR; + } + + if (fwrite(readBuf, 1, getSize, fp) != getSize) { + LOGD("filemove write %ld off=%ld failed\n", + (long) getSize, (long) dst); + return UNKNOWN_ERROR; + } + + src += getSize; + dst += getSize; + n -= getSize; + } + } else { + /* shift stuff toward end of file; must read from end */ + assert(false); // write this someday, maybe + return UNKNOWN_ERROR; + } + + return NO_ERROR; +} + + +/* + * Get the modification time from a file descriptor. + */ +time_t ZipFile::getModTime(int fd) +{ + struct stat sb; + + if (fstat(fd, &sb) < 0) { + LOGD("HEY: fstat on fd %d failed\n", fd); + return (time_t) -1; + } + + return sb.st_mtime; +} + + +#if 0 /* this is a bad idea */ +/* + * Get a copy of the Zip file descriptor. + * + * We don't allow this if the file was opened read-write because we tend + * to leave the file contents in an uncertain state between calls to + * flush(). The duplicated file descriptor should only be valid for reads. + */ +int ZipFile::getZipFd(void) const +{ + if (!mReadOnly) + return INVALID_OPERATION; + assert(mZipFp != NULL); + + int fd; + fd = dup(fileno(mZipFp)); + if (fd < 0) { + LOGD("didn't work, errno=%d\n", errno); + } + + return fd; +} +#endif + + +#if 0 +/* + * Expand data. + */ +bool ZipFile::uncompress(const ZipEntry* pEntry, void* buf) const +{ + return false; +} +#endif + +// free the memory when you're done +void* ZipFile::uncompress(const ZipEntry* entry) +{ + size_t unlen = entry->getUncompressedLen(); + size_t clen = entry->getCompressedLen(); + + void* buf = malloc(unlen); + if (buf == NULL) { + return NULL; + } + + fseek(mZipFp, 0, SEEK_SET); + + off_t offset = entry->getFileOffset(); + if (fseek(mZipFp, offset, SEEK_SET) != 0) { + goto bail; + } + + switch (entry->getCompressionMethod()) + { + case ZipEntry::kCompressStored: { + ssize_t amt = fread(buf, 1, unlen, mZipFp); + if (amt != (ssize_t)unlen) { + goto bail; + } +#if 0 + printf("data...\n"); + const unsigned char* p = (unsigned char*)buf; + const unsigned char* end = p+unlen; + for (int i=0; i<32 && p < end; i++) { + printf("0x%08x ", (int)(offset+(i*0x10))); + for (int j=0; j<0x10 && p < end; j++) { + printf(" %02x", *p); + p++; + } + printf("\n"); + } +#endif + + } + break; + case ZipEntry::kCompressDeflated: { + if (!ZipUtils::inflateToBuffer(mZipFp, buf, unlen, clen)) { + goto bail; + } + } + break; + default: + goto bail; + } + return buf; + +bail: + free(buf); + return NULL; +} + + +/* + * =========================================================================== + * ZipFile::EndOfCentralDir + * =========================================================================== + */ + +/* + * Read the end-of-central-dir fields. + * + * "buf" should be positioned at the EOCD signature, and should contain + * the entire EOCD area including the comment. + */ +status_t ZipFile::EndOfCentralDir::readBuf(const unsigned char* buf, int len) +{ + /* don't allow re-use */ + assert(mComment == NULL); + + if (len < kEOCDLen) { + /* looks like ZIP file got truncated */ + LOGD(" Zip EOCD: expected >= %d bytes, found %d\n", + kEOCDLen, len); + return INVALID_OPERATION; + } + + /* this should probably be an assert() */ + if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) + return UNKNOWN_ERROR; + + mDiskNumber = ZipEntry::getShortLE(&buf[0x04]); + mDiskWithCentralDir = ZipEntry::getShortLE(&buf[0x06]); + mNumEntries = ZipEntry::getShortLE(&buf[0x08]); + mTotalNumEntries = ZipEntry::getShortLE(&buf[0x0a]); + mCentralDirSize = ZipEntry::getLongLE(&buf[0x0c]); + mCentralDirOffset = ZipEntry::getLongLE(&buf[0x10]); + mCommentLen = ZipEntry::getShortLE(&buf[0x14]); + + // TODO: validate mCentralDirOffset + + if (mCommentLen > 0) { + if (kEOCDLen + mCommentLen > len) { + LOGD("EOCD(%d) + comment(%d) exceeds len (%d)\n", + kEOCDLen, mCommentLen, len); + return UNKNOWN_ERROR; + } + mComment = new unsigned char[mCommentLen]; + memcpy(mComment, buf + kEOCDLen, mCommentLen); + } + + return NO_ERROR; +} + +/* + * Write an end-of-central-directory section. + */ +status_t ZipFile::EndOfCentralDir::write(FILE* fp) +{ + unsigned char buf[kEOCDLen]; + + ZipEntry::putLongLE(&buf[0x00], kSignature); + ZipEntry::putShortLE(&buf[0x04], mDiskNumber); + ZipEntry::putShortLE(&buf[0x06], mDiskWithCentralDir); + ZipEntry::putShortLE(&buf[0x08], mNumEntries); + ZipEntry::putShortLE(&buf[0x0a], mTotalNumEntries); + ZipEntry::putLongLE(&buf[0x0c], mCentralDirSize); + ZipEntry::putLongLE(&buf[0x10], mCentralDirOffset); + ZipEntry::putShortLE(&buf[0x14], mCommentLen); + + if (fwrite(buf, 1, kEOCDLen, fp) != kEOCDLen) + return UNKNOWN_ERROR; + if (mCommentLen > 0) { + assert(mComment != NULL); + if (fwrite(mComment, mCommentLen, 1, fp) != mCommentLen) + return UNKNOWN_ERROR; + } + + return NO_ERROR; +} + +/* + * Dump the contents of an EndOfCentralDir object. + */ +void ZipFile::EndOfCentralDir::dump(void) const +{ + LOGD(" EndOfCentralDir contents:\n"); + LOGD(" diskNum=%u diskWCD=%u numEnt=%u totalNumEnt=%u\n", + mDiskNumber, mDiskWithCentralDir, mNumEntries, mTotalNumEntries); + LOGD(" centDirSize=%lu centDirOff=%lu commentLen=%u\n", + mCentralDirSize, mCentralDirOffset, mCommentLen); +} + diff --git a/tools/aapt/ZipFile.h b/tools/aapt/ZipFile.h new file mode 100644 index 0000000..dbbd072 --- /dev/null +++ b/tools/aapt/ZipFile.h @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// +// General-purpose Zip archive access. This class allows both reading and +// writing to Zip archives, including deletion of existing entries. +// +#ifndef __LIBS_ZIPFILE_H +#define __LIBS_ZIPFILE_H + +#include <utils/Vector.h> +#include <utils/Errors.h> +#include <stdio.h> + +#include "ZipEntry.h" + +namespace android { + +/* + * Manipulate a Zip archive. + * + * Some changes will not be visible in the until until "flush" is called. + * + * The correct way to update a file archive is to make all changes to a + * copy of the archive in a temporary file, and then unlink/rename over + * the original after everything completes. Because we're only interested + * in using this for packaging, we don't worry about such things. Crashing + * after making changes and before flush() completes could leave us with + * an unusable Zip archive. + */ +class ZipFile { +public: + ZipFile(void) + : mZipFp(NULL), mReadOnly(false), mNeedCDRewrite(false) + {} + ~ZipFile(void) { + if (!mReadOnly) + flush(); + if (mZipFp != NULL) + fclose(mZipFp); + discardEntries(); + } + + /* + * Open a new or existing archive. + */ + typedef enum { + kOpenReadOnly = 0x01, + kOpenReadWrite = 0x02, + kOpenCreate = 0x04, // create if it doesn't exist + kOpenTruncate = 0x08, // if it exists, empty it + }; + status_t open(const char* zipFileName, int flags); + + /* + * Add a file to the end of the archive. Specify whether you want the + * library to try to store it compressed. + * + * If "storageName" is specified, the archive will use that instead + * of "fileName". + * + * If there is already an entry with the same name, the call fails. + * Existing entries with the same name must be removed first. + * + * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. + */ + status_t add(const char* fileName, int compressionMethod, + ZipEntry** ppEntry) + { + return add(fileName, fileName, compressionMethod, ppEntry); + } + status_t add(const char* fileName, const char* storageName, + int compressionMethod, ZipEntry** ppEntry) + { + return addCommon(fileName, NULL, 0, storageName, + ZipEntry::kCompressStored, + compressionMethod, ppEntry); + } + + /* + * Add a file that is already compressed with gzip. + * + * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. + */ + status_t addGzip(const char* fileName, const char* storageName, + ZipEntry** ppEntry) + { + return addCommon(fileName, NULL, 0, storageName, + ZipEntry::kCompressDeflated, + ZipEntry::kCompressDeflated, ppEntry); + } + + /* + * Add a file from an in-memory data buffer. + * + * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. + */ + status_t add(const void* data, size_t size, const char* storageName, + int compressionMethod, ZipEntry** ppEntry) + { + return addCommon(NULL, data, size, storageName, + ZipEntry::kCompressStored, + compressionMethod, ppEntry); + } + + /* + * Add an entry by copying it from another zip file. If "padding" is + * nonzero, the specified number of bytes will be added to the "extra" + * field in the header. + * + * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. + */ + status_t add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry, + int padding, ZipEntry** ppEntry); + + /* + * Mark an entry as having been removed. It is not actually deleted + * from the archive or our internal data structures until flush() is + * called. + */ + status_t remove(ZipEntry* pEntry); + + /* + * Flush changes. If mNeedCDRewrite is set, this writes the central dir. + */ + status_t flush(void); + + /* + * Expand the data into the buffer provided. The buffer must hold + * at least <uncompressed len> bytes. Variation expands directly + * to a file. + * + * Returns "false" if an error was encountered in the compressed data. + */ + //bool uncompress(const ZipEntry* pEntry, void* buf) const; + //bool uncompress(const ZipEntry* pEntry, FILE* fp) const; + void* uncompress(const ZipEntry* pEntry); + + /* + * Get an entry, by name. Returns NULL if not found. + * + * Does not return entries pending deletion. + */ + ZipEntry* getEntryByName(const char* fileName) const; + + /* + * Get the Nth entry in the archive. + * + * This will return an entry that is pending deletion. + */ + int getNumEntries(void) const { return mEntries.size(); } + ZipEntry* getEntryByIndex(int idx) const; + +private: + /* these are private and not defined */ + ZipFile(const ZipFile& src); + ZipFile& operator=(const ZipFile& src); + + class EndOfCentralDir { + public: + EndOfCentralDir(void) : + mDiskNumber(0), + mDiskWithCentralDir(0), + mNumEntries(0), + mTotalNumEntries(0), + mCentralDirSize(0), + mCentralDirOffset(0), + mCommentLen(0), + mComment(NULL) + {} + virtual ~EndOfCentralDir(void) { + delete[] mComment; + } + + status_t readBuf(const unsigned char* buf, int len); + status_t write(FILE* fp); + + //unsigned long mSignature; + unsigned short mDiskNumber; + unsigned short mDiskWithCentralDir; + unsigned short mNumEntries; + unsigned short mTotalNumEntries; + unsigned long mCentralDirSize; + unsigned long mCentralDirOffset; // offset from first disk + unsigned short mCommentLen; + unsigned char* mComment; + + enum { + kSignature = 0x06054b50, + kEOCDLen = 22, // EndOfCentralDir len, excl. comment + + kMaxCommentLen = 65535, // longest possible in ushort + kMaxEOCDSearch = kMaxCommentLen + EndOfCentralDir::kEOCDLen, + + }; + + void dump(void) const; + }; + + + /* read all entries in the central dir */ + status_t readCentralDir(void); + + /* crunch deleted entries out */ + status_t crunchArchive(void); + + /* clean up mEntries */ + void discardEntries(void); + + /* common handler for all "add" functions */ + status_t addCommon(const char* fileName, const void* data, size_t size, + const char* storageName, int sourceType, int compressionMethod, + ZipEntry** ppEntry); + + /* copy all of "srcFp" into "dstFp" */ + status_t copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32); + /* copy all of "data" into "dstFp" */ + status_t copyDataToFp(FILE* dstFp, + const void* data, size_t size, unsigned long* pCRC32); + /* copy some of "srcFp" into "dstFp" */ + status_t copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length, + unsigned long* pCRC32); + /* like memmove(), but on parts of a single file */ + status_t filemove(FILE* fp, off_t dest, off_t src, size_t n); + /* compress all of "srcFp" into "dstFp", using Deflate */ + status_t compressFpToFp(FILE* dstFp, FILE* srcFp, + const void* data, size_t size, unsigned long* pCRC32); + + /* get modification date from a file descriptor */ + time_t getModTime(int fd); + + /* + * We use stdio FILE*, which gives us buffering but makes dealing + * with files >2GB awkward. Until we support Zip64, we're fine. + */ + FILE* mZipFp; // Zip file pointer + + /* one of these per file */ + EndOfCentralDir mEOCD; + + /* did we open this read-only? */ + bool mReadOnly; + + /* set this when we trash the central dir */ + bool mNeedCDRewrite; + + /* + * One ZipEntry per entry in the zip file. I'm using pointers instead + * of objects because it's easier than making operator= work for the + * classes and sub-classes. + */ + Vector<ZipEntry*> mEntries; +}; + +}; // namespace android + +#endif // __LIBS_ZIPFILE_H diff --git a/tools/aidl/AST.cpp b/tools/aidl/AST.cpp index 85ca5da..752ef7c 100755 --- a/tools/aidl/AST.cpp +++ b/tools/aidl/AST.cpp @@ -845,23 +845,6 @@ Document::Write(FILE* to) fprintf(to, "package %s;\n", this->package.c_str()); } - // gather the types for the import statements - set<Type*> types; - N = this->classes.size(); - for (i=0; i<N; i++) { - Class* c = this->classes[i]; - c->GatherTypes(&types); - } - - set<Type*>::iterator it; - for (it=types.begin(); it!=types.end(); it++) { - Type* t = *it; - string pkg = t->Package(); - if (pkg.length() != 0 && pkg != this->package) { - fprintf(to, "import %s;\n", t->ImportType().c_str()); - } - } - N = this->classes.size(); for (i=0; i<N; i++) { Class* c = this->classes[i]; diff --git a/tools/aidl/generate_java.cpp b/tools/aidl/generate_java.cpp index 622b691..0f18132 100644 --- a/tools/aidl/generate_java.cpp +++ b/tools/aidl/generate_java.cpp @@ -1,6 +1,7 @@ #include "generate_java.h" #include "AST.h" #include "Type.h" +#include <string.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -133,7 +134,7 @@ StubClass::make_as_interface(Type *interfaceType) Method* m = new Method; m->comment = "/**\n * Cast an IBinder object into an "; - m->comment += interfaceType->Name(); + m->comment += interfaceType->QualifiedName(); m->comment += " interface,\n"; m->comment += " * generating a proxy if needed.\n */"; m->modifiers = PUBLIC | STATIC; @@ -323,7 +324,7 @@ generate_method(const method_type* method, Class* interface, transactCodeName += method->name.data; char transactCodeValue[50]; - sprintf(transactCodeValue, "(IBinder.FIRST_CALL_TRANSACTION + %d)", index); + sprintf(transactCodeValue, "(android.os.IBinder.FIRST_CALL_TRANSACTION + %d)", index); Field* transactCode = new Field(STATIC | FINAL, new Variable(INT_TYPE, transactCodeName)); @@ -517,7 +518,7 @@ generate_method(const method_type* method, Class* interface, new LiteralExpression("Stub." + transactCodeName), _data, _reply ? _reply : NULL_VALUE, new LiteralExpression( - oneway ? "IBinder.FLAG_ONEWAY" : "0")); + oneway ? "android.os.IBinder.FLAG_ONEWAY" : "0")); tryStatement->statements->Add(call); // throw back exceptions. diff --git a/tools/aidl/options.h b/tools/aidl/options.h index e9bf0f7..d88d988 100644 --- a/tools/aidl/options.h +++ b/tools/aidl/options.h @@ -1,6 +1,7 @@ #ifndef DEVICE_TOOLS_AIDL_H #define DEVICE_TOOLS_AIDL_H +#include <string.h> #include <string> #include <vector> diff --git a/tools/layoutlib/api/src/com/android/layoutlib/api/IDensityBasedResourceValue.java b/tools/layoutlib/api/src/com/android/layoutlib/api/IDensityBasedResourceValue.java new file mode 100644 index 0000000..7128032 --- /dev/null +++ b/tools/layoutlib/api/src/com/android/layoutlib/api/IDensityBasedResourceValue.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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.api; + +/** + * Represents an Android Resources that has a density info attached to it. + */ +public interface IDensityBasedResourceValue extends IResourceValue { + public static enum Density { + HIGH(240), + MEDIUM(160), + LOW(120), + NODPI(0); + + private final int mValue; + + Density(int value) { + mValue = value; + } + + public int getValue() { + return mValue; + } + } + + /** + * Returns the density associated to the resource. + */ + Density getDensity(); +} diff --git a/tools/layoutlib/api/src/com/android/layoutlib/api/ILayoutBridge.java b/tools/layoutlib/api/src/com/android/layoutlib/api/ILayoutBridge.java index df1876d..4dbcfdc 100644 --- a/tools/layoutlib/api/src/com/android/layoutlib/api/ILayoutBridge.java +++ b/tools/layoutlib/api/src/com/android/layoutlib/api/ILayoutBridge.java @@ -24,35 +24,40 @@ import java.util.Map; * <p/> * <p/>{@link #getApiLevel()} gives the ability to know which methods are available. * <p/> + * Changes in API level 4: + * <ul> + * <li>new render method: {@link #computeLayout(IXmlPullParser, Object, int, int, boolean, int, float, float, String, boolean, Map, Map, IProjectCallback, ILayoutLog)}</li> + * <li>deprecated {@link #computeLayout(IXmlPullParser, Object, int, int, int, float, float, String, boolean, Map, Map, IProjectCallback, ILayoutLog)}</li> + * </ul> * Changes in API level 3: * <ul> - * <li>{@link #computeLayout(IXmlPullParser, Object, int, int, int, float, float, String, boolean, Map, Map, IProjectCallback, ILayoutLog)}</li> - * <li> deprecated {@link #computeLayout(IXmlPullParser, Object, int, int, String, boolean, Map, Map, IProjectCallback, ILayoutLog)}</li> + * <li>new render method: {@link #computeLayout(IXmlPullParser, Object, int, int, int, float, float, String, boolean, Map, Map, IProjectCallback, ILayoutLog)}</li> + * <li>deprecated {@link #computeLayout(IXmlPullParser, Object, int, int, String, boolean, Map, Map, IProjectCallback, ILayoutLog)}</li> * </ul> * Changes in API level 2: * <ul> - * <li>{@link #getApiLevel()}</li> - * <li>{@link #computeLayout(IXmlPullParser, Object, int, int, String, boolean, Map, Map, IProjectCallback, ILayoutLog)}</li> + * <li>new API Level method: {@link #getApiLevel()}</li> + * <li>new render method: {@link #computeLayout(IXmlPullParser, Object, int, int, String, boolean, Map, Map, IProjectCallback, ILayoutLog)}</li> * <li>deprecated {@link #computeLayout(IXmlPullParser, Object, int, int, String, Map, Map, IProjectCallback, ILayoutLog)}</li> * </ul> */ public interface ILayoutBridge { - - final int API_CURRENT = 3; + + final int API_CURRENT = 4; /** * Returns the API level of the layout library. * While no methods will ever be removed, some may become deprecated, and some new ones * will appear. * <p/>If calling this method throws an {@link AbstractMethodError}, then the API level - * should be considered to be 1. + * should be considered to be 1. */ int getApiLevel(); /** * Initializes the Bridge object. * @param fontOsLocation the location of the fonts. - * @param enumValueMap map attrName => { map enumFlagName => Integer value }. + * @param enumValueMap map attrName => { map enumFlagName => Integer value }. * @return true if success. * @since 1 */ @@ -65,6 +70,43 @@ public interface ILayoutBridge { * @param projectKey An Object identifying the project. This is used for the cache mechanism. * @param screenWidth the screen width * @param screenHeight the screen height + * @param renderFullSize if true, the rendering will render the full size needed by the + * layout. This size is never smaller than <var>screenWidth</var> x <var>screenHeight</var>. + * @param density the density factor for the screen. + * @param xdpi the screen actual dpi in X + * @param ydpi the screen actual dpi in Y + * @param themeName The name of the theme to use. + * @param isProjectTheme true if the theme is a project theme, false if it is a framework theme. + * @param projectResources the resources of the project. The map contains (String, map) pairs + * where the string is the type of the resource reference used in the layout file, and the + * map contains (String, {@link IResourceValue}) pairs where the key is the resource name, + * and the value is the resource value. + * @param frameworkResources the framework resources. The map contains (String, map) pairs + * where the string is the type of the resource reference used in the layout file, and the map + * contains (String, {@link IResourceValue}) pairs where the key is the resource name, and the + * value is the resource value. + * @param projectCallback The {@link IProjectCallback} object to get information from + * the project. + * @param logger the object responsible for displaying warning/errors to the user. + * @return a new {@link ILayoutResult} object that contains the result of the layout. + * @since 4 + */ + ILayoutResult computeLayout(IXmlPullParser layoutDescription, + Object projectKey, + int screenWidth, int screenHeight, boolean renderFullSize, + int density, float xdpi, float ydpi, + String themeName, boolean isProjectTheme, + Map<String, Map<String, IResourceValue>> projectResources, + Map<String, Map<String, IResourceValue>> frameworkResources, + IProjectCallback projectCallback, ILayoutLog logger); + + /** + * Computes and renders a layout + * @param layoutDescription the {@link IXmlPullParser} letting the LayoutLib Bridge visit the + * layout file. + * @param projectKey An Object identifying the project. This is used for the cache mechanism. + * @param screenWidth the screen width + * @param screenHeight the screen height * @param density the density factor for the screen. * @param xdpi the screen actual dpi in X * @param ydpi the screen actual dpi in Y @@ -81,9 +123,10 @@ public interface ILayoutBridge { * @param projectCallback The {@link IProjectCallback} object to get information from * the project. * @param logger the object responsible for displaying warning/errors to the user. - * @return an {@link ILayoutResult} object that contains the result of the layout. + * @return a new {@link ILayoutResult} object that contains the result of the layout. * @since 3 */ + @Deprecated ILayoutResult computeLayout(IXmlPullParser layoutDescription, Object projectKey, int screenWidth, int screenHeight, int density, float xdpi, float ydpi, @@ -112,7 +155,7 @@ public interface ILayoutBridge { * @param projectCallback The {@link IProjectCallback} object to get information from * the project. * @param logger the object responsible for displaying warning/errors to the user. - * @return an {@link ILayoutResult} object that contains the result of the layout. + * @return a new {@link ILayoutResult} object that contains the result of the layout. * @deprecated Use {@link #computeLayout(IXmlPullParser, Object, int, int, int, float, float, String, boolean, Map, Map, IProjectCallback, ILayoutLog)} * @since 2 */ @@ -144,7 +187,7 @@ public interface ILayoutBridge { * @param projectCallback The {@link IProjectCallback} object to get information from * the project. * @param logger the object responsible for displaying warning/errors to the user. - * @return an {@link ILayoutResult} object that contains the result of the layout. + * @return a new {@link ILayoutResult} object that contains the result of the layout. * @deprecated Use {@link #computeLayout(IXmlPullParser, Object, int, int, int, float, float, String, boolean, Map, Map, IProjectCallback, ILayoutLog)} * @since 1 */ @@ -155,14 +198,14 @@ public interface ILayoutBridge { Map<String, Map<String, IResourceValue>> projectResources, Map<String, Map<String, IResourceValue>> frameworkResources, IProjectCallback projectCallback, ILayoutLog logger); - + /** * Clears the resource cache for a specific project. * <p/>This cache contains bitmaps and nine patches that are loaded from the disk and reused * until this method is called. * <p/>The cache is not configuration dependent and should only be cleared when a * resource changes (at this time only bitmaps and 9 patches go into the cache). - * @param objectKey the key for the project. + * @param projectKey the key for the project. * @since 1 */ void clearCaches(Object projectKey); diff --git a/tools/layoutlib/api/src/com/android/layoutlib/api/ILayoutResult.java b/tools/layoutlib/api/src/com/android/layoutlib/api/ILayoutResult.java index 5a06349..2d8a210 100644 --- a/tools/layoutlib/api/src/com/android/layoutlib/api/ILayoutResult.java +++ b/tools/layoutlib/api/src/com/android/layoutlib/api/ILayoutResult.java @@ -23,13 +23,17 @@ import java.awt.image.BufferedImage; * {@link ILayoutLibBridge#computeLayout(IXmlPullParser, int, int, String, java.util.Map, java.util.Map, java.util.Map, IFontLoader, ILayoutLibLog, ICustomViewLoader)} */ public interface ILayoutResult { - /** Sucess return code */ + /** + * Success return code + */ final static int SUCCESS = 0; - /** Error return code. - * <p/>See {@link #getErrorMessage()} - */ + + /** + * Error return code, in which case an error message is guaranteed to be defined. + * @See {@link #getErrorMessage()} + */ final static int ERROR = 1; - + /** * Returns the result code. * @see #SUCCESS @@ -62,18 +66,18 @@ public interface ILayoutResult { * Returns the list of children views. */ ILayoutViewInfo[] getChildren(); - + /** * Returns the key associated with the node. * @see IXmlPullParser#getViewKey() */ Object getViewKey(); - + /** * Returns the name of the view. */ String getName(); - + /** * Returns the left of the view bounds. */ diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap.java index 7dde634..ff1b295 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Bitmap.java +++ b/tools/layoutlib/bridge/src/android/graphics/Bitmap.java @@ -28,13 +28,13 @@ public final class Bitmap extends _Original_Bitmap { private BufferedImage mImage; public Bitmap(File input) throws IOException { - super(1, true, null); + super(1, true, null, -1); mImage = ImageIO.read(input); } Bitmap(BufferedImage image) { - super(1, true, null); + super(1, true, null, -1); mImage = image; } diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas.java b/tools/layoutlib/bridge/src/android/graphics/Canvas.java index 3fa1d1d..4986c77 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Canvas.java +++ b/tools/layoutlib/bridge/src/android/graphics/Canvas.java @@ -26,6 +26,7 @@ import android.graphics.RectF; import android.graphics.Region; import android.graphics.Xfermode; import android.graphics.Paint.Align; +import android.graphics.Paint.FontInfo; import android.graphics.Paint.Style; import android.graphics.Region.Op; @@ -37,6 +38,7 @@ import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; +import java.util.List; import java.util.Stack; import javax.microedition.khronos.opengles.GL; @@ -620,19 +622,21 @@ public class Canvas extends _Original_Canvas { */ @Override public void drawText(char[] text, int index, int count, float x, float y, Paint paint) { + // WARNING: the logic in this method is similar to Paint.measureText. + // Any change to this method should be reflected in Paint.measureText Graphics2D g = getGraphics2d(); g = (Graphics2D)g.create(); g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - g.setFont(paint.getFont()); - - // set the color. because this only handles RGB we have to handle the alpha separately + // set the color. because this only handles RGB, the alpha channel is handled + // as a composite. g.setColor(new Color(paint.getColor())); int alpha = paint.getAlpha(); float falpha = alpha / 255.f; g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, falpha)); + // Paint.TextAlign indicates how the text is positioned relative to X. // LEFT is the default and there's nothing to do. if (paint.getTextAlign() != Align.LEFT) { @@ -644,9 +648,83 @@ public class Canvas extends _Original_Canvas { } } - g.drawChars(text, index, count, (int)x, (int)y); - - g.dispose(); + List<FontInfo> fonts = paint.getFonts(); + try { + if (fonts.size() > 0) { + FontInfo mainFont = fonts.get(0); + int i = index; + int lastIndex = index + count; + while (i < lastIndex) { + // always start with the main font. + int upTo = mainFont.mFont.canDisplayUpTo(text, i, lastIndex); + if (upTo == -1) { + // draw all the rest and exit. + g.setFont(mainFont.mFont); + g.drawChars(text, i, lastIndex - i, (int)x, (int)y); + return; + } else if (upTo > 0) { + // draw what's possible + g.setFont(mainFont.mFont); + g.drawChars(text, i, upTo - i, (int)x, (int)y); + + // compute the width that was drawn to increase x + x += mainFont.mMetrics.charsWidth(text, i, upTo - i); + + // move index to the first non displayed char. + i = upTo; + + // don't call continue at this point. Since it is certain the main font + // cannot display the font a index upTo (now ==i), we move on to the + // fallback fonts directly. + } + + // no char supported, attempt to read the next char(s) with the + // fallback font. In this case we only test the first character + // and then go back to test with the main font. + // Special test for 2-char characters. + boolean foundFont = false; + for (int f = 1 ; f < fonts.size() ; f++) { + FontInfo fontInfo = fonts.get(f); + + // need to check that the font can display the character. We test + // differently if the char is a high surrogate. + int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1; + upTo = fontInfo.mFont.canDisplayUpTo(text, i, i + charCount); + if (upTo == -1) { + // draw that char + g.setFont(fontInfo.mFont); + g.drawChars(text, i, charCount, (int)x, (int)y); + + // update x + x += fontInfo.mMetrics.charsWidth(text, i, charCount); + + // update the index in the text, and move on + i += charCount; + foundFont = true; + break; + + } + } + + // in case no font can display the char, display it with the main font. + // (it'll put a square probably) + if (foundFont == false) { + int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1; + + g.setFont(mainFont.mFont); + g.drawChars(text, i, charCount, (int)x, (int)y); + + // measure it to advance x + x += mainFont.mMetrics.charsWidth(text, i, charCount); + + // and move to the next chars. + i += charCount; + } + } + } + } finally { + g.dispose(); + } } /* (non-Javadoc) diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint.java b/tools/layoutlib/bridge/src/android/graphics/Paint.java index ade07d6..86de56b 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Paint.java +++ b/tools/layoutlib/bridge/src/android/graphics/Paint.java @@ -26,6 +26,9 @@ import java.awt.Toolkit; import java.awt.font.FontRenderContext; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; /** * A paint implementation overridden by the LayoutLib bridge. @@ -33,17 +36,28 @@ import java.awt.geom.Rectangle2D; public class Paint extends _Original_Paint { private int mColor = 0xFFFFFFFF; + private float mStrokeWidth = 1.f; private float mTextSize = 20; private float mScaleX = 1; private float mSkewX = 0; private Align mAlign = Align.LEFT; private Style mStyle = Style.FILL; + private float mStrokeMiter = 4.0f; + private Cap mCap = Cap.BUTT; + private Join mJoin = Join.MITER; private int mFlags = 0; - - private Font mFont; + + /** + * Class associating a {@link Font} and it's {@link java.awt.FontMetrics}. + */ + public static final class FontInfo { + Font mFont; + java.awt.FontMetrics mMetrics; + } + + private List<FontInfo> mFonts; private final FontRenderContext mFontContext = new FontRenderContext( new AffineTransform(), true, true); - private java.awt.FontMetrics mMetrics; @SuppressWarnings("hiding") public static final int ANTI_ALIAS_FLAG = _Original_Paint.ANTI_ALIAS_FLAG; @@ -65,11 +79,11 @@ public class Paint extends _Original_Paint { public static final int DEV_KERN_TEXT_FLAG = _Original_Paint.DEV_KERN_TEXT_FLAG; public static class FontMetrics extends _Original_Paint.FontMetrics { - } + } public static class FontMetricsInt extends _Original_Paint.FontMetricsInt { } - + /** * The Style specifies if the primitive being drawn is filled, * stroked, or both (in the same color). The default is FILL. @@ -91,7 +105,7 @@ public class Paint extends _Original_Paint { * the paint. */ FILL_AND_STROKE (2); - + Style(int nativeInt) { this.nativeInt = nativeInt; } @@ -117,7 +131,7 @@ public class Paint extends _Original_Paint { * end of the path. */ SQUARE (2); - + private Cap(int nativeInt) { this.nativeInt = nativeInt; } @@ -141,7 +155,7 @@ public class Paint extends _Original_Paint { * The outer edges of a join meet with a straight line */ BEVEL (2); - + private Join(int nativeInt) { this.nativeInt = nativeInt; } @@ -165,7 +179,7 @@ public class Paint extends _Original_Paint { * The text is drawn to the left of the x,y origin */ RIGHT (2); - + private Align(int nativeInt) { this.nativeInt = nativeInt; } @@ -185,43 +199,61 @@ public class Paint extends _Original_Paint { set(paint); initFont(); } - + @Override public void finalize() throws Throwable { // pass } - + + @Override + public void reset() { + super.reset(); + } + /** - * Returns the {@link Font} object. + * Returns the list of {@link Font} objects. The first item is the main font, the rest + * are fall backs for characters not present in the main font. */ - public Font getFont() { - return mFont; + public List<FontInfo> getFonts() { + return mFonts; } - + private void initFont() { mTypeface = Typeface.DEFAULT; updateFontObject(); } - + /** * Update the {@link Font} object from the typeface, text size and scaling */ + @SuppressWarnings("deprecation") private void updateFontObject() { if (mTypeface != null) { - // get the typeface font object, and get our font object from it, based on the current size - mFont = mTypeface.getFont().deriveFont(mTextSize); - if (mScaleX != 1.0 || mSkewX != 0) { - // TODO: support skew - mFont = mFont.deriveFont(new AffineTransform( - mScaleX, mSkewX, 0, 0, 1, 0)); + // Get the fonts from the TypeFace object. + List<Font> fonts = mTypeface.getFonts(); + + // 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) { + FontInfo info = new FontInfo(); + info.mFont = font.deriveFont(mTextSize); + if (mScaleX != 1.0 || mSkewX != 0) { + // TODO: support skew + info.mFont = info.mFont.deriveFont(new AffineTransform( + mScaleX, mSkewX, 0, 0, 1, 0)); + } + info.mMetrics = Toolkit.getDefaultToolkit().getFontMetrics(info.mFont); + + infoList.add(info); } - - mMetrics = Toolkit.getDefaultToolkit().getFontMetrics(mFont); + + mFonts = Collections.unmodifiableList(infoList); } } - + //---------------------------------------- - + public void set(Paint src) { if (this != src) { mColor = src.mColor; @@ -237,6 +269,11 @@ public class Paint extends _Original_Paint { } @Override + public void setCompatibilityScaling(float factor) { + super.setCompatibilityScaling(factor); + } + + @Override public int getFlags() { return mFlags; } @@ -245,7 +282,47 @@ public class Paint extends _Original_Paint { public void setFlags(int flags) { mFlags = flags; } - + + @Override + public boolean isAntiAlias() { + return super.isAntiAlias(); + } + + @Override + public boolean isDither() { + return super.isDither(); + } + + @Override + public boolean isLinearText() { + return super.isLinearText(); + } + + @Override + public boolean isStrikeThruText() { + return super.isStrikeThruText(); + } + + @Override + public boolean isUnderlineText() { + return super.isUnderlineText(); + } + + @Override + public boolean isFakeBoldText() { + return super.isFakeBoldText(); + } + + @Override + public boolean isSubpixelText() { + return super.isSubpixelText(); + } + + @Override + public boolean isFilterBitmap() { + return super.isFilterBitmap(); + } + /** * Return the font's recommended interline spacing, given the Paint's * settings for typeface, textSize, etc. If metrics is not null, return the @@ -256,39 +333,41 @@ public class Paint extends _Original_Paint { * @return the font's recommended interline spacing. */ public float getFontMetrics(FontMetrics metrics) { - if (mMetrics != null) { + if (mFonts.size() > 0) { + java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics; if (metrics != null) { - // ascent stuff should be negatif, but awt returns them as positive. - metrics.top = - mMetrics.getMaxAscent(); - metrics.ascent = - mMetrics.getAscent(); - metrics.descent = mMetrics.getDescent(); - metrics.bottom = mMetrics.getMaxDescent(); - metrics.leading = mMetrics.getLeading(); + // Android expects negative ascent so we invert the value from Java. + metrics.top = - javaMetrics.getMaxAscent(); + metrics.ascent = - javaMetrics.getAscent(); + metrics.descent = javaMetrics.getDescent(); + metrics.bottom = javaMetrics.getMaxDescent(); + metrics.leading = javaMetrics.getLeading(); } - - return mMetrics.getHeight(); + + return javaMetrics.getHeight(); } - + return 0; } public int getFontMetricsInt(FontMetricsInt metrics) { - if (mMetrics != null) { + if (mFonts.size() > 0) { + java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics; if (metrics != null) { - // ascent stuff should be negatif, but awt returns them as positive. - metrics.top = - mMetrics.getMaxAscent(); - metrics.ascent = - mMetrics.getAscent(); - metrics.descent = mMetrics.getDescent(); - metrics.bottom = mMetrics.getMaxDescent(); - metrics.leading = mMetrics.getLeading(); + // Android expects negative ascent so we invert the value from Java. + metrics.top = - javaMetrics.getMaxAscent(); + metrics.ascent = - javaMetrics.getAscent(); + metrics.descent = javaMetrics.getDescent(); + metrics.bottom = javaMetrics.getMaxDescent(); + metrics.leading = javaMetrics.getLeading(); } - - return mMetrics.getHeight(); + + return javaMetrics.getHeight(); } - + return 0; } - + /** * Reimplemented to return Paint.FontMetrics instead of _Original_Paint.FontMetrics */ @@ -297,7 +376,7 @@ public class Paint extends _Original_Paint { getFontMetrics(fm); return fm; } - + /** * Reimplemented to return Paint.FontMetricsInt instead of _Original_Paint.FontMetricsInt */ @@ -311,16 +390,14 @@ public class Paint extends _Original_Paint { @Override public float getFontMetrics(_Original_Paint.FontMetrics metrics) { - // TODO implement if needed throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); } @Override public int getFontMetricsInt(_Original_Paint.FontMetricsInt metrics) { - // TODO implement if needed throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); } - + @Override public Typeface setTypeface(Typeface typeface) { if (typeface != null) { @@ -328,12 +405,17 @@ public class Paint extends _Original_Paint { } else { mTypeface = Typeface.DEFAULT; } - + updateFontObject(); return typeface; } - + + @Override + public Typeface getTypeface() { + return super.getTypeface(); + } + @Override public int getColor() { return mColor; @@ -344,17 +426,21 @@ public class Paint extends _Original_Paint { mColor = color; } + @Override + public void setARGB(int a, int r, int g, int b) { + super.setARGB(a, r, g, b); + } @Override public void setAlpha(int alpha) { mColor = (alpha << 24) | (mColor & 0x00FFFFFF); } - + @Override public int getAlpha() { return mColor >>> 24; } - + /** * Set or clear the shader object. * <p /> @@ -369,6 +455,11 @@ public class Paint extends _Original_Paint { return mShader = shader; } + @Override + public Shader getShader() { + return super.getShader(); + } + /** * Set or clear the paint's colorfilter, returning the parameter. * @@ -377,13 +468,15 @@ public class Paint extends _Original_Paint { */ @Override public ColorFilter setColorFilter(ColorFilter filter) { - int filterNative = 0; - if (filter != null) - filterNative = filter.native_instance; mColorFilter = filter; return filter; } + @Override + public ColorFilter getColorFilter() { + return super.getColorFilter(); + } + /** * Set or clear the xfermode object. * <p /> @@ -397,50 +490,172 @@ public class Paint extends _Original_Paint { public Xfermode setXfermode(Xfermode xfermode) { return mXfermode = xfermode; } - + + @Override + public Xfermode getXfermode() { + return super.getXfermode(); + } + + @Override + public Rasterizer setRasterizer(Rasterizer rasterizer) { + mRasterizer = rasterizer; + return rasterizer; + } + + @Override + public Rasterizer getRasterizer() { + return super.getRasterizer(); + } + + @Override + public void setShadowLayer(float radius, float dx, float dy, int color) { + // TODO Auto-generated method stub + } + + @Override + public void clearShadowLayer() { + super.clearShadowLayer(); + } + public void setTextAlign(Align align) { mAlign = align; } - + @Override public void setTextAlign(android.graphics._Original_Paint.Align align) { - // TODO implement if needed throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); } - + public Align getTextAlign() { return mAlign; } - + public void setStyle(Style style) { mStyle = style; } @Override public void setStyle(android.graphics._Original_Paint.Style style) { - // TODO implement if needed throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); } public Style getStyle() { return mStyle; } - + @Override public void setDither(boolean dither) { mFlags |= dither ? DITHER_FLAG : ~DITHER_FLAG; } - + @Override public void setAntiAlias(boolean aa) { mFlags |= aa ? ANTI_ALIAS_FLAG : ~ANTI_ALIAS_FLAG; } - + @Override public void setFakeBoldText(boolean flag) { mFlags |= flag ? FAKE_BOLD_TEXT_FLAG : ~FAKE_BOLD_TEXT_FLAG; } + @Override + public void setLinearText(boolean flag) { + mFlags |= flag ? LINEAR_TEXT_FLAG : ~LINEAR_TEXT_FLAG; + } + + @Override + public void setSubpixelText(boolean flag) { + mFlags |= flag ? SUBPIXEL_TEXT_FLAG : ~SUBPIXEL_TEXT_FLAG; + } + + @Override + public void setUnderlineText(boolean flag) { + mFlags |= flag ? UNDERLINE_TEXT_FLAG : ~UNDERLINE_TEXT_FLAG; + } + + @Override + public void setStrikeThruText(boolean flag) { + mFlags |= flag ? STRIKE_THRU_TEXT_FLAG : ~STRIKE_THRU_TEXT_FLAG; + } + + @Override + public void setFilterBitmap(boolean flag) { + mFlags |= flag ? FILTER_BITMAP_FLAG : ~FILTER_BITMAP_FLAG; + } + + @Override + public float getStrokeWidth() { + return mStrokeWidth; + } + + @Override + public void setStrokeWidth(float width) { + mStrokeWidth = width; + } + + @Override + public float getStrokeMiter() { + return mStrokeMiter; + } + + @Override + public void setStrokeMiter(float miter) { + mStrokeMiter = miter; + } + + @Override + public void setStrokeCap(android.graphics._Original_Paint.Cap cap) { + throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); + } + + public void setStrokeCap(Cap cap) { + mCap = cap; + } + + public Cap getStrokeCap() { + return mCap; + } + + @Override + public void setStrokeJoin(android.graphics._Original_Paint.Join join) { + throw new UnsupportedOperationException("CALL TO PARENT FORBIDDEN"); + } + + public void setStrokeJoin(Join join) { + mJoin = join; + } + + public Join getStrokeJoin() { + return mJoin; + } + + @Override + public boolean getFillPath(Path src, Path dst) { + return super.getFillPath(src, dst); + } + + @Override + public PathEffect setPathEffect(PathEffect effect) { + mPathEffect = effect; + return effect; + } + + @Override + public PathEffect getPathEffect() { + return super.getPathEffect(); + } + + @Override + public MaskFilter setMaskFilter(MaskFilter maskfilter) { + mMaskFilter = maskfilter; + return maskfilter; + } + + @Override + public MaskFilter getMaskFilter() { + return super.getMaskFilter(); + } + /** * Return the paint's text size. * @@ -459,7 +674,7 @@ public class Paint extends _Original_Paint { @Override public void setTextSize(float textSize) { mTextSize = textSize; - + updateFontObject(); } @@ -484,7 +699,7 @@ public class Paint extends _Original_Paint { @Override public void setTextScaleX(float scaleX) { mScaleX = scaleX; - + updateFontObject(); } @@ -508,10 +723,15 @@ public class Paint extends _Original_Paint { @Override public void setTextSkewX(float skewX) { mSkewX = skewX; - + updateFontObject(); } + @Override + public float getFontSpacing() { + return super.getFontSpacing(); + } + /** * Return the distance above (negative) the baseline (ascent) based on the * current typeface and text size. @@ -521,11 +741,12 @@ public class Paint extends _Original_Paint { */ @Override public float ascent() { - if (mMetrics != null) { - // ascent stuff should be negatif, but awt returns them as positive. - return - mMetrics.getAscent(); + if (mFonts.size() > 0) { + java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics; + // Android expects negative ascent so we invert the value from Java. + return - javaMetrics.getAscent(); } - + return 0; } @@ -538,13 +759,14 @@ public class Paint extends _Original_Paint { */ @Override public float descent() { - if (mMetrics != null) { - return mMetrics.getDescent(); + if (mFonts.size() > 0) { + java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics; + return javaMetrics.getDescent(); } - + return 0; } - + /** * Return the width of the text. * @@ -555,12 +777,57 @@ public class Paint extends _Original_Paint { */ @Override public float measureText(char[] text, int index, int count) { - if (mFont != null && text != null && text.length > 0) { - Rectangle2D bounds = mFont.getStringBounds(text, index, index + count, mFontContext); - - return (float)bounds.getWidth(); + // WARNING: the logic in this method is similar to Canvas.drawText. + // Any change to this method should be reflected in Canvas.drawText + if (mFonts.size() > 0) { + FontInfo mainFont = mFonts.get(0); + int i = index; + int lastIndex = index + count; + float total = 0f; + while (i < lastIndex) { + // always start with the main font. + int upTo = mainFont.mFont.canDisplayUpTo(text, i, lastIndex); + if (upTo == -1) { + // shortcut to exit + return total + mainFont.mMetrics.charsWidth(text, i, lastIndex - i); + } else if (upTo > 0) { + total += mainFont.mMetrics.charsWidth(text, i, upTo - i); + i = upTo; + // don't call continue at this point. Since it is certain the main font + // cannot display the font a index upTo (now ==i), we move on to the + // fallback fonts directly. + } + + // no char supported, attempt to read the next char(s) with the + // fallback font. In this case we only test the first character + // and then go back to test with the main font. + // Special test for 2-char characters. + boolean foundFont = false; + for (int f = 1 ; f < mFonts.size() ; f++) { + FontInfo fontInfo = mFonts.get(f); + + // need to check that the font can display the character. We test + // differently if the char is a high surrogate. + int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1; + upTo = fontInfo.mFont.canDisplayUpTo(text, i, i + charCount); + if (upTo == -1) { + total += fontInfo.mMetrics.charsWidth(text, i, charCount); + i += charCount; + foundFont = true; + break; + + } + } + + // in case no font can display the char, measure it with the main font. + if (foundFont == false) { + int size = Character.isHighSurrogate(text[i]) ? 2 : 1; + total += mainFont.mMetrics.charsWidth(text, i, size); + i += size; + } + } } - + return 0; } @@ -587,7 +854,7 @@ public class Paint extends _Original_Paint { public float measureText(String text) { return measureText(text.toCharArray(), 0, text.length()); } - + /* * re-implement to call SpannableStringBuilder.measureText with a Paint object * instead of an _Original_Paint @@ -611,7 +878,7 @@ public class Paint extends _Original_Paint { TemporaryBuffer.recycle(buf); return result; } - + /** * Measure the text, stopping early if the measured width exceeds maxWidth. * Return the number of chars that were measured, and if measuredWidth is @@ -633,7 +900,7 @@ public class Paint extends _Original_Paint { public int breakText(char[] text, int index, int count, float maxWidth, float[] measuredWidth) { int inc = count > 0 ? 1 : -1; - + int measureIndex = 0; float measureAcc = 0; for (int i = index ; i != index + count ; i += inc, measureIndex++) { @@ -645,23 +912,23 @@ public class Paint extends _Original_Paint { start = index; end = i; } - + // measure from start to end float res = measureText(text, start, end - start + 1); - + if (measuredWidth != null) { measuredWidth[measureIndex] = res; } - + measureAcc += res; if (res > maxWidth) { // we should not return this char index, but since it's 0-based and we need // to return a count, we simply return measureIndex; return measureIndex; } - + } - + return measureIndex; } @@ -690,6 +957,28 @@ public class Paint extends _Original_Paint { } /** + * Measure the text, stopping early if the measured width exceeds maxWidth. + * Return the number of chars that were measured, and if measuredWidth is + * not null, return in it the actual width measured. + * + * @param text The text to measure + * @param start The offset into text to begin measuring at + * @param end The end of the text slice to measure. + * @param measureForwards If true, measure forwards, starting at start. + * Otherwise, measure backwards, starting with end. + * @param maxWidth The maximum width to accumulate. + * @param measuredWidth Optional. If not null, returns the actual width + * measured. + * @return The number of chars that were measured. Will always be <= + * abs(end - start). + */ + @Override + public int breakText(CharSequence text, int start, int end, boolean measureForwards, + float maxWidth, float[] measuredWidth) { + return super.breakText(text, start, end, measureForwards, maxWidth, measuredWidth); + } + + /** * Return the advance widths for the characters in the string. * * @param text The text to measure @@ -702,19 +991,35 @@ public class Paint extends _Original_Paint { @Override public int getTextWidths(char[] text, int index, int count, float[] widths) { - if (mMetrics != null) { + if (mFonts.size() > 0) { if ((index | count) < 0 || index + count > text.length || count > widths.length) { throw new ArrayIndexOutOfBoundsException(); } - + + // FIXME: handle multi-char characters. + // Need to figure out if the lengths of the width array takes into account + // multi-char characters. for (int i = 0; i < count; i++) { - widths[i] = mMetrics.charWidth(text[i + index]); + char c = text[i + index]; + boolean found = false; + for (FontInfo info : mFonts) { + if (info.mFont.canDisplay(c)) { + widths[i] = info.mMetrics.charWidth(c); + found = true; + break; + } + } + + if (found == false) { + // we stop there. + return i; + } } - + return count; } - + return 0; } @@ -736,10 +1041,10 @@ public class Paint extends _Original_Paint { if (end - start > widths.length) { throw new ArrayIndexOutOfBoundsException(); } - + return getTextWidths(text.toCharArray(), start, end - start, widths); } - + /* * re-implement to call SpannableStringBuilder.getTextWidths with a Paint object * instead of an _Original_Paint @@ -763,6 +1068,10 @@ public class Paint extends _Original_Paint { return result; } + @Override + public int getTextWidths(String text, float[] widths) { + return super.getTextWidths(text, widths); + } /** * Return the path (outline) for the specified text. @@ -782,13 +1091,13 @@ public class Paint extends _Original_Paint { float x, float y, Path path) { // TODO this is the ORIGINAL implementation. REPLACE AS NEEDED OR REMOVE - + if ((index | count) < 0 || index + count > text.length) { throw new ArrayIndexOutOfBoundsException(); } - + // TODO native_getTextPath(mNativePaint, text, index, count, x, y, path.ni()); - + throw new UnsupportedOperationException("IMPLEMENT AS NEEDED"); } @@ -811,10 +1120,10 @@ public class Paint extends _Original_Paint { if ((start | end | (end - start) | (text.length() - end)) < 0) { throw new IndexOutOfBoundsException(); } - + getTextPath(text.toCharArray(), start, end - start, x, y, path); } - + /** * Return in bounds (allocated by the caller) the smallest rectangle that * encloses all of the characters, with an implied origin at (0,0). @@ -833,10 +1142,10 @@ public class Paint extends _Original_Paint { if (bounds == null) { throw new NullPointerException("need bounds Rect"); } - + getTextBounds(text.toCharArray(), start, end - start, bounds); } - + /** * Return in bounds (allocated by the caller) the smallest rectangle that * encloses all of the characters, with an implied origin at (0,0). @@ -849,16 +1158,23 @@ public class Paint extends _Original_Paint { */ @Override public void getTextBounds(char[] text, int index, int count, Rect bounds) { - if (mFont != null) { + // FIXME + if (mFonts.size() > 0) { if ((index | count) < 0 || index + count > text.length) { throw new ArrayIndexOutOfBoundsException(); } if (bounds == null) { throw new NullPointerException("need bounds Rect"); } - - Rectangle2D rect = mFont.getStringBounds(text, index, index + count, mFontContext); + + FontInfo mainInfo = mFonts.get(0); + + Rectangle2D rect = mainInfo.mFont.getStringBounds(text, index, index + count, mFontContext); bounds.set(0, 0, (int)rect.getWidth(), (int)rect.getHeight()); } } + + public static void finalizer(int foo) { + // pass + } } diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface.java b/tools/layoutlib/bridge/src/android/graphics/Typeface.java index e878b04..af3adb5 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Typeface.java +++ b/tools/layoutlib/bridge/src/android/graphics/Typeface.java @@ -21,9 +21,12 @@ import com.android.layoutlib.bridge.FontLoader; import android.content.res.AssetManager; import java.awt.Font; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; /** - * Re-implementation of Typeface over java.awt + * Re-implementation of Typeface over java.awt */ public class Typeface { private static final String DEFAULT_FAMILY = "sans-serif"; @@ -46,11 +49,11 @@ public class Typeface { private static Typeface[] sDefaults; private static FontLoader mFontLoader; - + private final int mStyle; - private final Font mFont; + private final List<Font> mFonts; private final String mFamily; - + // Style public static final int NORMAL = _Original_Typeface.NORMAL; public static final int BOLD = _Original_Typeface.BOLD; @@ -58,12 +61,13 @@ public class Typeface { public static final int BOLD_ITALIC = _Original_Typeface.BOLD_ITALIC; /** - * Returns the underlying {@link Font} object. + * Returns the underlying {@link Font} objects. The first item in the list is the real + * font. Any other items are fallback fonts for characters not found in the first one. */ - public Font getFont() { - return mFont; + public List<Font> getFonts() { + return mFonts; } - + /** Returns the typeface's intrinsic style attributes */ public int getStyle() { return mStyle; @@ -94,9 +98,12 @@ public class Typeface { styleBuffer[0] = style; Font font = mFontLoader.getFont(familyName, styleBuffer); if (font != null) { - return new Typeface(familyName, styleBuffer[0], font); + ArrayList<Font> list = new ArrayList<Font>(); + list.add(font); + list.addAll(mFontLoader.getFallBackFonts()); + return new Typeface(familyName, styleBuffer[0], list); } - + return null; } @@ -115,7 +122,10 @@ public class Typeface { styleBuffer[0] = style; Font font = mFontLoader.getFont(family.mFamily, styleBuffer); if (font != null) { - return new Typeface(family.mFamily, styleBuffer[0], font); + ArrayList<Font> list = new ArrayList<Font>(); + list.add(font); + list.addAll(mFontLoader.getFallBackFonts()); + return new Typeface(family.mFamily, styleBuffer[0], list); } return null; @@ -129,7 +139,7 @@ public class Typeface { public static Typeface defaultFromStyle(int style) { return sDefaults[style]; } - + /** * Create a new typeface from the specified font data. * @param mgr The application's asset manager @@ -140,17 +150,17 @@ public class Typeface { return null; //return new Typeface(nativeCreateFromAsset(mgr, path)); } - + // don't allow clients to call this directly - private Typeface(String family, int style, Font f) { + private Typeface(String family, int style, List<Font> fonts) { mFamily = family; - mFont = f; + mFonts = Collections.unmodifiableList(fonts); mStyle = style; } - + public static void init(FontLoader fontLoader) { mFontLoader = fontLoader; - + DEFAULT = create(DEFAULT_FAMILY, NORMAL); DEFAULT_BOLD = create(DEFAULT_FAMILY, BOLD); SANS_SERIF = create("sans-serif", NORMAL); @@ -162,14 +172,14 @@ public class Typeface { create(DEFAULT_FAMILY, ITALIC), create(DEFAULT_FAMILY, BOLD_ITALIC), }; - + /* DEFAULT = create((String)null, 0); DEFAULT_BOLD = create((String)null, Typeface.BOLD); SANS_SERIF = create("sans-serif", 0); SERIF = create("serif", 0); MONOSPACE = create("monospace", 0); - + sDefaults = new Typeface[] { DEFAULT, DEFAULT_BOLD, diff --git a/tools/layoutlib/bridge/src/android/webkit/WebView.java b/tools/layoutlib/bridge/src/android/webkit/WebView.java index 42e4dfa..3b66188 100644 --- a/tools/layoutlib/bridge/src/android/webkit/WebView.java +++ b/tools/layoutlib/bridge/src/android/webkit/WebView.java @@ -240,13 +240,6 @@ public class WebView extends MockView { return null; } - public static synchronized PluginList getPluginList() { - return null; - } - - public void refreshPlugins(boolean reloadOpenPages) { - } - public View getZoomControls() { return null; } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java index 145a045..c455977 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java @@ -36,6 +36,7 @@ import android.graphics.Rect; import android.graphics.Region; import android.graphics.Typeface; import android.graphics.drawable.Drawable; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -312,6 +313,7 @@ public final class Bridge implements ILayoutBridge { } /* + * For compatilibty purposes, we implement the old deprecated version of computeLayout. * (non-Javadoc) * @see com.android.layoutlib.api.ILayoutBridge#computeLayout(com.android.layoutlib.api.IXmlPullParser, java.lang.Object, int, int, int, float, float, java.lang.String, boolean, java.util.Map, java.util.Map, com.android.layoutlib.api.IProjectCallback, com.android.layoutlib.api.ILayoutLog) */ @@ -321,6 +323,23 @@ public final class Bridge implements ILayoutBridge { Map<String, Map<String, IResourceValue>> projectResources, Map<String, Map<String, IResourceValue>> frameworkResources, IProjectCallback customViewLoader, ILayoutLog logger) { + return computeLayout(layoutDescription, projectKey, + screenWidth, screenHeight, false /* renderFullSize */, + density, xdpi, ydpi, themeName, isProjectTheme, + projectResources, frameworkResources, customViewLoader, logger); + } + + /* + * (non-Javadoc) + * @see com.android.layoutlib.api.ILayoutBridge#computeLayout(com.android.layoutlib.api.IXmlPullParser, java.lang.Object, int, int, boolean, int, float, float, java.lang.String, boolean, java.util.Map, java.util.Map, com.android.layoutlib.api.IProjectCallback, com.android.layoutlib.api.ILayoutLog) + */ + public ILayoutResult computeLayout(IXmlPullParser layoutDescription, Object projectKey, + int screenWidth, int screenHeight, boolean renderFullSize, + int density, float xdpi, float ydpi, + String themeName, boolean isProjectTheme, + Map<String, Map<String, IResourceValue>> projectResources, + Map<String, Map<String, IResourceValue>> frameworkResources, + IProjectCallback customViewLoader, ILayoutLog logger) { if (logger == null) { logger = sDefaultLogger; } @@ -341,6 +360,7 @@ public final class Bridge implements ILayoutBridge { try { // setup the display Metrics. DisplayMetrics metrics = new DisplayMetrics(); + metrics.densityDpi = density; metrics.density = density / (float) DisplayMetrics.DENSITY_DEFAULT; metrics.scaledDensity = metrics.density; metrics.widthPixels = screenWidth; @@ -388,20 +408,44 @@ public final class Bridge implements ILayoutBridge { // get the background drawable if (windowBackground != null) { - Drawable d = ResourceHelper.getDrawable(windowBackground.getValue(), + Drawable d = ResourceHelper.getDrawable(windowBackground, context, true /* isFramework */); root.setBackgroundDrawable(d); } - int w_spec = MeasureSpec.makeMeasureSpec(screenWidth, MeasureSpec.EXACTLY); - int h_spec = MeasureSpec.makeMeasureSpec(screenHeight - screenOffset, - MeasureSpec.EXACTLY); - // measure the views + int w_spec, h_spec; + + if (renderFullSize) { + // measure the full size needed by the layout. + w_spec = MeasureSpec.makeMeasureSpec(screenWidth, + MeasureSpec.UNSPECIFIED); // this lets us know the actual needed size + h_spec = MeasureSpec.makeMeasureSpec(screenHeight - screenOffset, + MeasureSpec.UNSPECIFIED); // this lets us know the actual needed size + view.measure(w_spec, h_spec); + + int neededWidth = root.getChildAt(0).getMeasuredWidth(); + if (neededWidth > screenWidth) { + screenWidth = neededWidth; + } + + int neededHeight = root.getChildAt(0).getMeasuredHeight(); + if (neededHeight > screenHeight - screenOffset) { + screenHeight = neededHeight + screenOffset; + } + } + + // remeasure with only the size we need + // This must always be done before the call to layout + w_spec = MeasureSpec.makeMeasureSpec(screenWidth, MeasureSpec.EXACTLY); + h_spec = MeasureSpec.makeMeasureSpec(screenHeight - screenOffset, + MeasureSpec.EXACTLY); view.measure(w_spec, h_spec); + + // now do the layout. view.layout(0, screenOffset, screenWidth, screenHeight); - // draw them + // draw the views Canvas canvas = new Canvas(screenWidth, screenHeight - screenOffset, logger); root.draw(canvas); @@ -1009,11 +1053,40 @@ public final class Bridge implements ILayoutBridge { // pass for now. } + @SuppressWarnings("unused") public void setInsets(IWindow window, int touchable, Rect contentInsets, Rect visibleInsets) { // pass for now. } + @SuppressWarnings("unused") + public void setWallpaperPosition(IBinder window, float x, float y, + float xStep, float yStep) { + // pass for now. + } + + @SuppressWarnings("unused") + public void wallpaperOffsetsComplete(IBinder window) { + // pass for now. + } + + @SuppressWarnings("unused") + public Bundle sendWallpaperCommand(IBinder window, String action, int x, int y, + int z, Bundle extras, boolean sync) { + // pass for now. + return null; + } + + @SuppressWarnings("unused") + public void wallpaperCommandComplete(IBinder window, Bundle result) { + // pass for now. + } + + @SuppressWarnings("unused") + public void closeSystemDialogs(String reason) { + // pass for now. + } + public IBinder asBinder() { // pass for now. return null; @@ -1041,12 +1114,12 @@ public final class Bridge implements ILayoutBridge { } @SuppressWarnings("unused") - public void dispatchPointer(MotionEvent arg0, long arg1) throws RemoteException { + public void dispatchPointer(MotionEvent arg0, long arg1, boolean arg2) throws RemoteException { // pass for now. } @SuppressWarnings("unused") - public void dispatchTrackball(MotionEvent arg0, long arg1) throws RemoteException { + public void dispatchTrackball(MotionEvent arg0, long arg1, boolean arg2) throws RemoteException { // pass for now. } @@ -1067,6 +1140,23 @@ public final class Bridge implements ILayoutBridge { // pass for now. } + @SuppressWarnings("unused") + public void dispatchWallpaperOffsets(float x, float y, float xStep, float yStep, + boolean sync) { + // pass for now. + } + + @SuppressWarnings("unused") + public void dispatchWallpaperCommand(String action, int x, int y, + int z, Bundle extras, boolean sync) { + // pass for now. + } + + @SuppressWarnings("unused") + public void closeSystemDialogs(String reason) { + // pass for now. + } + public IBinder asBinder() { // pass for now. return null; diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java index f48c8db..1e9f573 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java @@ -27,6 +27,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.IntentSender; import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; @@ -1098,6 +1099,13 @@ public final class BridgeContext extends Context { } @Override + public void sendStickyOrderedBroadcast(Intent intent, + BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, + Bundle initialExtras) { + // TODO Auto-generated method stub + } + + @Override public void setTheme(int arg0) { // TODO Auto-generated method stub @@ -1124,6 +1132,13 @@ public final class BridgeContext extends Context { } @Override + public void startIntentSender(IntentSender intent, + Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) + throws IntentSender.SendIntentException { + // TODO Auto-generated method stub + } + + @Override public boolean startInstrumentation(ComponentName arg0, String arg1, Bundle arg2) { // TODO Auto-generated method stub diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeResources.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeResources.java index 2b0100b..1fafef4 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeResources.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeResources.java @@ -124,7 +124,7 @@ public final class BridgeResources extends Resources { IResourceValue value = getResourceValue(id, mPlatformResourceFlag); if (value != null) { - return ResourceHelper.getDrawable(value.getValue(), mContext, value.isFramework()); + return ResourceHelper.getDrawable(value, mContext, value.isFramework()); } // id was not found or not resolved. Throw a NotFoundException. diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeTypedArray.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeTypedArray.java index 10421de..957f737 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeTypedArray.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeTypedArray.java @@ -659,8 +659,9 @@ public final class BridgeTypedArray extends TypedArray { return null; } - String value = mData[index].getValue(); - if (value == null || BridgeConstants.REFERENCE_NULL.equals(value)) { + IResourceValue value = mData[index]; + String stringValue = value.getValue(); + if (stringValue == null || BridgeConstants.REFERENCE_NULL.equals(stringValue)) { return null; } @@ -672,7 +673,8 @@ public final class BridgeTypedArray extends TypedArray { // looks like we were unable to resolve the drawable mContext.getLogger().warning(String.format( - "Unable to resolve drawable \"%1$s\" in attribute \"%2$s\"", value, mNames[index])); + "Unable to resolve drawable \"%1$s\" in attribute \"%2$s\"", stringValue, + mNames[index])); return null; } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/FontLoader.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/FontLoader.java index 1bdd1cc..801503b 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/FontLoader.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/FontLoader.java @@ -29,6 +29,7 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -47,14 +48,13 @@ import javax.xml.parsers.SAXParserFactory; */ public final class FontLoader { private static final String FONTS_DEFINITIONS = "fonts.xml"; - + private static final String NODE_FONTS = "fonts"; private static final String NODE_FONT = "font"; private static final String NODE_NAME = "name"; - - private static final String ATTR_TTF = "ttf"; + private static final String NODE_FALLBACK = "fallback"; - private static final String[] NODE_LEVEL = { NODE_FONTS, NODE_FONT, NODE_NAME }; + private static final String ATTR_TTF = "ttf"; private static final String FONT_EXT = ".ttf"; @@ -62,7 +62,7 @@ public final class FontLoader { private static final String[] FONT_STYLE_BOLD = { "-Bold" }; private static final String[] FONT_STYLE_ITALIC = { "-Italic" }; private static final String[] FONT_STYLE_BOLDITALIC = { "-BoldItalic" }; - + // list of font style, in the order matching the Typeface Font style private static final String[][] FONT_STYLES = { FONT_STYLE_DEFAULT, @@ -70,23 +70,25 @@ public final class FontLoader { FONT_STYLE_ITALIC, FONT_STYLE_BOLDITALIC }; - + private final Map<String, String> mFamilyToTtf = new HashMap<String, String>(); private final Map<String, Map<Integer, Font>> mTtfToFontMap = new HashMap<String, Map<Integer, Font>>(); - + + private List<Font> mFallBackFonts = null; + public static FontLoader create(String fontOsLocation) { try { SAXParserFactory parserFactory = SAXParserFactory.newInstance(); parserFactory.setNamespaceAware(true); - + SAXParser parser = parserFactory.newSAXParser(); File f = new File(fontOsLocation + File.separator + FONTS_DEFINITIONS); - + FontDefinitionParser definitionParser = new FontDefinitionParser( fontOsLocation + File.separator); parser.parse(new FileInputStream(f), definitionParser); - + return definitionParser.getFontLoader(); } catch (ParserConfigurationException e) { // return null below @@ -101,12 +103,35 @@ public final class FontLoader { return null; } - private FontLoader(List<FontInfo> fontList) { + private FontLoader(List<FontInfo> fontList, List<String> fallBackList) { for (FontInfo info : fontList) { for (String family : info.families) { mFamilyToTtf.put(family, info.ttf); } } + + ArrayList<Font> list = new ArrayList<Font>(); + for (String path : fallBackList) { + File f = new File(path + FONT_EXT); + if (f.isFile()) { + try { + Font font = Font.createFont(Font.TRUETYPE_FONT, f); + if (font != null) { + list.add(font); + } + } catch (FontFormatException e) { + // skip this font name + } catch (IOException e) { + // skip this font name + } + } + } + + mFallBackFonts = Collections.unmodifiableList(list); + } + + public List<Font> getFallBackFonts() { + return mFallBackFonts; } public synchronized Font getFont(String family, int[] style) { @@ -116,25 +141,25 @@ public final class FontLoader { // get the ttf name from the family String ttf = mFamilyToTtf.get(family); - + if (ttf == null) { return null; } - + // get the font from the ttf Map<Integer, Font> styleMap = mTtfToFontMap.get(ttf); - + if (styleMap == null) { styleMap = new HashMap<Integer, Font>(); mTtfToFontMap.put(ttf, styleMap); } - + Font f = styleMap.get(style); - + if (f != null) { return f; } - + // if it doesn't exist, we create it, and we can't, we try with a simpler style switch (style[0]) { case Typeface.NORMAL: @@ -178,7 +203,7 @@ public final class FontLoader { private Font getFont(String ttf, String[] fontFileSuffix) { for (String suffix : fontFileSuffix) { String name = ttf + suffix + FONT_EXT; - + File f = new File(name); if (f.isFile()) { try { @@ -193,14 +218,14 @@ public final class FontLoader { } } } - + return null; } private final static class FontInfo { String ttf; final Set<String> families; - + FontInfo() { families = new HashSet<String>(); } @@ -208,19 +233,19 @@ public final class FontLoader { private final static class FontDefinitionParser extends DefaultHandler { private final String mOsFontsLocation; - - private int mDepth = 0; + private FontInfo mFontInfo = null; private final StringBuilder mBuilder = new StringBuilder(); - private final List<FontInfo> mFontList = new ArrayList<FontInfo>(); - + private List<FontInfo> mFontList; + private List<String> mFallBackList; + private FontDefinitionParser(String osFontsLocation) { super(); mOsFontsLocation = osFontsLocation; } - + FontLoader getFontLoader() { - return new FontLoader(mFontList); + return new FontLoader(mFontList, mFallBackList); } /* (non-Javadoc) @@ -229,10 +254,11 @@ public final class FontLoader { @Override public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException { - if (localName.equals(NODE_LEVEL[mDepth])) { - mDepth++; - - if (mDepth == 2) { // font level. + if (NODE_FONTS.equals(localName)) { + mFontList = new ArrayList<FontInfo>(); + mFallBackList = new ArrayList<String>(); + } else if (NODE_FONT.equals(localName)) { + if (mFontList != null) { String ttf = attributes.getValue(ATTR_TTF); if (ttf != null) { mFontInfo = new FontInfo(); @@ -240,42 +266,50 @@ public final class FontLoader { mFontList.add(mFontInfo); } } + } else if (NODE_NAME.equals(localName)) { + // do nothing, we'll handle the name in the endElement + } else if (NODE_FALLBACK.equals(localName)) { + if (mFallBackList != null) { + String ttf = attributes.getValue(ATTR_TTF); + if (ttf != null) { + mFallBackList.add(mOsFontsLocation + ttf); + } + } } + mBuilder.setLength(0); + super.startElement(uri, localName, name, attributes); } /* (non-Javadoc) * @see org.xml.sax.helpers.DefaultHandler#characters(char[], int, int) */ - @SuppressWarnings("unused") @Override public void characters(char[] ch, int start, int length) throws SAXException { - if (mFontInfo != null) { - mBuilder.append(ch, start, length); - } + mBuilder.append(ch, start, length); } /* (non-Javadoc) * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String, java.lang.String) */ - @SuppressWarnings("unused") @Override public void endElement(String uri, String localName, String name) throws SAXException { - if (localName.equals(NODE_LEVEL[mDepth-1])) { - mDepth--; - if (mDepth == 2) { // end of a <name> node - if (mFontInfo != null) { - String family = trimXmlWhitespaces(mBuilder.toString()); - mFontInfo.families.add(family); - mBuilder.setLength(0); - } - } else if (mDepth == 1) { // end of a <font> node - mFontInfo = null; + if (NODE_FONTS.equals(localName)) { + // top level, do nothing + } else if (NODE_FONT.equals(localName)) { + mFontInfo = null; + } else if (NODE_NAME.equals(localName)) { + // handle a new name for an existing Font Info + if (mFontInfo != null) { + String family = trimXmlWhitespaces(mBuilder.toString()); + mFontInfo.families.add(family); } + } else if (NODE_FALLBACK.equals(localName)) { + // nothing to do here. } } - + private String trimXmlWhitespaces(String value) { if (value == null) { return null; @@ -283,7 +317,7 @@ public final class FontLoader { // look for carriage return and replace all whitespace around it by just 1 space. int index; - + while ((index = value.indexOf('\n')) != -1) { // look for whitespace on each side int left = index - 1; @@ -294,7 +328,7 @@ public final class FontLoader { break; } } - + int right = index + 1; int count = value.length(); while (right < count) { @@ -304,7 +338,7 @@ public final class FontLoader { break; } } - + // remove all between left and right (non inclusive) and replace by a single space. String leftString = null; if (left >= 0) { @@ -314,7 +348,7 @@ public final class FontLoader { if (right < count) { rightString = value.substring(right); } - + if (leftString != null) { value = leftString; if (rightString != null) { @@ -324,24 +358,24 @@ public final class FontLoader { value = rightString != null ? rightString : ""; } } - + // now we un-escape the string int length = value.length(); char[] buffer = value.toCharArray(); - + for (int i = 0 ; i < length ; i++) { if (buffer[i] == '\\') { if (buffer[i+1] == 'n') { // replace the char with \n buffer[i+1] = '\n'; } - + // offset the rest of the buffer since we go from 2 to 1 char System.arraycopy(buffer, i+1, buffer, i, length - i - 1); length--; } } - + return new String(buffer, 0, length); } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/ResourceHelper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/ResourceHelper.java index fbdf8dc..3d0dd73 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/ResourceHelper.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/ResourceHelper.java @@ -16,6 +16,9 @@ package com.android.layoutlib.bridge; +import com.android.layoutlib.api.IDensityBasedResourceValue; +import com.android.layoutlib.api.IResourceValue; +import com.android.layoutlib.api.IDensityBasedResourceValue.Density; import com.android.ninepatch.NinePatch; import org.kxml2.io.KXmlParser; @@ -40,7 +43,7 @@ import java.util.regex.Pattern; * Helper class to provide various convertion method used in handling android resources. */ public final class ResourceHelper { - + private final static Pattern sFloatPattern = Pattern.compile("(-?[0-9]+(?:\\.[0-9]+)?)(.*)"); private final static float[] sFloatOut = new float[1]; @@ -59,12 +62,12 @@ public final class ResourceHelper { } value = value.substring(1); - + // make sure it's not longer than 32bit if (value.length() > 8) { throw new NumberFormatException(); } - + if (value.length() == 3) { // RGB format char[] color = new char[8]; color[0] = color[1] = 'F'; @@ -84,7 +87,7 @@ public final class ResourceHelper { } // this is a RRGGBB or AARRGGBB value - + // Integer.parseInt will fail to parse strings like "ff191919", so we use // a Long, but cast the result back into an int, since we know that we're only // dealing with 32 bit values. @@ -96,28 +99,30 @@ public final class ResourceHelper { /** * Returns a drawable from the given value. - * @param value The value. A path to a 9 patch, a bitmap or a xml based drawable, + * @param value The value that contains a path to a 9 patch, a bitmap or a xml based drawable, * or an hexadecimal color - * @param context + * @param context * @param isFramework indicates whether the resource is a framework resources. * Framework resources are cached, and loaded only once. */ - public static Drawable getDrawable(String value, BridgeContext context, boolean isFramework) { + public static Drawable getDrawable(IResourceValue value, BridgeContext context, boolean isFramework) { Drawable d = null; - - String lowerCaseValue = value.toLowerCase(); + + String stringValue = value.getValue(); + + String lowerCaseValue = stringValue.toLowerCase(); if (lowerCaseValue.endsWith(NinePatch.EXTENSION_9PATCH)) { - File f = new File(value); - if (f.isFile()) { - NinePatch ninePatch = Bridge.getCached9Patch(value, + File file = new File(stringValue); + if (file.isFile()) { + NinePatch ninePatch = Bridge.getCached9Patch(stringValue, isFramework ? null : context.getProjectKey()); - + if (ninePatch == null) { try { - ninePatch = NinePatch.load(new File(value).toURL(), false /* convert */); - - Bridge.setCached9Patch(value, ninePatch, + ninePatch = NinePatch.load(file.toURL(), false /* convert */); + + Bridge.setCached9Patch(stringValue, ninePatch, isFramework ? null : context.getProjectKey()); } catch (MalformedURLException e) { // URL is wrong, we'll return null below @@ -125,23 +130,23 @@ public final class ResourceHelper { // failed to read the file, we'll return null below. } } - + if (ninePatch != null) { return new NinePatchDrawable(ninePatch); } } - + return null; } else if (lowerCaseValue.endsWith(".xml")) { // create a blockparser for the file - File f = new File(value); + File f = new File(stringValue); if (f.isFile()) { try { // let the framework inflate the Drawable from the XML file. KXmlParser parser = new KXmlParser(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); parser.setInput(new FileReader(f)); - + d = Drawable.createFromXml(context.getResources(), // FIXME: we need to know if this resource is platform or not new BridgeXmlBlockParser(parser, context, false)); @@ -157,19 +162,43 @@ public final class ResourceHelper { return null; } else { - File bmpFile = new File(value); + File bmpFile = new File(stringValue); if (bmpFile.isFile()) { try { - Bitmap bitmap = Bridge.getCachedBitmap(value, + Bitmap bitmap = Bridge.getCachedBitmap(stringValue, isFramework ? null : context.getProjectKey()); - + if (bitmap == null) { bitmap = new Bitmap(bmpFile); - Bridge.setCachedBitmap(value, bitmap, + try { + bitmap.setDensity(Density.MEDIUM.getValue()); + } catch (NoClassDefFoundError error) { + // look like we're running in an older version of ADT that doesn't + // include the new layoutlib_api. Let's just ignore this, the drawing + // will just be wrong. + } + Bridge.setCachedBitmap(stringValue, bitmap, isFramework ? null : context.getProjectKey()); } - - return new BitmapDrawable(bitmap); + + try { + if (value instanceof IDensityBasedResourceValue) { + Density density = ((IDensityBasedResourceValue)value).getDensity(); + if (density != Density.MEDIUM) { + // create a copy of the bitmap + bitmap = Bitmap.createBitmap(bitmap); + + // apply the density + bitmap.setDensity(density.getValue()); + } + } + } catch (NoClassDefFoundError error) { + // look like we're running in an older version of ADT that doesn't include + // the new layoutlib_api. Let's just ignore this, the drawing will just be + // wrong. + } + + return new BitmapDrawable(context.getResources(), bitmap); } catch (IOException e) { // we'll return null below // TODO: log the error. @@ -177,7 +206,7 @@ public final class ResourceHelper { } else { // attempt to get a color from the value try { - int color = getColor(value); + int color = getColor(stringValue); return new ColorDrawable(color); } catch (NumberFormatException e) { // we'll return null below. @@ -185,20 +214,20 @@ public final class ResourceHelper { } } } - + return null; } - + // ------- TypedValue stuff // This is taken from //device/libs/utils/ResourceTypes.cpp - + private static final class UnitEntry { String name; int type; int unit; float scale; - + UnitEntry(String name, int type, int unit, float scale) { this.name = name; this.type = type; @@ -218,7 +247,7 @@ public final class ResourceHelper { new UnitEntry("%", TypedValue.TYPE_FRACTION, TypedValue.COMPLEX_UNIT_FRACTION, 1.0f/100), new UnitEntry("%p", TypedValue.TYPE_FRACTION, TypedValue.COMPLEX_UNIT_FRACTION_PARENT, 1.0f/100), }; - + /** * Returns the raw value from the given string. * This object is only valid until the next call on to {@link ResourceHelper}. @@ -227,10 +256,10 @@ public final class ResourceHelper { if (stringToFloat(s, mValue)) { return mValue; } - + return null; } - + /** * Convert the string into a {@link TypedValue}. * @param s @@ -258,7 +287,7 @@ public final class ResourceHelper { if (buf[0] < '0' && buf[0] > '9' && buf[0] != '.') { return false; } - + // now look for the string that is after the float... Matcher m = sFloatPattern.matcher(s); if (m.matches()) { @@ -272,11 +301,11 @@ public final class ResourceHelper { // this shouldn't happen with the regexp above. return false; } - + if (end.length() > 0 && end.charAt(0) != ' ') { // Might be a unit... if (parseUnit(end, outValue, sFloatOut)) { - + f *= sFloatOut[0]; boolean neg = f < 0; if (neg) { @@ -312,17 +341,17 @@ public final class ResourceHelper { if (neg) { mantissa = (-mantissa) & TypedValue.COMPLEX_MANTISSA_MASK; } - outValue.data |= + outValue.data |= (radix<<TypedValue.COMPLEX_RADIX_SHIFT) | (mantissa<<TypedValue.COMPLEX_MANTISSA_SHIFT); return true; } return false; } - + // make sure it's only spaces at the end. end = end.trim(); - + if (end.length() == 0) { if (outValue != null) { outValue.type = TypedValue.TYPE_FLOAT; @@ -334,7 +363,7 @@ public final class ResourceHelper { return false; } - + private static boolean parseUnit(String str, TypedValue outValue, float[] outScale) { str = str.trim(); @@ -343,7 +372,7 @@ public final class ResourceHelper { outValue.type = unit.type; outValue.data = unit.unit << TypedValue.COMPLEX_UNIT_SHIFT; outScale[0] = unit.scale; - + return true; } } diff --git a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/AndroidGraphicsTests.java b/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/AndroidGraphicsTests.java index ac144e7..6e14e82 100644 --- a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/AndroidGraphicsTests.java +++ b/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/AndroidGraphicsTests.java @@ -24,7 +24,7 @@ import android.text.TextPaint; import junit.framework.TestCase; /** - * + * */ public class AndroidGraphicsTests extends TestCase { @@ -40,24 +40,24 @@ public class AndroidGraphicsTests extends TestCase { public void testMatrix() { Matrix m1 = new Matrix(); - - assertFalse(m1.isIdentity()); - + + assertTrue(m1.isIdentity()); + m1.setValues(new float[] { 1,0,0, 0,1,0, 0,0,1 }); assertTrue(m1.isIdentity()); - + Matrix m2 = new Matrix(m1); - + float[] v1 = new float[9]; float[] v2 = new float[9]; m1.getValues(v1); m2.getValues(v2); - + for (int i = 0 ; i < 9; i++) { assertEquals(v1[i], v2[i]); } } - + public void testPaint() { _Original_Paint o = new _Original_Paint(); assertNotNull(o); @@ -65,7 +65,7 @@ public class AndroidGraphicsTests extends TestCase { Paint p = new Paint(); assertNotNull(p); } - + public void textTextPaint() { TextPaint p = new TextPaint(); assertNotNull(p); diff --git a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/BridgeTest.java b/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/BridgeTest.java deleted file mode 100644 index e424f1d..0000000 --- a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/BridgeTest.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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; - -import com.android.layoutlib.api.ILayoutResult; -import com.android.layoutlib.api.IResourceValue; -import com.android.layoutlib.api.IStyleResourceValue; -import com.android.layoutlib.api.IXmlPullParser; -import com.android.layoutlib.api.ILayoutResult.ILayoutViewInfo; - -import org.kxml2.io.KXmlParser; -import org.xmlpull.v1.XmlPullParser; - -import java.io.File; -import java.io.FileReader; -import java.net.URL; -import java.util.HashMap; -import java.util.Map; - -import junit.framework.TestCase; - -public class BridgeTest extends TestCase { - - /** the class being tested */ - private Bridge mBridge; - /** the path to the sample layout.xml file */ - private String mLayoutXml1Path; - private String mTextOnlyXmlPath; - - @Override - protected void setUp() throws Exception { - super.setUp(); - - mBridge = new Bridge(); - - // FIXME: need some fonts somewhere. - mBridge.init(null /* fontOsLocation */, getAttributeValues()); - - URL url = this.getClass().getClassLoader().getResource("data/layout1.xml"); - mLayoutXml1Path = url.getFile(); - - url = this.getClass().getClassLoader().getResource("data/textonly.xml"); - mTextOnlyXmlPath = url.getFile(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - - // --------------- - - /** - * Test parser that implements {@link IXmlPullParser}. - */ - private static class TestParser extends KXmlParser implements IXmlPullParser { - public Object getViewKey() { - return null; - } - } - - public void testComputeLayout() throws Exception { - - TestParser parser = new TestParser(); - parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); - parser.setInput(new FileReader(new File(mLayoutXml1Path))); - - Map<String, Map<String, IResourceValue>> projectResources = getProjectResources(); - - Map<String, Map<String, IResourceValue>> frameworkResources = getFrameworkResources(); - - int screenWidth = 320; - int screenHeight = 480; - - // FIXME need a dummy font for the tests! - ILayoutResult result = mBridge.computeLayout(parser, new Integer(1) /* projectKey */, - screenWidth, screenHeight, - "Theme", projectResources, frameworkResources, null, null); - - display(result.getRootView(), ""); - } - - private Map<String, Map<String, Integer>> getAttributeValues() { - Map<String, Map<String, Integer>> attributeValues = - new HashMap<String, Map<String,Integer>>(); - - // lets create a map for the orientation attribute - Map<String, Integer> attributeMap = new HashMap<String, Integer>(); - - attributeMap.put("horizontal", Integer.valueOf(0)); - attributeMap.put("vertical", Integer.valueOf(1)); - - attributeValues.put("orientation", attributeMap); - - return attributeValues; - } - - private Map<String, Map<String, IResourceValue>> getFrameworkResources() { - Map<String, Map<String, IResourceValue>> frameworkResources = - new HashMap<String, Map<String, IResourceValue>>(); - - // create the style map - Map<String, IResourceValue> styleMap = new HashMap<String, IResourceValue>(); - frameworkResources.put("style", styleMap); - - // create a button style. - IStyleResourceValue style = createStyle("Widget.Button", - "background", "@android:drawable/something", - "focusable", "true", - "clickable", "true", - "textAppearance", "?android:attr/textAppearanceSmallInverse", - "textColor", "?android:attr/textColorBrightInverseNoDisable", - "gravity", "center_vertical|center_horizontal" - ); - styleMap.put(style.getName(), style); - - // create the parent style of button style - style = createStyle("Widget", - "textAppearance", "?textAppearance"); - styleMap.put(style.getName(), style); - - // link the buttonStyle info in the default theme. - style = createStyle("Theme", - BridgeConstants.RES_STYLE, "buttonStyle", "@android:style/Widget.Button", - BridgeConstants.RES_STYLE, "textAppearance", "@android:style/TextAppearance", - BridgeConstants.RES_STYLE, "textAppearanceSmallInverse", "@android:style/TextAppearance.Small.Inverse", - BridgeConstants.RES_COLOR, "textColorBrightInverseNoDisable", "@android:color/bright_text_light_nodisable" - ); - styleMap.put(style.getName(), style); - - // create a dummy drawable to go with it - Map<String, IResourceValue> drawableMap = new HashMap<String, IResourceValue>(); - frameworkResources.put("drawable", drawableMap); - - // get the 9 patch test location - URL url = this.getClass().getClassLoader().getResource("data/button.9.png"); - - IResourceValue drawable = new ResourceValue(BridgeConstants.RES_DRAWABLE, "something", - url.getPath()); - drawableMap.put(drawable.getName(), drawable); - return frameworkResources; - } - - private Map<String, Map<String, IResourceValue>> getProjectResources() { - Map<String, Map<String, IResourceValue>> projectResources = - new HashMap<String, Map<String, IResourceValue>>(); - - // create the style map (even empty there should be one) - Map<String, IResourceValue> styleMap = new HashMap<String, IResourceValue>(); - projectResources.put("style", styleMap); - - return projectResources; - } - - - private void display(ILayoutViewInfo result, String offset) { - - String msg = String.format("%s%s L:%d T:%d R:%d B:%d", - offset, - result.getName(), - result.getLeft(), result.getTop(), result.getRight(), result.getBottom()); - - System.out.println(msg); - ILayoutViewInfo[] children = result.getChildren(); - if (children != null) { - offset += "+-"; - for (ILayoutViewInfo child : children) { - display(child, offset); - } - } - } - - /** - * Creates a {@link IStyleResourceValue} based on the given values. - * @param styleName the name of the style. - * @param items An array of Strings. Even indices contain a style item name, and odd indices - * a style item value. If the number of string in the array is not even, an exception is thrown. - */ - private IStyleResourceValue createStyle(String styleName, String... items) { - StyleResourceValue value = new StyleResourceValue(styleName); - - if (items.length % 3 == 0) { - for (int i = 0 ; i < items.length;) { - value.addItem(new ResourceValue(items[i++], items[i++], items[i++])); - } - } else { - throw new IllegalArgumentException("Need a multiple of 3 for the number of strings"); - } - - return value; - } - - // --------------- - - public void testTextLayout() throws Exception { - - TestParser parser = new TestParser(); - parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); - parser.setInput(new FileReader(new File(mTextOnlyXmlPath))); - - Map<String, Map<String, IResourceValue>> projectResources = getProjectResources(); - Map<String, Map<String, IResourceValue>> frameworkResources = getFrameworkResources(); - - int screenWidth = 320; - int screenHeight = 480; - - // FIXME need a dummy font for the tests! - ILayoutResult result = mBridge.computeLayout(parser, new Integer(1) /* projectKey */, - screenWidth, screenHeight, - "Theme", projectResources, frameworkResources, null, null); - - display(result.getRootView(), ""); - } - -} diff --git a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/BridgeXmlBlockParserTest.java b/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/BridgeXmlBlockParserTest.java index cac1f95..db1262f 100644 --- a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/BridgeXmlBlockParserTest.java +++ b/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/BridgeXmlBlockParserTest.java @@ -17,33 +17,18 @@ package com.android.layoutlib.bridge; import org.kxml2.io.KXmlParser; -import org.w3c.dom.Document; import org.w3c.dom.Node; -import org.xml.sax.SAXException; import org.xmlpull.v1.XmlPullParser; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.net.URL; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; +import java.io.InputStream; import junit.framework.TestCase; public class BridgeXmlBlockParserTest extends TestCase { - private String mXmlPath; - private Document mDoc; - @Override protected void setUp() throws Exception { super.setUp(); - URL url = this.getClass().getClassLoader().getResource("data/layout1.xml"); - mXmlPath = url.getFile(); - mDoc = getXmlDocument(mXmlPath); } @Override @@ -54,7 +39,10 @@ public class BridgeXmlBlockParserTest extends TestCase { public void testXmlBlockParser() throws Exception { XmlPullParser parser = new KXmlParser(); parser = new BridgeXmlBlockParser(parser, null, false /* platformResourceFlag */); - parser.setInput(new FileReader(new File(mXmlPath))); + + InputStream input = this.getClass().getClassLoader().getResourceAsStream( + "com/android/layoutlib/testdata/layout1.xml"); + parser.setInput(input, null /*encoding*/); assertEquals(XmlPullParser.START_DOCUMENT, parser.next()); @@ -85,24 +73,8 @@ public class BridgeXmlBlockParserTest extends TestCase { assertEquals(XmlPullParser.END_TAG, parser.next()); assertEquals(XmlPullParser.END_DOCUMENT, parser.next()); } - - //------------ - - private Document getXmlDocument(String xmlFilePath) - throws ParserConfigurationException, SAXException, IOException { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - - // keep comments - factory.setIgnoringComments(false); - // don't validate our bogus DTD - factory.setValidating(false); - // we want namespaces - factory.setNamespaceAware(true); - - DocumentBuilder builder = factory.newDocumentBuilder(); - return builder.parse(new File(xmlFilePath)); - } + //------------ /** * Quick'n'dirty debug helper that dumps an XML structure to stdout. @@ -126,7 +98,7 @@ public class BridgeXmlBlockParserTest extends TestCase { "DOCUMENT_FRAGMENT_NODE", "NOTATION_NODE" }; - + String s = String.format("%s<%s> %s %s", prefix, types[node.getNodeType()], @@ -134,7 +106,7 @@ public class BridgeXmlBlockParserTest extends TestCase { node.getNodeValue() == null ? "" : node.getNodeValue().trim()); System.out.println(s); - + n = node.getFirstChild(); if (n != null) { dump(n, prefix + "- "); diff --git a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/NinePatchTest.java b/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/NinePatchTest.java index 67ec5e1..d5993db 100644 --- a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/NinePatchTest.java +++ b/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/NinePatchTest.java @@ -7,20 +7,21 @@ import java.net.URL; import junit.framework.TestCase; public class NinePatchTest extends TestCase { - + private NinePatch mPatch; @Override protected void setUp() throws Exception { - URL url = this.getClass().getClassLoader().getResource("data/button.9.png"); + URL url = this.getClass().getClassLoader().getResource( + "com/android/layoutlib/testdata/button.9.png"); mPatch = NinePatch.load(url, false /* convert */); } - + public void test9PatchLoad() throws Exception { assertNotNull(mPatch); } - + public void test9PatchMinSize() { int[] padding = new int[4]; mPatch.getPadding(padding); @@ -28,8 +29,8 @@ public class NinePatchTest extends TestCase { assertEquals(3, padding[1]); assertEquals(13, padding[2]); assertEquals(4, padding[3]); - assertEquals(38, mPatch.getWidth()); - assertEquals(27, mPatch.getHeight()); + assertEquals(36, mPatch.getWidth()); + assertEquals(25, mPatch.getHeight()); } } diff --git a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/StyleResourceValue.java b/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/StyleResourceValue.java deleted file mode 100644 index 84bdc2f..0000000 --- a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/StyleResourceValue.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT 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; - -import com.android.layoutlib.api.IResourceValue; -import com.android.layoutlib.api.IStyleResourceValue; - -import java.util.HashMap; - -class StyleResourceValue extends ResourceValue implements IStyleResourceValue { - - private String mParentStyle = null; - private HashMap<String, IResourceValue> mItems = new HashMap<String, IResourceValue>(); - - StyleResourceValue(String name) { - super(name); - } - - StyleResourceValue(String name, String parentStyle) { - super(name); - mParentStyle = parentStyle; - } - - public String getParentStyle() { - return mParentStyle; - } - - public IResourceValue findItem(String name) { - return mItems.get(name); - } - - public void addItem(IResourceValue value) { - mItems.put(value.getName(), value); - } - - @Override - public void replaceWith(ResourceValue value) { - super.replaceWith(value); - - if (value instanceof StyleResourceValue) { - mItems.clear(); - mItems.putAll(((StyleResourceValue)value).mItems); - } - } - -} diff --git a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/TestClassReplacement.java b/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/TestClassReplacement.java new file mode 100644 index 0000000..e0dc55f --- /dev/null +++ b/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/TestClassReplacement.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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; + +import java.lang.reflect.Method; + +import junit.framework.TestCase; + +public class TestClassReplacement extends TestCase { + + public void testClassReplacements() { + // TODO: we want to test all the classes. For now only Paint passes the tests. +// final String[] classes = CreateInfo.RENAMED_CLASSES; + final String[] classes = new String[] { + "android.graphics.Paint", "android.graphics._Original_Paint" + }; + final int count = classes.length; + for (int i = 0 ; i < count ; i += 2) { + loadAndCompareClasses(classes[i], classes[i+1]); + } + } + + private void loadAndCompareClasses(String newClassName, String oldClassName) { + // load the classes + try { + Class<?> newClass = TestClassReplacement.class.getClassLoader().loadClass(newClassName); + Class<?> oldClass = TestClassReplacement.class.getClassLoader().loadClass(oldClassName); + + compare(newClass, oldClass); + } catch (ClassNotFoundException e) { + fail("Failed to load class: " + e.getMessage()); + } + } + + private void compare(Class<?> newClass, Class<?> oldClass) { + // first compare the methods. + Method[] newClassMethods = newClass.getDeclaredMethods(); + Method[] oldClassMethods = oldClass.getDeclaredMethods(); + + for (Method oldMethod : oldClassMethods) { + // we ignore anything that starts with native + if (oldMethod.getName().startsWith("native")) { + continue; + } + boolean found = false; + for (Method newMethod : newClassMethods) { + if (compareMethods(newClass, newMethod, oldClass, oldMethod)) { + found = true; + break; + } + } + + if (found == false) { + fail(String.format("Unable to find %1$s", oldMethod.toGenericString())); + } + } + + // TODO: check (somehow?) that the methods that were removed from the original class + // have been put back in the new class! + // For this we need the original unmodified class (ie renamed, but w/o the methods removed) + } + + private boolean compareMethods(Class<?> newClass, Method newMethod, + Class<?> oldClass, Method oldMethod) { + // first check the name of the method + if (newMethod.getName().equals(oldMethod.getName()) == false) { + return false; + } + + // check the return value + Class<?> oldReturnType = oldMethod.getReturnType(); + // if it's the old class, or if it's a inner class of the oldclass, we need to change this. + oldReturnType = adapt(oldReturnType, newClass, oldClass); + + // compare the return types + Class<?> newReturnType = newMethod.getReturnType(); + if (newReturnType.equals(oldReturnType) == false) { + return false; + } + + // now check the parameters type. + Class<?>[] oldParameters = oldMethod.getParameterTypes(); + Class<?>[] newParemeters = newMethod.getParameterTypes(); + if (oldParameters.length != newParemeters.length) { + return false; + } + + for (int i = 0 ; i < oldParameters.length ; i++) { + if (newParemeters[i].equals(adapt(oldParameters[i], newClass, oldClass)) == false) { + return false; + } + } + + return true; + } + + /** + * Adapts a class to deal with renamed classes. + * <p/>For instance if old class is <code>android.graphics._Original_Paint</code> and the + * new class is <code>android.graphics.Paint</code> and the class to adapt is + * <code>android.graphics._Original_Paint$Cap</code>, then the method will return a + * {@link Class} object representing <code>android.graphics.Paint$Cap</code>. + * <p/> + * This method will also ensure that all renamed classes contains all the proper inner classes + * that they should be declaring. + * @param theClass the class to adapt + * @param newClass the new class object + * @param oldClass the old class object + * @return the adapted class. + * @throws ClassNotFoundException + */ + private Class<?> adapt(Class<?> theClass, Class<?> newClass, Class<?> oldClass) { + // only look for a new class if it's not primitive as Class.forName() would fail otherwise. + if (theClass.isPrimitive() == false) { + String n = theClass.getName().replace(oldClass.getName(), newClass.getName()); + try { + return Class.forName(n); + } catch (ClassNotFoundException e) { + fail("Missing class: " + n); + } + } + + return theClass; + } +} diff --git a/tools/layoutlib/bridge/tests/data/button.9.png b/tools/layoutlib/bridge/tests/com/android/layoutlib/testdata/button.9.png Binary files differindex 9d52f40..9d52f40 100644 --- a/tools/layoutlib/bridge/tests/data/button.9.png +++ b/tools/layoutlib/bridge/tests/com/android/layoutlib/testdata/button.9.png diff --git a/tools/layoutlib/bridge/tests/data/layout1.xml b/tools/layoutlib/bridge/tests/com/android/layoutlib/testdata/layout1.xml index 554f541..554f541 100644 --- a/tools/layoutlib/bridge/tests/data/layout1.xml +++ b/tools/layoutlib/bridge/tests/com/android/layoutlib/testdata/layout1.xml diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java index 1adcc17..7b55ed3e 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java @@ -68,6 +68,7 @@ public class AsmGenerator { * * @param log Output logger. * @param osDestJar The path of the destination JAR to create. + * @param injectClasses The list of class from layoutlib_create to inject in layoutlib. * @param stubMethods The list of methods to stub out. Each entry must be in the form * "package.package.OuterClass$InnerClass#MethodName". * @param renameClasses The list of classes to rename, must be an even list: the binary FQCN 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 new file mode 100644 index 0000000..5a13b0b --- /dev/null +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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; + +public class CreateInfo { + /** + * The list of class from layoutlib_create to inject in layoutlib. + */ + public final static Class<?>[] INJECTED_CLASSES = new Class<?>[] { + OverrideMethod.class, + MethodListener.class, + MethodAdapter.class, + CreateInfo.class + }; + + /** + * The list of methods to stub out. Each entry must be in the form + * "package.package.OuterClass$InnerClass#MethodName". + */ + public final static String[] OVERRIDDEN_METHODS = new String[] { + "android.view.View#isInEditMode", + "android.content.res.Resources$Theme#obtainStyledAttributes", + }; + + /** + * The list of classes to rename, must be an even list: the binary FQCN + * of class to replace followed by the new FQCN. + */ + public final static String[] RENAMED_CLASSES = + new String[] { + "android.graphics.Bitmap", "android.graphics._Original_Bitmap", + "android.graphics.BitmapShader", "android.graphics._Original_BitmapShader", + "android.graphics.Canvas", "android.graphics._Original_Canvas", + "android.graphics.ComposeShader", "android.graphics._Original_ComposeShader", + "android.graphics.LinearGradient", "android.graphics._Original_LinearGradient", + "android.graphics.Matrix", "android.graphics._Original_Matrix", + "android.graphics.Paint", "android.graphics._Original_Paint", + "android.graphics.Path", "android.graphics._Original_Path", + "android.graphics.PorterDuffXfermode", "android.graphics._Original_PorterDuffXfermode", + "android.graphics.RadialGradient", "android.graphics._Original_RadialGradient", + "android.graphics.Shader", "android.graphics._Original_Shader", + "android.graphics.SweepGradient", "android.graphics._Original_SweepGradient", + "android.graphics.Typeface", "android.graphics._Original_Typeface", + "android.os.ServiceManager", "android.os._Original_ServiceManager", + "android.util.FloatMath", "android.util._Original_FloatMath", + "android.view.SurfaceView", "android.view._Original_SurfaceView", + "android.view.accessibility.AccessibilityManager", "android.view.accessibility._Original_AccessibilityManager", + }; + + /** + * List of classes for which the methods returning them should be deleted. + * The array contains a list of null terminated section starting with the name of the class + * to rename in which the methods are deleted, followed by a list of return types identifying + * the methods to delete. + */ + public final static String[] REMOVED_METHODS = + new String[] { + "android.graphics.Paint", // class to delete methods from + "android.graphics.Paint$Align", // list of type identifying methods to delete + "android.graphics.Paint$Style", + "android.graphics.Paint$Join", + "android.graphics.Paint$Cap", + "android.graphics.Paint$FontMetrics", + "android.graphics.Paint$FontMetricsInt", + null }; // separator, for next class/methods list. +} + diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java index 47184f1..303f097 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java @@ -43,44 +43,10 @@ public class Main { try { AsmGenerator agen = new AsmGenerator(log, osDestJar[0], - new Class<?>[] { // classes to inject in the final JAR - OverrideMethod.class, - MethodListener.class, - MethodAdapter.class - }, - new String[] { // methods to force override - "android.view.View#isInEditMode", - "android.content.res.Resources$Theme#obtainStyledAttributes", - }, - new String[] { // classes to rename (so that we can replace them in layoutlib) - // original-platform-class-name ======> renamed-class-name - "android.graphics.Bitmap", "android.graphics._Original_Bitmap", - "android.graphics.BitmapShader", "android.graphics._Original_BitmapShader", - "android.graphics.Canvas", "android.graphics._Original_Canvas", - "android.graphics.ComposeShader", "android.graphics._Original_ComposeShader", - "android.graphics.LinearGradient", "android.graphics._Original_LinearGradient", - "android.graphics.Matrix", "android.graphics._Original_Matrix", - "android.graphics.Paint", "android.graphics._Original_Paint", - "android.graphics.Path", "android.graphics._Original_Path", - "android.graphics.PorterDuffXfermode", "android.graphics._Original_PorterDuffXfermode", - "android.graphics.RadialGradient", "android.graphics._Original_RadialGradient", - "android.graphics.Shader", "android.graphics._Original_Shader", - "android.graphics.SweepGradient", "android.graphics._Original_SweepGradient", - "android.graphics.Typeface", "android.graphics._Original_Typeface", - "android.os.ServiceManager", "android.os._Original_ServiceManager", - "android.util.FloatMath", "android.util._Original_FloatMath", - "android.view.SurfaceView", "android.view._Original_SurfaceView", - "android.view.accessibility.AccessibilityManager", "android.view.accessibility._Original_AccessibilityManager", - }, - new String[] { // methods deleted from their return type. - "android.graphics.Paint", // class to delete method from - "android.graphics.Paint$Align", // list of type identifying methods to delete - "android.graphics.Paint$Style", - "android.graphics.Paint$Join", - "android.graphics.Paint$Cap", - "android.graphics.Paint$FontMetrics", - "android.graphics.Paint$FontMetricsInt", - null } + CreateInfo.INJECTED_CLASSES, + CreateInfo.OVERRIDDEN_METHODS, + CreateInfo.RENAMED_CLASSES, + CreateInfo.REMOVED_METHODS ); AsmAnalyzer aa = new AsmAnalyzer(log, osJarPath, agen, diff --git a/tools/localize/Perforce.cpp b/tools/localize/Perforce.cpp index 1c644ed..ae11231 100644 --- a/tools/localize/Perforce.cpp +++ b/tools/localize/Perforce.cpp @@ -6,7 +6,10 @@ #include <sstream> #include <sys/types.h> #include <unistd.h> +#include <stdlib.h> +#include <string.h> #include <sys/wait.h> +#include <cstdio> using namespace std; diff --git a/tools/localize/SourcePos.cpp b/tools/localize/SourcePos.cpp index 2533f0a..184bfe0 100644 --- a/tools/localize/SourcePos.cpp +++ b/tools/localize/SourcePos.cpp @@ -3,6 +3,7 @@ #include <stdarg.h> #include <cstdio> #include <set> +#include <cstdio> using namespace std; diff --git a/tools/localize/XMLHandler.h b/tools/localize/XMLHandler.h index 1130710..324385f 100644 --- a/tools/localize/XMLHandler.h +++ b/tools/localize/XMLHandler.h @@ -3,6 +3,7 @@ #include "SourcePos.h" +#include <algorithm> #include <string> #include <vector> #include <map> diff --git a/tools/localize/file_utils.cpp b/tools/localize/file_utils.cpp index 293e50e..775ce2f 100644 --- a/tools/localize/file_utils.cpp +++ b/tools/localize/file_utils.cpp @@ -8,6 +8,9 @@ #include <sys/fcntl.h> #include <sys/stat.h> #include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <cstdio> #include "log.h" using namespace android; diff --git a/tools/localize/file_utils.h b/tools/localize/file_utils.h index 3b3fa21..7706587 100644 --- a/tools/localize/file_utils.h +++ b/tools/localize/file_utils.h @@ -4,6 +4,7 @@ #include "ValuesFile.h" #include "Configuration.h" #include <string> +#include <cstdio> using namespace std; diff --git a/tools/localize/localize.cpp b/tools/localize/localize.cpp index c0d84cc..68c03b6 100644 --- a/tools/localize/localize.cpp +++ b/tools/localize/localize.cpp @@ -15,6 +15,7 @@ #include <sstream> #include <stdio.h> #include <string.h> +#include <stdlib.h> using namespace std; diff --git a/tools/localize/localize_test.cpp b/tools/localize/localize_test.cpp index 678cad8..1d0ac9a 100644 --- a/tools/localize/localize_test.cpp +++ b/tools/localize/localize_test.cpp @@ -1,3 +1,4 @@ +#include <cstdio> #include "XLIFFFile.h" #include "ValuesFile.h" #include "localize.h" diff --git a/tools/localize/merge_res_and_xliff_test.cpp b/tools/localize/merge_res_and_xliff_test.cpp index e4ab562..6fe2629 100644 --- a/tools/localize/merge_res_and_xliff_test.cpp +++ b/tools/localize/merge_res_and_xliff_test.cpp @@ -1,3 +1,4 @@ +#include <cstdio> #include "merge_res_and_xliff.h" #include <stdio.h> diff --git a/tools/preload/20080522.compiled b/tools/preload/20080522.compiled Binary files differdeleted file mode 100644 index a2af422..0000000 --- a/tools/preload/20080522.compiled +++ /dev/null diff --git a/tools/preload/20090811.compiled b/tools/preload/20090922.compiled Binary files differindex 6dbeca0..fc66405 100644 --- a/tools/preload/20090811.compiled +++ b/tools/preload/20090922.compiled diff --git a/tools/preload/Android.mk b/tools/preload/Android.mk index f325870..65b7d1a 100644 --- a/tools/preload/Android.mk +++ b/tools/preload/Android.mk @@ -8,6 +8,7 @@ LOCAL_SRC_FILES := \ MemoryUsage.java \ Operation.java \ Policy.java \ + PrintBugReports.java \ PrintCsv.java \ PrintHtmlDiff.java \ PrintPsTree.java \ diff --git a/tools/preload/LoadedClass.java b/tools/preload/LoadedClass.java index 86e5dfc..02cff10 100644 --- a/tools/preload/LoadedClass.java +++ b/tools/preload/LoadedClass.java @@ -15,7 +15,11 @@ */ import java.io.Serializable; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; /** * A loaded class. @@ -50,6 +54,30 @@ class LoadedClass implements Serializable, Comparable<LoadedClass> { this.systemClass = systemClass; } + /** + * Returns true if this class was loaded by more than one proc. + */ + boolean isSharable() { + Set<String> procNames = new HashSet<String>(); + for (Operation load : loads) { + if (load.process.fromZygote()) { + procNames.add(load.process.name); + if (procNames.size() > 1) { + return true; + } + } + } + for (Operation init : initializations) { + if (init.process.fromZygote()) { + procNames.add(init.process.name); + if (procNames.size() > 1) { + return true; + } + } + } + return false; + } + void measureMemoryUsage() { this.memoryUsage = MemoryUsage.forClass(name); } diff --git a/tools/preload/PrintBugReports.java b/tools/preload/PrintBugReports.java new file mode 100644 index 0000000..a6d4187 --- /dev/null +++ b/tools/preload/PrintBugReports.java @@ -0,0 +1,272 @@ +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.io.DataInputStream; +import java.io.FileInputStream; +import java.util.Map; +import java.util.List; +import java.util.HashMap; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Set; +import java.util.TreeSet; +import java.util.Iterator; + +/** + * Prints HTML reports that can be attached to bugs. + */ +public class PrintBugReports { + + private static final String DIR = "out/preload"; + private static boolean PRINT_MEMORY_USAGE = false; + + private static final Comparator<LoadedClass> DEFAULT_ORDER + = new Comparator<LoadedClass>() { + public int compare(LoadedClass a, LoadedClass b) { + // Longest load time first. + int diff = b.medianTimeMicros() - a.medianTimeMicros(); + if (diff != 0) { + return diff; + } + + return a.name.compareTo(b.name); + } + }; + + public static void main(String[] args) + throws IOException, ClassNotFoundException { + Root root = Root.fromFile(args[0]); + String baseUrl = ""; + if (args.length > 1) { + baseUrl = args[1]; + } + + new File(DIR).mkdirs(); + + Map<String, List<Proc>> procsByName = new HashMap<String, List<Proc>>(); + for (Proc proc : root.processes.values()) { + if (proc.fromZygote()) { + List<Proc> procs = procsByName.get(proc.name); + if (procs == null) { + procs = new ArrayList<Proc>(); + procsByName.put(proc.name, procs); + } + procs.add(proc); + } + } + + Set<LoadedClass> coreClasses = new TreeSet<LoadedClass>(DEFAULT_ORDER); + Set<LoadedClass> frameworkClasses = new TreeSet<LoadedClass>(DEFAULT_ORDER); + + for (List<Proc> procs : procsByName.values()) { + Proc first = procs.get(0); + Set<LoadedClass> classes = new TreeSet<LoadedClass>(DEFAULT_ORDER); + Set<LoadedClass> sharedClasses + = new TreeSet<LoadedClass>(DEFAULT_ORDER); + for (Proc proc : procs) { + for (Operation operation : proc.operations) { + LoadedClass clazz = operation.loadedClass; + if (clazz.isSharable() && clazz.systemClass) { + if (clazz.name.startsWith("dalvik") + || clazz.name.startsWith("org") + || clazz.name.startsWith("java")) { + coreClasses.add(clazz); + } else { + frameworkClasses.add(clazz); + } + sharedClasses.add(clazz); + } else { + classes.add(clazz); + } + } + } + printApplicationHtml(first.name, root.baseline, classes, + sharedClasses); + } + + printHtml("core", root.baseline, coreClasses); + printHtml("framework", root.baseline, frameworkClasses); + + PrintStream out = new PrintStream(DIR + "/toc.html"); + out.println("<html><body>"); + out.println("<a href='" + baseUrl + + "/core.html'>core</a><br/>"); + out.println("<a href='" + baseUrl + + "/framework.html'>framework</a><br/>"); + + for (String s : new TreeSet<String>(procsByName.keySet())) { + out.println("<a href='" + baseUrl + "/" + + s + ".html'>" + s + "</a><br/>"); + } + out.println("</body></html>"); + out.close(); + } + + static void printApplicationHtml(String name, MemoryUsage baseline, + Iterable<LoadedClass> classes, Iterable<LoadedClass> sharedClasses) + throws IOException { + PrintStream out = new PrintStream(DIR + "/" + name + ".html"); + + printHeader(name, out); + out.println("<body>"); + out.println("<h1><tt>" + name + "</tt></h1>"); + out.println("<p><i>Click a column header to sort by that column.</i></p>"); + + out.println("<p><a href=\"#shared\">Shared Classes</a></p>"); + + out.println("<h3>Application-Specific Classes</h3>"); + + out.println("<p>These classes were loaded only by " + name + ". If" + + " the value of the <i>Preloaded</i> column is <i>yes</i> or " + + " <i>no</i>, the class is in the boot classpath; if it's not" + + " part of the published API, consider" + + " moving it into the APK.</p>"); + + printTable(out, baseline, classes, false); + + out.println("<p><a href=\"#\">Top</a></p>"); + + out.println("<a name=\"shared\"/><h3>Shared Classes</h3>"); + + out.println("<p>These classes are in the boot classpath. They are used" + + " by " + name + " as well as others."); + + printTable(out, baseline, sharedClasses, true); + + out.println("</body></html>"); + out.close(); + } + + static void printHtml(String name, MemoryUsage baseline, + Iterable<LoadedClass> classes) + throws IOException { + PrintStream out = new PrintStream(DIR + "/" + name + ".html"); + + printHeader(name, out); + out.println("<body>"); + out.println("<h1><tt>" + name + "</tt></h1>"); + out.println("<p><i>Click a column header to sort by that column.</i></p>"); + + printTable(out, baseline, classes, true); + + out.println("</body></html>"); + out.close(); + } + + private static void printHeader(String name, PrintStream out) + throws IOException { + out.println("<html><head>"); + out.println("<title>" + name + "</title>"); + out.println("<style>"); + out.println("a, th, td, h1, h3, p { font-family: arial }"); + out.println("th, td { font-size: small }"); + out.println("</style>"); + out.println("<script language=\"javascript\">"); + out.write(SCRIPT); + out.println("</script>"); + out.println("</head>"); + } + + static void printTable(PrintStream out, MemoryUsage baseline, + Iterable<LoadedClass> classes, boolean showProcNames) { + out.println("<p><table border=\"1\" cellpadding=\"5\"" + + " class=\"sortable\" cellspacing=\"0\">"); + + out.println("<thead bgcolor=\"#eeeeee\"><tr>"); + out.println("<th>Name</th>"); + out.println("<th>Preloaded</th>"); + out.println("<th>Total Time (us)</th>"); + out.println("<th>Load Time (us)</th>"); + out.println("<th>Init Time (us)</th>"); + if (PRINT_MEMORY_USAGE) { + out.println("<th>Total Heap (B)</th>"); + out.println("<th>Dalvik Heap (B)</th>"); + out.println("<th>Native Heap (B)</th>"); + out.println("<th>Total Pages (kB)</th>"); + out.println("<th>Dalvik Pages (kB)</th>"); + out.println("<th>Native Pages (kB)</th>"); + out.println("<th>Other Pages (kB)</th>"); + } + if (showProcNames) { + out.println("<th>Loaded by</th>"); + } + out.println("</tr></thead>"); + + for (LoadedClass clazz : classes) { + out.println("<tr>"); + out.println("<td>" + clazz.name + "</td>"); + + out.println("<td>" + ((clazz.systemClass) + ? ((clazz.preloaded) ? "yes" : "no") : "n/a") + "</td>"); + + out.println("<td>" + clazz.medianTimeMicros() + "</td>"); + out.println("<td>" + clazz.medianLoadTimeMicros() + "</td>"); + out.println("<td>" + clazz.medianInitTimeMicros() + "</td>"); + + if (PRINT_MEMORY_USAGE) { + if (clazz.memoryUsage.isAvailable()) { + MemoryUsage subtracted + = clazz.memoryUsage.subtract(baseline); + + long totalHeap = subtracted.javaHeapSize() + + subtracted.nativeHeapSize; + out.println("<td>" + totalHeap + "</td>"); + out.println("<td>" + subtracted.javaHeapSize() + "</td>"); + out.println("<td>" + subtracted.nativeHeapSize + "</td>"); + + out.println("<td>" + subtracted.totalPages() + "</td>"); + out.println("<td>" + subtracted.javaPagesInK() + "</td>"); + out.println("<td>" + subtracted.nativePagesInK() + "</td>"); + out.println("<td>" + subtracted.otherPagesInK() + "</td>"); + } else { + for (int i = 0; i < 7; i++) { + out.println("<td> </td>"); + } + } + } + + if (showProcNames) { + out.println("<td>"); + Set<String> procNames = new TreeSet<String>(); + for (Operation op : clazz.loads) { + procNames.add(op.process.name); + } + for (Operation op : clazz.initializations) { + procNames.add(op.process.name); + } + if (procNames.size() <= 3) { + for (String name : procNames) { + out.print(name + "<br/>"); + } + } else { + Iterator<String> i = procNames.iterator(); + out.print(i.next() + "<br/>"); + out.print(i.next() + "<br/>"); + out.print("...and " + (procNames.size() - 2) + + " others."); + } + out.println("</td>"); + } + + out.println("</tr>"); + } + + out.println("</table></p>"); + } + + static byte[] SCRIPT; + static { + try { + File script = new File( + "frameworks/base/tools/preload/sorttable.js"); + int length = (int) script.length(); + SCRIPT = new byte[length]; + DataInputStream in = new DataInputStream( + new FileInputStream(script)); + in.readFully(SCRIPT); + in.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/tools/preload/Root.java b/tools/preload/Root.java index 0bc29bf..3f12dea 100644 --- a/tools/preload/Root.java +++ b/tools/preload/Root.java @@ -46,7 +46,8 @@ public class Root implements Serializable { final Map<String, LoadedClass> loadedClasses = new HashMap<String, LoadedClass>(); - MemoryUsage baseline = MemoryUsage.baseline(); +// MemoryUsage baseline = MemoryUsage.baseline(); + MemoryUsage baseline = MemoryUsage.NOT_AVAILABLE; /** * Records class loads and initializations. @@ -73,7 +74,7 @@ public class Root implements Serializable { if (loadedClass.systemClass) { // Only measure memory for classes in the boot // classpath. - loadedClass.measureMemoryUsage(); +// loadedClass.measureMemoryUsage(); } loadedClasses.put(name, loadedClass); } diff --git a/tools/preload/WritePreloadedClassFile.java b/tools/preload/WritePreloadedClassFile.java index 96c539b..757d17d3 100644 --- a/tools/preload/WritePreloadedClassFile.java +++ b/tools/preload/WritePreloadedClassFile.java @@ -32,7 +32,7 @@ public class WritePreloadedClassFile { /** * Preload any class that take longer to load than MIN_LOAD_TIME_MICROS us. */ - static final int MIN_LOAD_TIME_MICROS = 1250; + static final int MIN_LOAD_TIME_MICROS = 1000; public static void main(String[] args) throws IOException, ClassNotFoundException { diff --git a/tools/preload/preload.ipr b/tools/preload/preload.ipr index 0c9621c..dddca3b 100644 --- a/tools/preload/preload.ipr +++ b/tools/preload/preload.ipr @@ -364,7 +364,7 @@ </component> <component name="ProjectFileVersion" converted="true" /> <component name="ProjectKey"> - <option name="state" value="project:///Volumes/Android/donut/frameworks/base/tools/preload/preload.ipr" /> + <option name="state" value="project:///Volumes/Android/eclair/frameworks/base/tools/preload/preload.ipr" /> </component> <component name="ProjectModuleManager"> <modules> diff --git a/tools/preload/sorttable.js b/tools/preload/sorttable.js index 25bccb2..f03859e 100644 --- a/tools/preload/sorttable.js +++ b/tools/preload/sorttable.js @@ -6,7 +6,7 @@ Instructions: Download this file - Add <script src="sorttable.js"></script> to your HTML + Add <script src="sorttable.js"> to your HTML Add class="sortable" to any table you'd like to make sortable Click on the headers to sort @@ -88,6 +88,7 @@ sorttable = { } // make it clickable to sort headrow[i].sorttable_columnindex = i; + headrow[i].style.cursor = "pointer"; headrow[i].sorttable_tbody = table.tBodies[0]; dean_addEvent(headrow[i],"click", function(e) { |