diff options
author | Andy Mast <andy@cyngn.com> | 2014-02-02 12:52:58 -0800 |
---|---|---|
committer | d34d <clark@cyngn.com> | 2015-10-26 15:57:35 -0700 |
commit | 39f748480050ef6d555d03fc7c9315f3a0b2f30e (patch) | |
tree | a4af5b36022a6c91a12286997d1ac8544174f3ab /tools/aapt | |
parent | 12aed4e65691e06656afa0d6c3ccbe05ccabf735 (diff) | |
download | frameworks_base-39f748480050ef6d555d03fc7c9315f3a0b2f30e.zip frameworks_base-39f748480050ef6d555d03fc7c9315f3a0b2f30e.tar.gz frameworks_base-39f748480050ef6d555d03fc7c9315f3a0b2f30e.tar.bz2 |
Themes: Port to CM13 [1/3]
Themes: Aapt port
Id: I106d447daf7935bada65e78911d8973ce0ca27ae
Themes: Port our additions to idmap
Id: I2e47cc23de4e7c0b884cccbd87c7d77079ac6824
Themes: remove legacy theme support from idmap
Id: I17dfe35f9985d8cef790b26a8bcda738ea65917c
Themes: Forward port changes to Installer.java
Id: If64e856773e50f5ed74f2358e0c590abad724689
Themes: Add clean spec for aapt
Id: I68f5f63f7e83b99230860dd2d8646e96da484b62
androidfw: Port CM overlay contributions
Id: Id7b7f5f35011922c668fcea3a8aec5b42bd28653
androidfw: Port addOverlayPath and removeOverlayPath
Id: I279db083af28fd8941f3227f2a7512ff094742c1
androidfw: Port addCommonOverlayPath
Id: I12d2fe05a04f6a7e553c330505a475346374b507
androidfw: Port addIconPath
Id: Ide3db28cde0c7f93edd9e7ad626ebace8d4105cc
androidfw: Allow package ID to be overriden at runtime
Id: Ieca3a0ae070a6c0ad0cf2b73b5944d83397d08b9
Aapt: Zip Parsing Test Cases w/ Refactoring
Id: I28f9115700e186136432138d228111ebcfbd0480
Themes: Update tests for idmap
Id: I3dae5bd376d122eab397863378599ae0ac7c6734
androidfw: Add test case for overriding package id
Id: I2668c529e24a55cd6bc8437406fc284b853a75e7
androidfw: Add tests for bags
The overlayOverridesStyleAttribute will currently fail since
our changes to allow theming styles is not currently implemented.
Id: Idfacc4382baf4152c839799a22b6cbe015ef2197
androidfw: Don't consider package ids 0x60 and 0x61 as dynamic
Package IDs that are not 0x01 (system) or 0x7f (app) are treated
as dynamic references. Overlays are assigned specific package
ids that fall within the region of shared libraries. This patch
treats them the same as system and app resource packages.
Id: Ieecaa889bed50490796351302405a38f77c84f4e
Theme Parsing & Info
Id: I3583d7e8ca704402e3d8c6e1c7cea1645b91c06f
Port ThemeUtils and its dependencies
This is pretty much copy/paste from cm-11.0
Id: I406860a259136ccca107b981aca0369851df445e
Themes: AndroidManifest and Intent port
Id: Ib97e8539301d20d120fd8b49891fdaae8205fe42
ComposedIconInfo Port (w/ stubbed IconPackHelper)
IconPackHelper is stubbed out so that PMService can reference it,
we'll need to port the Resource stack before porting the implementation.
Id: I59b680511de525e1d375a4f3be04347686b5e81b
Port PackageManagerService and other dependencies
Id: I11629d1e5eee21e01c060bc6c0393aae96034b69
Themes: Add in ThemeService
See also: external/selinux. The policy must be added
in order for the service to start without a security exception.
Change-Id: Ic6f64796b264e430e9706a17a3fd2a35085fd1ca
TODO: ThemeService / Keyguard interaction
TODO: SystemServer - AppsLaunchFailure
Add AppsLaunchFailure
Id: I09a3826f89c62cb898866408e807f269616f48fc
androidfw: Update bags tests
The overlayOverridesStyleAttribute was updated such that it does
not check the attribute of the theme before adding the overlay.
The bag is cached on the first call and the next call, after the
overlay is attached, returns that pre-computed bag so the test would
always fail. We now simply check that the attribute matches the one
in the overlay, and if so then the test passes.
This patch also adds a new test to check that an overlay can reference
and access resources that are unique to the overlay. Currently this
test fails.
Id: I3892df3f0d9443a73eaa11b3d5e97cfe86620a73
androidfw: Add test for referencing overlay styles as parents
Id: I4fa3bd447c888e96176955924ebe7ee5c784ab55
androidfw: Allow referencing and retrieving overlay resources
This patch allows a theme to reference it's own resources. Overlays
get their own group which contains those resources that are not idmapped.
Idmapped resources end up in the target's group.
Id: Ibc119ddcdb35d44a8afec3c6152bcab2909cda18
androidfw: Fill in missing attributes from overlay style
Id: I74051b379b73c728c6a2aa4bc62f3cd268a40b53
Protect windowNoTitle and windowActionBar attributes
This patch creates a new method to define "protected" attributes. These
are attributes like windowActionBar which should not be modified by
a theme.
Some apps (eg gmail) use the appcompat library which
has its own Actionbar classes. When an app uses its own
Actionbar it must not include the default actionbar which is
achieved through the windowActionBar attribute.
Some themes may try to change these attributes, which can will cause
the app to crash.
Id: Ie3bb7285eed09f3f13facf9d142ea9eb83796eec
Themes: Use SYSTEM_DEFAULT in ThemeService
Id: I52794dd98ca2f64aa50046ecdd7f79f27c21dd98
androidfw: Test missing parent attributes are merged in
This test checks that an overlaid style contains any attributes
that were in the original style but omitted in the overlaid style.
Id: I6b496ef2eb0a7ef27b4fafdfda5bdf7ccffad989
androidfw: Add test case for protected attributes
For this test to pass a protected attribute, such as windowNoTitle,
must be equal to the original and not the value specified in the
overlay.
Id: Ic03f11214a1fc4139e3c48d7e72694a80f819023
Themes: Attach theme and icon resources from java
Id: I9ffa0ce96a4af603b78b32d6b190f9698d3e4b4f
Themes: Icons, icons, icons!
Let there be icons. Legacy icons and composed icons are included
in this patch.
Id: I9fedafa270f1c4dc30c9c8ffd4cf619895e688e6
Themes: Retrieve explicitly themed context and resources
Id: I4e41c251aee47361b183b60089bf5666540f653e
Themes: Add themeChange config change to manifest
Id: Ia84c0089a79637906e4f75fa38a56e8ff3b21a2b
Themes: Register THEME_SERVICE in ContextImpl
Id: I608a0b65c7e2ff0d69bae7bf343916f2b985f4a0
Themes: Remove legacy theme support
Id: I25887843d31f705425aa40f9a23482fd2cafaef8
androidfw: correctly index paths in idmap
Since we added the mtime values for the target and overlay, the
indices are increased by two.
Id: Ie0f5474d425945d58a12021cd2739240d2e98c0a
androidfw: Fix opening assets from theme resources
Id: Iedb51163a62b046cdf7fda1ad1b55cc1ee409047
Themes: Consider overlaid resource as "best fit"
Id: Ife8342a49eb9502be52f085f88161b113332e9e6
Themes: Save and restore theme config
Id: I3fcd445fb458aa6ed09397c05df6eb66d9be7235
Themes: Let ThemeService process additional themes
Id: I45837f26948367d5cc6c520e8c53f9da60bd1fda
AAPT: Don't applyVersionForCompatibility on android
When compiling themes with aapt, we do not want aapt to call
applyVersionForCompatibility as this causes the entries in the
resource table to have an incorrect path.
Id: Ie2c69533b3659c7b7458d6e4b7bdc84946d1be8e
androidfw: Don't consider package id 0x5f as dynamic
Package id 0x5f is reserved for common overlay resources and needs
to be reserved so that it is not considered a dynamic.
Id: Id27b8e0e2231ee8541365274d512e347afcfd05b
AAPT: Include resources.arsc in apk
Common resources needs the resources.arsc in the resources.apk
so that it can be included when processing other overlays. Without
it, common resources cannot be referenced.
Id: I4aee29f660e4a0aa1909240dc0ca5680f0a2d135
Themes: Add keyguard wallpaper support to theme service
Id: Ib8f8acd55ab4d2b6ef06ee0a630dc50c4f870beb
Themes: Don't pre-process non .9.png images
When creating a resources.apk we do not need to pre-process
the normal .png images as those can be referenced directly from
the theme's apk.
Id: Iaf846a03ead9ecb1e68c040eac6e0ecbfc6e5875
Themes: Adjust offsets for idmap hashes
Idmap now has a header so the indices to the hashes need to be
incremented by one.
Id: If1fb183cc116ef9e3ad6cb4e17b6e44763e9e72a
Themes: Use single ThemeInfo instead of an array
We only ever used the first index so there is no need to use
an array of ThemeInfo(s)
Id: I9e2af076bc17396a0c978be3c0d31c41277db3df
New converter for Kitkat -> L fonts.xml
L introduced a new fonts xml format. Its great, but our themes will
keep using hte old format. This provides a converter and
test cases. The parser was taken from the chooser and remains
mostly unchanged with the exception of a getName() helper method.
Id: Ia1d42c9e50eb7b52d2d98fe6dbeee530bef3adc2
Themes: Port theme bootanimation support to CM12
Id: Ie016884b0e3b77e08732308923ac44e0975e0116
Resources: Clear drawable cache
Id: I04b5b78cce703194a2baeff9c51d2e4733b8ccc9
Font switching
Id: Ia43060a7db624102cdcd9b0d9dc7148441401584
Zygote changes
Id: Ie3681cf0d2b9929661cf1214e899cef9a5f37471
Recreate String Blocks
Id: I4747ebd1a0908b76ae7214b0584948353d426fc5
add a getter for the x and y offsets of the wallpaper window
Id: I35294bcac664e85cc5d344b50b5c4335a60d3f37
Themes: Don't spam logcat with CREATING STRING CACHE
When processing resources with AAPT on the device, it spams the
logcat with warning messages about CREATING STRING CACHE. Change
ALOGW to ALOGV so it will only show when verbose logging is enabled.
Id: I5b591c3336e176dd71cebe672d60721c29651b00
SystemUI: Audio Volume Panel
Id: I78c471864af401b274597339b8451e65931fdb32
AmService uiContext port
Id: Ida251d7f80797b0ec78b3d20cf60a795d6c4c1f0
Cleanly detach theme assets
Types from an overlay are added to the target group's
TypeList and need to be removed when the overlay assets
are detached. Failing to remove these types results in
resources not being retieved due to the erroneous types.
Id: I4a9c624e30309e61fce905ced45c55acd3ac4845
Themes: GlobalAction Port
Id: Ifd87e04f94a284e77f1c48bec9fd75d69c45c47e
Themes: Do not store forward locked themes in ASEC containers
If a theme is in a asec container and is applied, when the
device is rebooted the device will get stuck in a nasty boot loop since
the theme resources must be read and the asec container is not ready yet.
Id: I1d93d8175d5c40b34c222974960c43352012a5ad
Use systemui's applied theme for notifications.
Notifications contain RemoteViews which are inflated using the
application's context for which this notification belongs to. This
can look out of place if SystemUI is using a different theme than the
rest of the system.
This patch will use SystemUIs theme when inflating the RemoteViews,
giving us a more consistent look in the notification drawer.
Id: I9514ce7fcc4858bad3d3c4190f55c1f5a1441d7c
SysUI: Add theme support
This ports over the changes needed to facilitate a theme change in
SystemUI.
Id: I673fb79db90994371a9c0627746a97414132f0ba
Themes: Allow composing of VectorDrawable
Base icons can be vector drawables. This patch allows them to be
composed. Currently, VectorDrawables cannot have filters applied
since they do not have a method to get the Paint object like
BitmapDrawable and PaintDrawable.
Id: I762c8e1f4d1c945b8ebc164bbd7944120324bd42
Themes: Add target api to ThemesContract
This will allow the ThemesProvider to track the api a theme was
built for. We may want to let the user know when a theme may not
be designed for the version of CM installed on their device.
Id: Idf0e6cef0ce9ac5e221ce5ff7e0b155ae0258d5f
Access Themed ResTables from compiled theme apk [1/2]
Before this patch the ResTable for a theme/app was created and
accessed seperate from the compiled APK. Since the compiled APK
has its own copy of the resources.arsc, we can just reuse the table
in the APK instead.
Id: I106a2434e74784bc04014831098f49fe128bc7e2
Themes: Port AppsLaunchFailureReceiver to CM12
Id: I5c3265e64aef1536ba5fceed0ec89082e786b686
Themes: Bump idmap hash version to 3
Due to changes in idmap, we need to force the recreation of resource
cache when upgrading from CM11 to CM12.
Id: I25c1e2c598bca889818e2d685651e3214c30ab3c
Remove debug logs
Id: Ia5cfa83ddf6da195e20526a94ba154864b8d0ecb
Send target sdk version to aapt [1/2]
If vector drawables are used in a theme we must have a minSdkVersion of 21 passed
to aapt or else aapt will Segfault.
Id: I687ee146f9f80543bbcdd06d93891cb3b23001c4
Add missing imports to ActivityThread
Id: I09fe07807ed824ccb938e0e174b06653c613c403
Themes: Dynamically add/remove content from StatusBarWindow
StatusBarWindowView has logic for resizing and fading content
which doesn't always behave correctly if this view is not the
root. Rather than create a container, this patch uses the
existing StatusBarWindowView as the container and the inflated
status bar is then added to this view.
Id: Ia93d25a589419145f95d75b1b56eb3c2f300f935
Themes: don't use preloaded drawables when themed
If we have themed assets we should try and load those rather than
pulling from the preloaded drawables. This allows us to continue
and preload drawables in ZygoteInit while maintaining the ability
to theme those preloaded assets.
Id: I68cfc099d328ece0791b6d0e5cf11d07097fd1fd
CM11 -> CM12 Upgrade [1/3]
- Introduce a new secure setting "THEME_PREV_BOOT_API_LEVEL".
This field will always be set to the previous api level for themes.
So if we upgrade from CM11 to CM12 this value will differ from the current API
causing an upgrade to trigger
- When moving from CM11 -> CM12, unapply incompatible overlays
- Rename "holo" to "system" in secure settings themeConfig
- Provide a testing downgrade script to put the secure settings db into a state
similiar to CM11 (at least for themes)
Id: I71be2c0ad83e60ffe8c574f913e5eaecb9700045
Themes: Add constant for system target API
Id: I0a6caf65c9e8b0feeef1ae848ba4683235304e8c
Change-Id: Ide6d4e1daf535a54efb1ec7cf39ef8b2fb8cf272
Diffstat (limited to 'tools/aapt')
-rw-r--r-- | tools/aapt/AaptAssets.cpp | 151 | ||||
-rw-r--r-- | tools/aapt/AaptAssets.h | 21 | ||||
-rw-r--r-- | tools/aapt/AaptConfig.cpp | 11 | ||||
-rw-r--r-- | tools/aapt/Bundle.h | 17 | ||||
-rw-r--r-- | tools/aapt/Command.cpp | 33 | ||||
-rw-r--r-- | tools/aapt/Images.cpp | 95 | ||||
-rw-r--r-- | tools/aapt/Images.h | 16 | ||||
-rw-r--r-- | tools/aapt/Main.cpp | 47 | ||||
-rw-r--r-- | tools/aapt/Main.h | 9 | ||||
-rw-r--r-- | tools/aapt/Package.cpp | 306 | ||||
-rw-r--r-- | tools/aapt/Resource.cpp | 24 | ||||
-rw-r--r-- | tools/aapt/ResourceTable.cpp | 7 | ||||
-rw-r--r-- | tools/aapt/ResourceTable.h | 3 | ||||
-rw-r--r-- | tools/aapt/XMLNode.cpp | 43 | ||||
-rw-r--r-- | tools/aapt/XMLNode.h | 3 | ||||
-rw-r--r-- | tools/aapt/ZipFile.cpp | 65 | ||||
-rw-r--r-- | tools/aapt/ZipFile.h | 1 | ||||
-rw-r--r-- | tools/aapt/tests/ZipReading_test.cpp | 156 | ||||
-rw-r--r-- | tools/aapt/tests/mocks/MockZipEntry.h | 29 | ||||
-rw-r--r-- | tools/aapt/tests/mocks/MockZipFile.h | 29 |
20 files changed, 908 insertions, 158 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/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..4c10868 100644 --- a/tools/aapt/Command.cpp +++ b/tools/aapt/Command.cpp @@ -2439,21 +2439,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..528a960 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,29 +1278,39 @@ 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; @@ -1300,12 +1319,7 @@ status_t preProcessImage(const Bundle* bundle, const sp<AaptAssets>& /* assets * status_t error = UNKNOWN_ERROR; - fp = fopen(file->getSourceFile().string(), "rb"); - if (fp == NULL) { - fprintf(stderr, "%s: ERROR: Unable to open PNG file\n", printableName.string()); - goto bail; - } - + 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 +1331,47 @@ 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) { + PngMemoryFile* pmf = new PngMemoryFile(); + + ZipFile* 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()); + return err; + } + + 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 +1396,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: @@ -1511,3 +1566,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..c636c28 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) { diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp index 1f736d0..889883a 100644 --- a/tools/aapt/ResourceTable.cpp +++ b/tools/aapt/ResourceTable.cpp @@ -1753,7 +1753,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 +1779,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); 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/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 |