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