summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorJean-Baptiste Queru <jbq@google.com>2009-11-12 18:45:53 -0800
committerJean-Baptiste Queru <jbq@google.com>2009-11-13 13:53:39 -0800
commit9db3d07b9620b4269ab33f78604a36327e536ce1 (patch)
tree41e294f34b9695187af098cd42167489fb0c8fb0 /tools
parent6c63ee4fc4acae4bbbbd2a49e0a68206221f0de0 (diff)
downloadframeworks_base-9db3d07b9620b4269ab33f78604a36327e536ce1.zip
frameworks_base-9db3d07b9620b4269ab33f78604a36327e536ce1.tar.gz
frameworks_base-9db3d07b9620b4269ab33f78604a36327e536ce1.tar.bz2
eclair snapshot
Diffstat (limited to 'tools')
-rw-r--r--tools/aapt/AaptAssets.cpp72
-rw-r--r--tools/aapt/AaptAssets.h26
-rw-r--r--tools/aapt/Android.mk5
-rw-r--r--tools/aapt/Bundle.h23
-rw-r--r--tools/aapt/Command.cpp263
-rw-r--r--tools/aapt/Images.cpp5
-rw-r--r--tools/aapt/Main.cpp26
-rw-r--r--tools/aapt/Main.h12
-rw-r--r--tools/aapt/Package.cpp12
-rw-r--r--tools/aapt/Resource.cpp396
-rw-r--r--tools/aapt/ResourceTable.cpp99
-rw-r--r--tools/aapt/ResourceTable.h10
-rw-r--r--tools/aapt/SourcePos.cpp2
-rw-r--r--tools/aapt/XMLNode.cpp10
-rw-r--r--tools/aapt/ZipEntry.cpp696
-rw-r--r--tools/aapt/ZipEntry.h345
-rw-r--r--tools/aapt/ZipFile.cpp1297
-rw-r--r--tools/aapt/ZipFile.h270
-rwxr-xr-xtools/aidl/AST.cpp17
-rw-r--r--tools/aidl/generate_java.cpp7
-rw-r--r--tools/aidl/options.h1
-rw-r--r--tools/layoutlib/api/src/com/android/layoutlib/api/IDensityBasedResourceValue.java44
-rw-r--r--tools/layoutlib/api/src/com/android/layoutlib/api/ILayoutBridge.java69
-rw-r--r--tools/layoutlib/api/src/com/android/layoutlib/api/ILayoutResult.java20
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Bitmap.java4
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Canvas.java90
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Paint.java534
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/Typeface.java48
-rw-r--r--tools/layoutlib/bridge/src/android/webkit/WebView.java7
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java106
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java15
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeResources.java2
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeTypedArray.java8
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/FontLoader.java142
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/ResourceHelper.java113
-rw-r--r--tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/AndroidGraphicsTests.java18
-rw-r--r--tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/BridgeTest.java229
-rw-r--r--tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/BridgeXmlBlockParserTest.java44
-rw-r--r--tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/NinePatchTest.java13
-rw-r--r--tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/StyleResourceValue.java60
-rw-r--r--tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/TestClassReplacement.java139
-rw-r--r--tools/layoutlib/bridge/tests/com/android/layoutlib/testdata/button.9.png (renamed from tools/layoutlib/bridge/tests/data/button.9.png)bin3750 -> 3750 bytes
-rw-r--r--tools/layoutlib/bridge/tests/com/android/layoutlib/testdata/layout1.xml (renamed from tools/layoutlib/bridge/tests/data/layout1.xml)0
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java1
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java81
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java42
-rw-r--r--tools/localize/Perforce.cpp3
-rw-r--r--tools/localize/SourcePos.cpp1
-rw-r--r--tools/localize/XMLHandler.h1
-rw-r--r--tools/localize/file_utils.cpp3
-rw-r--r--tools/localize/file_utils.h1
-rw-r--r--tools/localize/localize.cpp1
-rw-r--r--tools/localize/localize_test.cpp1
-rw-r--r--tools/localize/merge_res_and_xliff_test.cpp1
-rw-r--r--tools/preload/20080522.compiledbin11414749 -> 0 bytes
-rw-r--r--tools/preload/20090922.compiled (renamed from tools/preload/20090811.compiled)bin15943336 -> 13406836 bytes
-rw-r--r--tools/preload/Android.mk1
-rw-r--r--tools/preload/LoadedClass.java30
-rw-r--r--tools/preload/PrintBugReports.java272
-rw-r--r--tools/preload/Root.java5
-rw-r--r--tools/preload/WritePreloadedClassFile.java2
-rw-r--r--tools/preload/preload.ipr2
-rw-r--r--tools/preload/sorttable.js3
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(), &params);
getKeysHiddenName(keysHidden.string(), &params);
getKeyboardName(keyboard.string(), &params);
+ getNavHiddenName(navHidden.string(), &params);
getNavigationName(navigation.string(), &params);
getScreenSizeName(screenSize.string(), &params);
getVersionName(version.string(), &params);
@@ -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
index 9d52f40..9d52f40 100644
--- a/tools/layoutlib/bridge/tests/data/button.9.png
+++ b/tools/layoutlib/bridge/tests/com/android/layoutlib/testdata/button.9.png
Binary files differ
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
deleted file mode 100644
index a2af422..0000000
--- a/tools/preload/20080522.compiled
+++ /dev/null
Binary files differ
diff --git a/tools/preload/20090811.compiled b/tools/preload/20090922.compiled
index 6dbeca0..fc66405 100644
--- a/tools/preload/20090811.compiled
+++ b/tools/preload/20090922.compiled
Binary files differ
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>&nbsp;</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) {