am afd0debe: am dacd4159: merge in klp-release (no-op)
* commit 'afd0debe4bdf47dc0f968282ca1261842bb65d60':
Change API from flush(handle) to flush(). Call flush on all active sensors in the given SensorEventConnection.
Eliminate latency when resampling is disabled
Fix blank / partial screenshots
Set the outbuf acquire fence after we actually have it.
only clear FB when asked for the opaque layer
Treat composition frames with no layers as using GLES composition
Fix two EGLConfig selection bugs
fix crashers with wifi/virtual displays
diff --git a/cmds/flatland/Main.cpp b/cmds/flatland/Main.cpp
index d6ac3d2..e80dbb1 100644
--- a/cmds/flatland/Main.cpp
+++ b/cmds/flatland/Main.cpp
@@ -73,7 +73,7 @@
},
},
- { "3:2 Single Static Window",
+ { "4:3 Single Static Window",
2048, 1536, { 1536 },
{
{ // Window
@@ -117,7 +117,7 @@
},
},
- { "3:2 App -> Home Transition",
+ { "4:3 App -> Home Transition",
2048, 1536, { 1536 },
{
{ // Wallpaper
@@ -173,7 +173,7 @@
},
},
- { "3:2 SurfaceView -> Home Transition",
+ { "4:3 SurfaceView -> Home Transition",
2048, 1536, { 1536 },
{
{ // Wallpaper
diff --git a/cmds/servicemanager/service_manager.c b/cmds/servicemanager/service_manager.c
index 3eaf1eb..11120f5 100644
--- a/cmds/servicemanager/service_manager.c
+++ b/cmds/servicemanager/service_manager.c
@@ -32,6 +32,8 @@
{ AID_MEDIA, "media.player" },
{ AID_MEDIA, "media.camera" },
{ AID_MEDIA, "media.audio_policy" },
+ { AID_AUDIO, "audio" },
+ { AID_INPUT, "input" },
{ AID_DRM, "drm.drmManager" },
{ AID_NFC, "nfc" },
{ AID_BLUETOOTH, "bluetooth" },
diff --git a/include/androidfw/Asset.h b/include/androidfw/Asset.h
new file mode 100644
index 0000000..1fe0e06
--- /dev/null
+++ b/include/androidfw/Asset.h
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Class providing access to a read-only asset. Asset objects are NOT
+// thread-safe, and should not be shared across threads.
+//
+#ifndef __LIBS_ASSET_H
+#define __LIBS_ASSET_H
+
+#include <stdio.h>
+#include <sys/types.h>
+
+#include <utils/Compat.h>
+#include <utils/Errors.h>
+#include <utils/FileMap.h>
+#include <utils/String8.h>
+
+namespace android {
+
+/*
+ * Instances of this class provide read-only operations on a byte stream.
+ *
+ * Access may be optimized for streaming, random, or whole buffer modes. All
+ * operations are supported regardless of how the file was opened, but some
+ * things will be less efficient. [pass that in??]
+ *
+ * "Asset" is the base class for all types of assets. The classes below
+ * provide most of the implementation. The AssetManager uses one of the
+ * static "create" functions defined here to create a new instance.
+ */
+class Asset {
+public:
+ virtual ~Asset(void);
+
+ static int32_t getGlobalCount();
+ static String8 getAssetAllocations();
+
+ /* used when opening an asset */
+ typedef enum AccessMode {
+ ACCESS_UNKNOWN = 0,
+
+ /* read chunks, and seek forward and backward */
+ ACCESS_RANDOM,
+
+ /* read sequentially, with an occasional forward seek */
+ ACCESS_STREAMING,
+
+ /* caller plans to ask for a read-only buffer with all data */
+ ACCESS_BUFFER,
+ } AccessMode;
+
+ /*
+ * Read data from the current offset. Returns the actual number of
+ * bytes read, 0 on EOF, or -1 on error.
+ */
+ virtual ssize_t read(void* buf, size_t count) = 0;
+
+ /*
+ * Seek to the specified offset. "whence" uses the same values as
+ * lseek/fseek. Returns the new position on success, or (off64_t) -1
+ * on failure.
+ */
+ virtual off64_t seek(off64_t offset, int whence) = 0;
+
+ /*
+ * Close the asset, freeing all associated resources.
+ */
+ virtual void close(void) = 0;
+
+ /*
+ * Get a pointer to a buffer with the entire contents of the file.
+ */
+ virtual const void* getBuffer(bool wordAligned) = 0;
+
+ /*
+ * Get the total amount of data that can be read.
+ */
+ virtual off64_t getLength(void) const = 0;
+
+ /*
+ * Get the total amount of data that can be read from the current position.
+ */
+ virtual off64_t getRemainingLength(void) const = 0;
+
+ /*
+ * Open a new file descriptor that can be used to read this asset.
+ * Returns -1 if you can not use the file descriptor (for example if the
+ * asset is compressed).
+ */
+ virtual int openFileDescriptor(off64_t* outStart, off64_t* outLength) const = 0;
+
+ /*
+ * Return whether this asset's buffer is allocated in RAM (not mmapped).
+ * Note: not virtual so it is safe to call even when being destroyed.
+ */
+ virtual bool isAllocated(void) const { return false; }
+
+ /*
+ * Get a string identifying the asset's source. This might be a full
+ * path, it might be a colon-separated list of identifiers.
+ *
+ * This is NOT intended to be used for anything except debug output.
+ * DO NOT try to parse this or use it to open a file.
+ */
+ const char* getAssetSource(void) const { return mAssetSource.string(); }
+
+protected:
+ Asset(void); // constructor; only invoked indirectly
+
+ /* handle common seek() housekeeping */
+ off64_t handleSeek(off64_t offset, int whence, off64_t curPosn, off64_t maxPosn);
+
+ /* set the asset source string */
+ void setAssetSource(const String8& path) { mAssetSource = path; }
+
+ AccessMode getAccessMode(void) const { return mAccessMode; }
+
+private:
+ /* these operations are not implemented */
+ Asset(const Asset& src);
+ Asset& operator=(const Asset& src);
+
+ /* AssetManager needs access to our "create" functions */
+ friend class AssetManager;
+
+ /*
+ * Create the asset from a named file on disk.
+ */
+ static Asset* createFromFile(const char* fileName, AccessMode mode);
+
+ /*
+ * Create the asset from a named, compressed file on disk (e.g. ".gz").
+ */
+ static Asset* createFromCompressedFile(const char* fileName,
+ AccessMode mode);
+
+#if 0
+ /*
+ * Create the asset from a segment of an open file. This will fail
+ * if "offset" and "length" don't fit within the bounds of the file.
+ *
+ * The asset takes ownership of the file descriptor.
+ */
+ static Asset* createFromFileSegment(int fd, off64_t offset, size_t length,
+ AccessMode mode);
+
+ /*
+ * Create from compressed data. "fd" should be seeked to the start of
+ * the compressed data. This could be inside a gzip file or part of a
+ * Zip archive.
+ *
+ * The asset takes ownership of the file descriptor.
+ *
+ * This may not verify the validity of the compressed data until first
+ * use.
+ */
+ static Asset* createFromCompressedData(int fd, off64_t offset,
+ int compressionMethod, size_t compressedLength,
+ size_t uncompressedLength, AccessMode mode);
+#endif
+
+ /*
+ * Create the asset from a memory-mapped file segment.
+ *
+ * The asset takes ownership of the FileMap.
+ */
+ static Asset* createFromUncompressedMap(FileMap* dataMap, AccessMode mode);
+
+ /*
+ * Create the asset from a memory-mapped file segment with compressed
+ * data. "method" is a Zip archive compression method constant.
+ *
+ * The asset takes ownership of the FileMap.
+ */
+ static Asset* createFromCompressedMap(FileMap* dataMap, int method,
+ size_t uncompressedLen, AccessMode mode);
+
+
+ /*
+ * Create from a reference-counted chunk of shared memory.
+ */
+ // TODO
+
+ AccessMode mAccessMode; // how the asset was opened
+ String8 mAssetSource; // debug string
+
+ Asset* mNext; // linked list.
+ Asset* mPrev;
+};
+
+
+/*
+ * ===========================================================================
+ *
+ * Innards follow. Do not use these classes directly.
+ */
+
+/*
+ * An asset based on an uncompressed file on disk. It may encompass the
+ * entire file or just a piece of it. Access is through fread/fseek.
+ */
+class _FileAsset : public Asset {
+public:
+ _FileAsset(void);
+ virtual ~_FileAsset(void);
+
+ /*
+ * Use a piece of an already-open file.
+ *
+ * On success, the object takes ownership of "fd".
+ */
+ status_t openChunk(const char* fileName, int fd, off64_t offset, size_t length);
+
+ /*
+ * Use a memory-mapped region.
+ *
+ * On success, the object takes ownership of "dataMap".
+ */
+ status_t openChunk(FileMap* dataMap);
+
+ /*
+ * Standard Asset interfaces.
+ */
+ virtual ssize_t read(void* buf, size_t count);
+ virtual off64_t seek(off64_t offset, int whence);
+ virtual void close(void);
+ virtual const void* getBuffer(bool wordAligned);
+ virtual off64_t getLength(void) const { return mLength; }
+ virtual off64_t getRemainingLength(void) const { return mLength-mOffset; }
+ virtual int openFileDescriptor(off64_t* outStart, off64_t* outLength) const;
+ virtual bool isAllocated(void) const { return mBuf != NULL; }
+
+private:
+ off64_t mStart; // absolute file offset of start of chunk
+ off64_t mLength; // length of the chunk
+ off64_t mOffset; // current local offset, 0 == mStart
+ FILE* mFp; // for read/seek
+ char* mFileName; // for opening
+
+ /*
+ * To support getBuffer() we either need to read the entire thing into
+ * a buffer or memory-map it. For small files it's probably best to
+ * just read them in.
+ */
+ enum { kReadVsMapThreshold = 4096 };
+
+ FileMap* mMap; // for memory map
+ unsigned char* mBuf; // for read
+
+ const void* ensureAlignment(FileMap* map);
+};
+
+
+/*
+ * An asset based on compressed data in a file.
+ */
+class _CompressedAsset : public Asset {
+public:
+ _CompressedAsset(void);
+ virtual ~_CompressedAsset(void);
+
+ /*
+ * Use a piece of an already-open file.
+ *
+ * On success, the object takes ownership of "fd".
+ */
+ status_t openChunk(int fd, off64_t offset, int compressionMethod,
+ size_t uncompressedLen, size_t compressedLen);
+
+ /*
+ * Use a memory-mapped region.
+ *
+ * On success, the object takes ownership of "fd".
+ */
+ status_t openChunk(FileMap* dataMap, int compressionMethod,
+ size_t uncompressedLen);
+
+ /*
+ * Standard Asset interfaces.
+ */
+ virtual ssize_t read(void* buf, size_t count);
+ virtual off64_t seek(off64_t offset, int whence);
+ virtual void close(void);
+ virtual const void* getBuffer(bool wordAligned);
+ virtual off64_t getLength(void) const { return mUncompressedLen; }
+ virtual off64_t getRemainingLength(void) const { return mUncompressedLen-mOffset; }
+ virtual int openFileDescriptor(off64_t* outStart, off64_t* outLength) const { return -1; }
+ virtual bool isAllocated(void) const { return mBuf != NULL; }
+
+private:
+ off64_t mStart; // offset to start of compressed data
+ off64_t mCompressedLen; // length of the compressed data
+ off64_t mUncompressedLen; // length of the uncompressed data
+ off64_t mOffset; // current offset, 0 == start of uncomp data
+
+ FileMap* mMap; // for memory-mapped input
+ int mFd; // for file input
+
+ class StreamingZipInflater* mZipInflater; // for streaming large compressed assets
+
+ unsigned char* mBuf; // for getBuffer()
+};
+
+// need: shared mmap version?
+
+}; // namespace android
+
+#endif // __LIBS_ASSET_H
diff --git a/include/androidfw/AssetDir.h b/include/androidfw/AssetDir.h
new file mode 100644
index 0000000..bd89d7d
--- /dev/null
+++ b/include/androidfw/AssetDir.h
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Access a chunk of the asset hierarchy as if it were a single directory.
+//
+#ifndef __LIBS_ASSETDIR_H
+#define __LIBS_ASSETDIR_H
+
+#include <androidfw/misc.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+#include <utils/SortedVector.h>
+#include <sys/types.h>
+
+namespace android {
+
+/*
+ * This provides vector-style access to a directory. We do this rather
+ * than modeling opendir/readdir access because it's simpler and the
+ * nature of the operation requires us to have all data on hand anyway.
+ *
+ * The list of files will be sorted in ascending order by ASCII value.
+ *
+ * The contents are populated by our friend, the AssetManager.
+ */
+class AssetDir {
+public:
+ AssetDir(void)
+ : mFileInfo(NULL)
+ {}
+ virtual ~AssetDir(void) {
+ delete mFileInfo;
+ }
+
+ /*
+ * Vector-style access.
+ */
+ size_t getFileCount(void) { return mFileInfo->size(); }
+ const String8& getFileName(int idx) {
+ return mFileInfo->itemAt(idx).getFileName();
+ }
+ const String8& getSourceName(int idx) {
+ return mFileInfo->itemAt(idx).getSourceName();
+ }
+
+ /*
+ * Get the type of a file (usually regular or directory).
+ */
+ FileType getFileType(int idx) {
+ return mFileInfo->itemAt(idx).getFileType();
+ }
+
+private:
+ /* these operations are not implemented */
+ AssetDir(const AssetDir& src);
+ const AssetDir& operator=(const AssetDir& src);
+
+ friend class AssetManager;
+
+ /*
+ * This holds information about files in the asset hierarchy.
+ */
+ class FileInfo {
+ public:
+ FileInfo(void) {}
+ FileInfo(const String8& path) // useful for e.g. svect.indexOf
+ : mFileName(path), mFileType(kFileTypeUnknown)
+ {}
+ ~FileInfo(void) {}
+ FileInfo(const FileInfo& src) {
+ copyMembers(src);
+ }
+ const FileInfo& operator= (const FileInfo& src) {
+ if (this != &src)
+ copyMembers(src);
+ return *this;
+ }
+
+ void copyMembers(const FileInfo& src) {
+ mFileName = src.mFileName;
+ mFileType = src.mFileType;
+ mSourceName = src.mSourceName;
+ }
+
+ /* need this for SortedVector; must compare only on file name */
+ bool operator< (const FileInfo& rhs) const {
+ return mFileName < rhs.mFileName;
+ }
+
+ /* used by AssetManager */
+ bool operator== (const FileInfo& rhs) const {
+ return mFileName == rhs.mFileName;
+ }
+
+ void set(const String8& path, FileType type) {
+ mFileName = path;
+ mFileType = type;
+ }
+
+ const String8& getFileName(void) const { return mFileName; }
+ void setFileName(const String8& path) { mFileName = path; }
+
+ FileType getFileType(void) const { return mFileType; }
+ void setFileType(FileType type) { mFileType = type; }
+
+ const String8& getSourceName(void) const { return mSourceName; }
+ void setSourceName(const String8& path) { mSourceName = path; }
+
+ /*
+ * Handy utility for finding an entry in a sorted vector of FileInfo.
+ * Returns the index of the matching entry, or -1 if none found.
+ */
+ static int findEntry(const SortedVector<FileInfo>* pVector,
+ const String8& fileName);
+
+ private:
+ String8 mFileName; // filename only
+ FileType mFileType; // regular, directory, etc
+
+ String8 mSourceName; // currently debug-only
+ };
+
+ /* AssetManager uses this to initialize us */
+ void setFileList(SortedVector<FileInfo>* list) { mFileInfo = list; }
+
+ SortedVector<FileInfo>* mFileInfo;
+};
+
+}; // namespace android
+
+#endif // __LIBS_ASSETDIR_H
diff --git a/include/androidfw/AssetManager.h b/include/androidfw/AssetManager.h
new file mode 100644
index 0000000..d95b45e
--- /dev/null
+++ b/include/androidfw/AssetManager.h
@@ -0,0 +1,374 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Asset management class. AssetManager objects are thread-safe.
+//
+#ifndef __LIBS_ASSETMANAGER_H
+#define __LIBS_ASSETMANAGER_H
+
+#include <androidfw/Asset.h>
+#include <androidfw/AssetDir.h>
+#include <androidfw/ZipFileRO.h>
+#include <utils/KeyedVector.h>
+#include <utils/SortedVector.h>
+#include <utils/String16.h>
+#include <utils/String8.h>
+#include <utils/threads.h>
+#include <utils/Vector.h>
+
+/*
+ * Native-app access is via the opaque typedef struct AAssetManager in the C namespace.
+ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct AAssetManager { };
+
+#ifdef __cplusplus
+};
+#endif
+
+
+/*
+ * Now the proper C++ android-namespace definitions
+ */
+
+namespace android {
+
+class Asset; // fwd decl for things that include Asset.h first
+class ResTable;
+struct ResTable_config;
+
+/*
+ * Every application that uses assets needs one instance of this. A
+ * single instance may be shared across multiple threads, and a single
+ * thread may have more than one instance (the latter is discouraged).
+ *
+ * The purpose of the AssetManager is to create Asset objects. To do
+ * this efficiently it may cache information about the locations of
+ * files it has seen. This can be controlled with the "cacheMode"
+ * argument.
+ *
+ * The asset hierarchy may be examined like a filesystem, using
+ * AssetDir objects to peruse a single directory.
+ */
+class AssetManager : public AAssetManager {
+public:
+ typedef enum CacheMode {
+ CACHE_UNKNOWN = 0,
+ CACHE_OFF, // don't try to cache file locations
+ CACHE_DEFER, // construct cache as pieces are needed
+ //CACHE_SCAN, // scan full(!) asset hierarchy at init() time
+ } CacheMode;
+
+ AssetManager(CacheMode cacheMode = CACHE_OFF);
+ virtual ~AssetManager(void);
+
+ static int32_t getGlobalCount();
+
+ /*
+ * Add a new source for assets. This can be called multiple times to
+ * look in multiple places for assets. It can be either a directory (for
+ * finding assets as raw files on the disk) or a ZIP file. This newly
+ * added asset path will be examined first when searching for assets,
+ * before any that were previously added.
+ *
+ * Returns "true" on success, "false" on failure. If 'cookie' is non-NULL,
+ * then on success, *cookie is set to the value corresponding to the
+ * newly-added asset source.
+ */
+ bool addAssetPath(const String8& path, void** cookie);
+
+ /*
+ * Convenience for adding the standard system assets. Uses the
+ * ANDROID_ROOT environment variable to find them.
+ */
+ bool addDefaultAssets();
+
+ /*
+ * Iterate over the asset paths in this manager. (Previously
+ * added via addAssetPath() and addDefaultAssets().) On first call,
+ * 'cookie' must be NULL, resulting in the first cookie being returned.
+ * Each next cookie will be returned there-after, until NULL indicating
+ * the end has been reached.
+ */
+ void* nextAssetPath(void* cookie) const;
+
+ /*
+ * Return an asset path in the manager. 'which' must be between 0 and
+ * countAssetPaths().
+ */
+ String8 getAssetPath(void* cookie) const;
+
+ /*
+ * Set the current locale and vendor. The locale can change during
+ * the lifetime of an AssetManager if the user updates the device's
+ * language setting. The vendor is less likely to change.
+ *
+ * Pass in NULL to indicate no preference.
+ */
+ void setLocale(const char* locale);
+ void setVendor(const char* vendor);
+
+ /*
+ * Choose screen orientation for resources values returned.
+ */
+ void setConfiguration(const ResTable_config& config, const char* locale = NULL);
+
+ void getConfiguration(ResTable_config* outConfig) const;
+
+ typedef Asset::AccessMode AccessMode; // typing shortcut
+
+ /*
+ * Open an asset.
+ *
+ * This will search through locale-specific and vendor-specific
+ * directories and packages to find the file.
+ *
+ * The object returned does not depend on the AssetManager. It should
+ * be freed by calling Asset::close().
+ */
+ Asset* open(const char* fileName, AccessMode mode);
+
+ /*
+ * Open a non-asset file as an asset.
+ *
+ * This is for opening files that are included in an asset package
+ * but aren't assets. These sit outside the usual "locale/vendor"
+ * path hierarchy, and will not be seen by "AssetDir" or included
+ * in our filename cache.
+ */
+ Asset* openNonAsset(const char* fileName, AccessMode mode);
+
+ /*
+ * Explicit non-asset file. The file explicitly named by the cookie (the
+ * resource set to look in) and fileName will be opened and returned.
+ */
+ Asset* openNonAsset(void* cookie, const char* fileName, AccessMode mode);
+
+ /*
+ * Open a directory within the asset hierarchy.
+ *
+ * The contents of the directory are an amalgam of vendor-specific,
+ * locale-specific, and generic assets stored loosely or in asset
+ * packages. Depending on the cache setting and previous accesses,
+ * this call may incur significant disk overhead.
+ *
+ * To open the top-level directory, pass in "".
+ */
+ AssetDir* openDir(const char* dirName);
+
+ /*
+ * Open a directory within a particular path of the asset manager.
+ *
+ * The contents of the directory are an amalgam of vendor-specific,
+ * locale-specific, and generic assets stored loosely or in asset
+ * packages. Depending on the cache setting and previous accesses,
+ * this call may incur significant disk overhead.
+ *
+ * To open the top-level directory, pass in "".
+ */
+ AssetDir* openNonAssetDir(void* cookie, const char* dirName);
+
+ /*
+ * Get the type of a file in the asset hierarchy. They will either
+ * be "regular" or "directory". [Currently only works for "regular".]
+ *
+ * Can also be used as a quick test for existence of a file.
+ */
+ FileType getFileType(const char* fileName);
+
+ /*
+ * Return the complete resource table to find things in the package.
+ */
+ const ResTable& getResources(bool required = true) const;
+
+ /*
+ * Discard cached filename information. This only needs to be called
+ * if somebody has updated the set of "loose" files, and we want to
+ * discard our cached notion of what's where.
+ */
+ void purge(void) { purgeFileNameCacheLocked(); }
+
+ /*
+ * Return true if the files this AssetManager references are all
+ * up-to-date (have not been changed since it was created). If false
+ * is returned, you will need to create a new AssetManager to get
+ * the current data.
+ */
+ bool isUpToDate();
+
+ /**
+ * Get the known locales for this asset manager object.
+ */
+ void getLocales(Vector<String8>* locales) const;
+
+private:
+ struct asset_path
+ {
+ String8 path;
+ FileType type;
+ String8 idmap;
+ };
+
+ Asset* openInPathLocked(const char* fileName, AccessMode mode,
+ const asset_path& path);
+ Asset* openNonAssetInPathLocked(const char* fileName, AccessMode mode,
+ const asset_path& path);
+ Asset* openInLocaleVendorLocked(const char* fileName, AccessMode mode,
+ const asset_path& path, const char* locale, const char* vendor);
+ String8 createPathNameLocked(const asset_path& path, const char* locale,
+ const char* vendor);
+ String8 createPathNameLocked(const asset_path& path, const char* rootDir);
+ String8 createZipSourceNameLocked(const String8& zipFileName,
+ const String8& dirName, const String8& fileName);
+
+ ZipFileRO* getZipFileLocked(const asset_path& path);
+ Asset* openAssetFromFileLocked(const String8& fileName, AccessMode mode);
+ Asset* openAssetFromZipLocked(const ZipFileRO* pZipFile,
+ const ZipEntryRO entry, AccessMode mode, const String8& entryName);
+
+ bool scanAndMergeDirLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo,
+ const asset_path& path, const char* rootDir, const char* dirName);
+ SortedVector<AssetDir::FileInfo>* scanDirLocked(const String8& path);
+ bool scanAndMergeZipLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo,
+ const asset_path& path, const char* rootDir, const char* dirName);
+ void mergeInfoLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo,
+ const SortedVector<AssetDir::FileInfo>* pContents);
+
+ void loadFileNameCacheLocked(void);
+ void fncScanLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo,
+ const char* dirName);
+ bool fncScanAndMergeDirLocked(
+ SortedVector<AssetDir::FileInfo>* pMergedInfo,
+ const asset_path& path, const char* locale, const char* vendor,
+ const char* dirName);
+ void purgeFileNameCacheLocked(void);
+
+ const ResTable* getResTable(bool required = true) const;
+ void setLocaleLocked(const char* locale);
+ void updateResourceParamsLocked() const;
+
+ bool createIdmapFileLocked(const String8& originalPath, const String8& overlayPath,
+ const String8& idmapPath);
+
+ bool isIdmapStaleLocked(const String8& originalPath, const String8& overlayPath,
+ const String8& idmapPath);
+
+ Asset* openIdmapLocked(const struct asset_path& ap) const;
+
+ bool getZipEntryCrcLocked(const String8& zipPath, const char* entryFilename, uint32_t* pCrc);
+
+ class SharedZip : public RefBase {
+ public:
+ static sp<SharedZip> get(const String8& path);
+
+ ZipFileRO* getZip();
+
+ Asset* getResourceTableAsset();
+ Asset* setResourceTableAsset(Asset* asset);
+
+ ResTable* getResourceTable();
+ ResTable* setResourceTable(ResTable* res);
+
+ bool isUpToDate();
+
+ protected:
+ ~SharedZip();
+
+ private:
+ SharedZip(const String8& path, time_t modWhen);
+ SharedZip(); // <-- not implemented
+
+ String8 mPath;
+ ZipFileRO* mZipFile;
+ time_t mModWhen;
+
+ Asset* mResourceTableAsset;
+ ResTable* mResourceTable;
+
+ static Mutex gLock;
+ static DefaultKeyedVector<String8, wp<SharedZip> > gOpen;
+ };
+
+ /*
+ * Manage a set of Zip files. For each file we need a pointer to the
+ * ZipFile and a time_t with the file's modification date.
+ *
+ * We currently only have two zip files (current app, "common" app).
+ * (This was originally written for 8, based on app/locale/vendor.)
+ */
+ class ZipSet {
+ public:
+ ZipSet(void);
+ ~ZipSet(void);
+
+ /*
+ * Return a ZipFileRO structure for a ZipFileRO with the specified
+ * parameters.
+ */
+ ZipFileRO* getZip(const String8& path);
+
+ Asset* getZipResourceTableAsset(const String8& path);
+ Asset* setZipResourceTableAsset(const String8& path, Asset* asset);
+
+ ResTable* getZipResourceTable(const String8& path);
+ ResTable* setZipResourceTable(const String8& path, ResTable* res);
+
+ // generate path, e.g. "common/en-US-noogle.zip"
+ static String8 getPathName(const char* path);
+
+ bool isUpToDate();
+
+ private:
+ void closeZip(int idx);
+
+ int getIndex(const String8& zip) const;
+ mutable Vector<String8> mZipPath;
+ mutable Vector<sp<SharedZip> > mZipFile;
+ };
+
+ // Protect all internal state.
+ mutable Mutex mLock;
+
+ ZipSet mZipSet;
+
+ Vector<asset_path> mAssetPaths;
+ char* mLocale;
+ char* mVendor;
+
+ mutable ResTable* mResources;
+ ResTable_config* mConfig;
+
+ /*
+ * Cached data for "loose" files. This lets us avoid poking at the
+ * filesystem when searching for loose assets. Each entry is the
+ * "extended partial" path, e.g. "default/default/foo/bar.txt". The
+ * full set of files is present, including ".EXCLUDE" entries.
+ *
+ * We do not cache directory names. We don't retain the ".gz",
+ * because to our clients "foo" and "foo.gz" both look like "foo".
+ */
+ CacheMode mCacheMode; // is the cache enabled?
+ bool mCacheValid; // clear when locale or vendor changes
+ SortedVector<AssetDir::FileInfo> mCache;
+};
+
+}; // namespace android
+
+#endif // __LIBS_ASSETMANAGER_H
diff --git a/include/androidfw/BackupHelpers.h b/include/androidfw/BackupHelpers.h
new file mode 100644
index 0000000..1bb04a7
--- /dev/null
+++ b/include/androidfw/BackupHelpers.h
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UTILS_BACKUP_HELPERS_H
+#define _UTILS_BACKUP_HELPERS_H
+
+#include <utils/Errors.h>
+#include <utils/String8.h>
+#include <utils/KeyedVector.h>
+
+namespace android {
+
+enum {
+ BACKUP_HEADER_ENTITY_V1 = 0x61746144, // Data (little endian)
+};
+
+typedef struct {
+ int type; // BACKUP_HEADER_ENTITY_V1
+ int keyLen; // length of the key name, not including the null terminator
+ int dataSize; // size of the data, not including the padding, -1 means delete
+} entity_header_v1;
+
+struct SnapshotHeader {
+ int magic0;
+ int fileCount;
+ int magic1;
+ int totalSize;
+};
+
+struct FileState {
+ int modTime_sec;
+ int modTime_nsec;
+ int mode;
+ int size;
+ int crc32;
+ int nameLen;
+};
+
+struct FileRec {
+ String8 file;
+ bool deleted;
+ FileState s;
+};
+
+
+/**
+ * Writes the data.
+ *
+ * If an error occurs, it poisons this object and all write calls will fail
+ * with the error that occurred.
+ */
+class BackupDataWriter
+{
+public:
+ BackupDataWriter(int fd);
+ // does not close fd
+ ~BackupDataWriter();
+
+ status_t WriteEntityHeader(const String8& key, size_t dataSize);
+
+ /* Note: WriteEntityData will write arbitrary data into the file without
+ * validation or a previously-supplied header. The full backup implementation
+ * uses it this way to generate a controlled binary stream that is not
+ * entity-structured. If the implementation here is changed, either this
+ * use case must remain valid, or the full backup implementation should be
+ * adjusted to use some other appropriate mechanism.
+ */
+ status_t WriteEntityData(const void* data, size_t size);
+
+ void SetKeyPrefix(const String8& keyPrefix);
+
+private:
+ explicit BackupDataWriter();
+ status_t write_padding_for(int n);
+
+ int m_fd;
+ status_t m_status;
+ ssize_t m_pos;
+ int m_entityCount;
+ String8 m_keyPrefix;
+};
+
+/**
+ * Reads the data.
+ *
+ * If an error occurs, it poisons this object and all write calls will fail
+ * with the error that occurred.
+ */
+class BackupDataReader
+{
+public:
+ BackupDataReader(int fd);
+ // does not close fd
+ ~BackupDataReader();
+
+ status_t Status();
+ status_t ReadNextHeader(bool* done, int* type);
+
+ bool HasEntities();
+ status_t ReadEntityHeader(String8* key, size_t* dataSize);
+ status_t SkipEntityData(); // must be called with the pointer at the beginning of the data.
+ ssize_t ReadEntityData(void* data, size_t size);
+
+private:
+ explicit BackupDataReader();
+ status_t skip_padding();
+
+ int m_fd;
+ bool m_done;
+ status_t m_status;
+ ssize_t m_pos;
+ ssize_t m_dataEndPos;
+ int m_entityCount;
+ union {
+ int type;
+ entity_header_v1 entity;
+ } m_header;
+ String8 m_key;
+};
+
+int back_up_files(int oldSnapshotFD, BackupDataWriter* dataStream, int newSnapshotFD,
+ char const* const* files, char const* const *keys, int fileCount);
+
+int write_tarfile(const String8& packageName, const String8& domain,
+ const String8& rootPath, const String8& filePath, BackupDataWriter* outputStream);
+
+class RestoreHelperBase
+{
+public:
+ RestoreHelperBase();
+ ~RestoreHelperBase();
+
+ status_t WriteFile(const String8& filename, BackupDataReader* in);
+ status_t WriteSnapshot(int fd);
+
+private:
+ void* m_buf;
+ bool m_loggedUnknownMetadata;
+ KeyedVector<String8,FileRec> m_files;
+};
+
+#define TEST_BACKUP_HELPERS 1
+
+#if TEST_BACKUP_HELPERS
+int backup_helper_test_empty();
+int backup_helper_test_four();
+int backup_helper_test_files();
+int backup_helper_test_null_base();
+int backup_helper_test_missing_file();
+int backup_helper_test_data_writer();
+int backup_helper_test_data_reader();
+#endif
+
+} // namespace android
+
+#endif // _UTILS_BACKUP_HELPERS_H
diff --git a/include/androidfw/CursorWindow.h b/include/androidfw/CursorWindow.h
new file mode 100644
index 0000000..8a2979a
--- /dev/null
+++ b/include/androidfw/CursorWindow.h
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID__DATABASE_WINDOW_H
+#define _ANDROID__DATABASE_WINDOW_H
+
+#include <cutils/log.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include <binder/Parcel.h>
+#include <utils/String8.h>
+
+#if LOG_NDEBUG
+
+#define IF_LOG_WINDOW() if (false)
+#define LOG_WINDOW(...)
+
+#else
+
+#define IF_LOG_WINDOW() IF_ALOG(LOG_DEBUG, "CursorWindow")
+#define LOG_WINDOW(...) ALOG(LOG_DEBUG, "CursorWindow", __VA_ARGS__)
+
+#endif
+
+namespace android {
+
+/**
+ * This class stores a set of rows from a database in a buffer. The begining of the
+ * window has first chunk of RowSlots, which are offsets to the row directory, followed by
+ * an offset to the next chunk in a linked-list of additional chunk of RowSlots in case
+ * the pre-allocated chunk isn't big enough to refer to all rows. Each row directory has a
+ * FieldSlot per column, which has the size, offset, and type of the data for that field.
+ * Note that the data types come from sqlite3.h.
+ *
+ * Strings are stored in UTF-8.
+ */
+class CursorWindow {
+ CursorWindow(const String8& name, int ashmemFd,
+ void* data, size_t size, bool readOnly);
+
+public:
+ /* Field types. */
+ enum {
+ FIELD_TYPE_NULL = 0,
+ FIELD_TYPE_INTEGER = 1,
+ FIELD_TYPE_FLOAT = 2,
+ FIELD_TYPE_STRING = 3,
+ FIELD_TYPE_BLOB = 4,
+ };
+
+ /* Opaque type that describes a field slot. */
+ struct FieldSlot {
+ private:
+ int32_t type;
+ union {
+ double d;
+ int64_t l;
+ struct {
+ uint32_t offset;
+ uint32_t size;
+ } buffer;
+ } data;
+
+ friend class CursorWindow;
+ } __attribute((packed));
+
+ ~CursorWindow();
+
+ static status_t create(const String8& name, size_t size, CursorWindow** outCursorWindow);
+ static status_t createFromParcel(Parcel* parcel, CursorWindow** outCursorWindow);
+
+ status_t writeToParcel(Parcel* parcel);
+
+ inline String8 name() { return mName; }
+ inline size_t size() { return mSize; }
+ inline size_t freeSpace() { return mSize - mHeader->freeOffset; }
+ inline uint32_t getNumRows() { return mHeader->numRows; }
+ inline uint32_t getNumColumns() { return mHeader->numColumns; }
+
+ status_t clear();
+ status_t setNumColumns(uint32_t numColumns);
+
+ /**
+ * Allocate a row slot and its directory.
+ * The row is initialized will null entries for each field.
+ */
+ status_t allocRow();
+ status_t freeLastRow();
+
+ status_t putBlob(uint32_t row, uint32_t column, const void* value, size_t size);
+ status_t putString(uint32_t row, uint32_t column, const char* value, size_t sizeIncludingNull);
+ status_t putLong(uint32_t row, uint32_t column, int64_t value);
+ status_t putDouble(uint32_t row, uint32_t column, double value);
+ status_t putNull(uint32_t row, uint32_t column);
+
+ /**
+ * Gets the field slot at the specified row and column.
+ * Returns null if the requested row or column is not in the window.
+ */
+ FieldSlot* getFieldSlot(uint32_t row, uint32_t column);
+
+ inline int32_t getFieldSlotType(FieldSlot* fieldSlot) {
+ return fieldSlot->type;
+ }
+
+ inline int64_t getFieldSlotValueLong(FieldSlot* fieldSlot) {
+ return fieldSlot->data.l;
+ }
+
+ inline double getFieldSlotValueDouble(FieldSlot* fieldSlot) {
+ return fieldSlot->data.d;
+ }
+
+ inline const char* getFieldSlotValueString(FieldSlot* fieldSlot,
+ size_t* outSizeIncludingNull) {
+ *outSizeIncludingNull = fieldSlot->data.buffer.size;
+ return static_cast<char*>(offsetToPtr(fieldSlot->data.buffer.offset));
+ }
+
+ inline const void* getFieldSlotValueBlob(FieldSlot* fieldSlot, size_t* outSize) {
+ *outSize = fieldSlot->data.buffer.size;
+ return offsetToPtr(fieldSlot->data.buffer.offset);
+ }
+
+private:
+ static const size_t ROW_SLOT_CHUNK_NUM_ROWS = 100;
+
+ struct Header {
+ // Offset of the lowest unused byte in the window.
+ uint32_t freeOffset;
+
+ // Offset of the first row slot chunk.
+ uint32_t firstChunkOffset;
+
+ uint32_t numRows;
+ uint32_t numColumns;
+ };
+
+ struct RowSlot {
+ uint32_t offset;
+ };
+
+ struct RowSlotChunk {
+ RowSlot slots[ROW_SLOT_CHUNK_NUM_ROWS];
+ uint32_t nextChunkOffset;
+ };
+
+ String8 mName;
+ int mAshmemFd;
+ void* mData;
+ size_t mSize;
+ bool mReadOnly;
+ Header* mHeader;
+
+ inline void* offsetToPtr(uint32_t offset) {
+ return static_cast<uint8_t*>(mData) + offset;
+ }
+
+ inline uint32_t offsetFromPtr(void* ptr) {
+ return static_cast<uint8_t*>(ptr) - static_cast<uint8_t*>(mData);
+ }
+
+ /**
+ * Allocate a portion of the window. Returns the offset
+ * of the allocation, or 0 if there isn't enough space.
+ * If aligned is true, the allocation gets 4 byte alignment.
+ */
+ uint32_t alloc(size_t size, bool aligned = false);
+
+ RowSlot* getRowSlot(uint32_t row);
+ RowSlot* allocRowSlot();
+
+ status_t putBlobOrString(uint32_t row, uint32_t column,
+ const void* value, size_t size, int32_t type);
+};
+
+}; // namespace android
+
+#endif
diff --git a/include/androidfw/ObbFile.h b/include/androidfw/ObbFile.h
new file mode 100644
index 0000000..47559cd
--- /dev/null
+++ b/include/androidfw/ObbFile.h
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#ifndef OBBFILE_H_
+#define OBBFILE_H_
+
+#include <stdint.h>
+#include <strings.h>
+
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+
+namespace android {
+
+// OBB flags (bit 0)
+#define OBB_OVERLAY (1 << 0)
+#define OBB_SALTED (1 << 1)
+
+class ObbFile : public RefBase {
+protected:
+ virtual ~ObbFile();
+
+public:
+ ObbFile();
+
+ bool readFrom(const char* filename);
+ bool readFrom(int fd);
+ bool writeTo(const char* filename);
+ bool writeTo(int fd);
+ bool removeFrom(const char* filename);
+ bool removeFrom(int fd);
+
+ const char* getFileName() const {
+ return mFileName;
+ }
+
+ const String8 getPackageName() const {
+ return mPackageName;
+ }
+
+ void setPackageName(String8 packageName) {
+ mPackageName = packageName;
+ }
+
+ int32_t getVersion() const {
+ return mVersion;
+ }
+
+ void setVersion(int32_t version) {
+ mVersion = version;
+ }
+
+ int32_t getFlags() const {
+ return mFlags;
+ }
+
+ void setFlags(int32_t flags) {
+ mFlags = flags;
+ }
+
+ const unsigned char* getSalt(size_t* length) const {
+ if ((mFlags & OBB_SALTED) == 0) {
+ *length = 0;
+ return NULL;
+ }
+
+ *length = sizeof(mSalt);
+ return mSalt;
+ }
+
+ bool setSalt(const unsigned char* salt, size_t length) {
+ if (length != sizeof(mSalt)) {
+ return false;
+ }
+
+ memcpy(mSalt, salt, sizeof(mSalt));
+ mFlags |= OBB_SALTED;
+ return true;
+ }
+
+ bool isOverlay() {
+ return (mFlags & OBB_OVERLAY) == OBB_OVERLAY;
+ }
+
+ void setOverlay(bool overlay) {
+ if (overlay) {
+ mFlags |= OBB_OVERLAY;
+ } else {
+ mFlags &= ~OBB_OVERLAY;
+ }
+ }
+
+ static inline uint32_t get4LE(const unsigned char* buf) {
+ return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
+ }
+
+ static inline void put4LE(unsigned char* buf, uint32_t val) {
+ buf[0] = val & 0xFF;
+ buf[1] = (val >> 8) & 0xFF;
+ buf[2] = (val >> 16) & 0xFF;
+ buf[3] = (val >> 24) & 0xFF;
+ }
+
+private:
+ /* Package name this ObbFile is associated with */
+ String8 mPackageName;
+
+ /* Package version this ObbFile is associated with */
+ int32_t mVersion;
+
+ /* Flags for this OBB type. */
+ int32_t mFlags;
+
+ /* Whether the file is salted. */
+ bool mSalted;
+
+ /* The encryption salt. */
+ unsigned char mSalt[8];
+
+ const char* mFileName;
+
+ size_t mFileSize;
+
+ size_t mFooterStart;
+
+ unsigned char* mReadBuf;
+
+ bool parseObbFile(int fd);
+};
+
+}
+#endif /* OBBFILE_H_ */
diff --git a/include/androidfw/PowerManager.h b/include/androidfw/PowerManager.h
new file mode 100644
index 0000000..ba98db0
--- /dev/null
+++ b/include/androidfw/PowerManager.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#ifndef _ANDROIDFW_POWER_MANAGER_H
+#define _ANDROIDFW_POWER_MANAGER_H
+
+
+namespace android {
+
+enum {
+ USER_ACTIVITY_EVENT_OTHER = 0,
+ USER_ACTIVITY_EVENT_BUTTON = 1,
+ USER_ACTIVITY_EVENT_TOUCH = 2,
+
+ USER_ACTIVITY_EVENT_LAST = USER_ACTIVITY_EVENT_TOUCH, // Last valid event code.
+};
+
+} // namespace android
+
+#endif // _ANDROIDFW_POWER_MANAGER_H
diff --git a/include/androidfw/ResourceTypes.h b/include/androidfw/ResourceTypes.h
new file mode 100644
index 0000000..97afa59
--- /dev/null
+++ b/include/androidfw/ResourceTypes.h
@@ -0,0 +1,1605 @@
+/*
+ * Copyright (C) 2005 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.
+ */
+
+//
+// Definitions of resource data structures.
+//
+#ifndef _LIBS_UTILS_RESOURCE_TYPES_H
+#define _LIBS_UTILS_RESOURCE_TYPES_H
+
+#include <androidfw/Asset.h>
+#include <utils/ByteOrder.h>
+#include <utils/Errors.h>
+#include <utils/String16.h>
+#include <utils/Vector.h>
+
+#include <utils/threads.h>
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <android/configuration.h>
+
+namespace android {
+
+/** ********************************************************************
+ * PNG Extensions
+ *
+ * New private chunks that may be placed in PNG images.
+ *
+ *********************************************************************** */
+
+/**
+ * This chunk specifies how to split an image into segments for
+ * scaling.
+ *
+ * There are J horizontal and K vertical segments. These segments divide
+ * the image into J*K regions as follows (where J=4 and K=3):
+ *
+ * F0 S0 F1 S1
+ * +-----+----+------+-------+
+ * S2| 0 | 1 | 2 | 3 |
+ * +-----+----+------+-------+
+ * | | | | |
+ * | | | | |
+ * F2| 4 | 5 | 6 | 7 |
+ * | | | | |
+ * | | | | |
+ * +-----+----+------+-------+
+ * S3| 8 | 9 | 10 | 11 |
+ * +-----+----+------+-------+
+ *
+ * Each horizontal and vertical segment is considered to by either
+ * stretchable (marked by the Sx labels) or fixed (marked by the Fy
+ * labels), in the horizontal or vertical axis, respectively. In the
+ * above example, the first is horizontal segment (F0) is fixed, the
+ * next is stretchable and then they continue to alternate. Note that
+ * the segment list for each axis can begin or end with a stretchable
+ * or fixed segment.
+ *
+ * The relative sizes of the stretchy segments indicates the relative
+ * amount of stretchiness of the regions bordered by the segments. For
+ * example, regions 3, 7 and 11 above will take up more horizontal space
+ * than regions 1, 5 and 9 since the horizontal segment associated with
+ * the first set of regions is larger than the other set of regions. The
+ * ratios of the amount of horizontal (or vertical) space taken by any
+ * two stretchable slices is exactly the ratio of their corresponding
+ * segment lengths.
+ *
+ * xDivs and yDivs point to arrays of horizontal and vertical pixel
+ * indices. The first pair of Divs (in either array) indicate the
+ * starting and ending points of the first stretchable segment in that
+ * axis. The next pair specifies the next stretchable segment, etc. So
+ * in the above example xDiv[0] and xDiv[1] specify the horizontal
+ * coordinates for the regions labeled 1, 5 and 9. xDiv[2] and
+ * xDiv[3] specify the coordinates for regions 3, 7 and 11. Note that
+ * the leftmost slices always start at x=0 and the rightmost slices
+ * always end at the end of the image. So, for example, the regions 0,
+ * 4 and 8 (which are fixed along the X axis) start at x value 0 and
+ * go to xDiv[0] and slices 2, 6 and 10 start at xDiv[1] and end at
+ * xDiv[2].
+ *
+ * The array pointed to by the colors field lists contains hints for
+ * each of the regions. They are ordered according left-to-right and
+ * top-to-bottom as indicated above. For each segment that is a solid
+ * color the array entry will contain that color value; otherwise it
+ * will contain NO_COLOR. Segments that are completely transparent
+ * will always have the value TRANSPARENT_COLOR.
+ *
+ * The PNG chunk type is "npTc".
+ */
+struct Res_png_9patch
+{
+ Res_png_9patch() : wasDeserialized(false), xDivs(NULL),
+ yDivs(NULL), colors(NULL) { }
+
+ int8_t wasDeserialized;
+ int8_t numXDivs;
+ int8_t numYDivs;
+ int8_t numColors;
+
+ // These tell where the next section of a patch starts.
+ // For example, the first patch includes the pixels from
+ // 0 to xDivs[0]-1 and the second patch includes the pixels
+ // from xDivs[0] to xDivs[1]-1.
+ // Note: allocation/free of these pointers is left to the caller.
+ int32_t* xDivs;
+ int32_t* yDivs;
+
+ int32_t paddingLeft, paddingRight;
+ int32_t paddingTop, paddingBottom;
+
+ enum {
+ // The 9 patch segment is not a solid color.
+ NO_COLOR = 0x00000001,
+
+ // The 9 patch segment is completely transparent.
+ TRANSPARENT_COLOR = 0x00000000
+ };
+ // Note: allocation/free of this pointer is left to the caller.
+ uint32_t* colors;
+
+ // Convert data from device representation to PNG file representation.
+ void deviceToFile();
+ // Convert data from PNG file representation to device representation.
+ void fileToDevice();
+ // Serialize/Marshall the patch data into a newly malloc-ed block
+ void* serialize();
+ // Serialize/Marshall the patch data
+ void serialize(void* outData);
+ // Deserialize/Unmarshall the patch data
+ static Res_png_9patch* deserialize(const void* data);
+ // Compute the size of the serialized data structure
+ size_t serializedSize();
+};
+
+/** ********************************************************************
+ * Base Types
+ *
+ * These are standard types that are shared between multiple specific
+ * resource types.
+ *
+ *********************************************************************** */
+
+/**
+ * Header that appears at the front of every data chunk in a resource.
+ */
+struct ResChunk_header
+{
+ // Type identifier for this chunk. The meaning of this value depends
+ // on the containing chunk.
+ uint16_t type;
+
+ // Size of the chunk header (in bytes). Adding this value to
+ // the address of the chunk allows you to find its associated data
+ // (if any).
+ uint16_t headerSize;
+
+ // Total size of this chunk (in bytes). This is the chunkSize plus
+ // the size of any data associated with the chunk. Adding this value
+ // to the chunk allows you to completely skip its contents (including
+ // any child chunks). If this value is the same as chunkSize, there is
+ // no data associated with the chunk.
+ uint32_t size;
+};
+
+enum {
+ RES_NULL_TYPE = 0x0000,
+ RES_STRING_POOL_TYPE = 0x0001,
+ RES_TABLE_TYPE = 0x0002,
+ RES_XML_TYPE = 0x0003,
+
+ // Chunk types in RES_XML_TYPE
+ RES_XML_FIRST_CHUNK_TYPE = 0x0100,
+ RES_XML_START_NAMESPACE_TYPE= 0x0100,
+ RES_XML_END_NAMESPACE_TYPE = 0x0101,
+ RES_XML_START_ELEMENT_TYPE = 0x0102,
+ RES_XML_END_ELEMENT_TYPE = 0x0103,
+ RES_XML_CDATA_TYPE = 0x0104,
+ RES_XML_LAST_CHUNK_TYPE = 0x017f,
+ // This contains a uint32_t array mapping strings in the string
+ // pool back to resource identifiers. It is optional.
+ RES_XML_RESOURCE_MAP_TYPE = 0x0180,
+
+ // Chunk types in RES_TABLE_TYPE
+ RES_TABLE_PACKAGE_TYPE = 0x0200,
+ RES_TABLE_TYPE_TYPE = 0x0201,
+ RES_TABLE_TYPE_SPEC_TYPE = 0x0202
+};
+
+/**
+ * Macros for building/splitting resource identifiers.
+ */
+#define Res_VALIDID(resid) (resid != 0)
+#define Res_CHECKID(resid) ((resid&0xFFFF0000) != 0)
+#define Res_MAKEID(package, type, entry) \
+ (((package+1)<<24) | (((type+1)&0xFF)<<16) | (entry&0xFFFF))
+#define Res_GETPACKAGE(id) ((id>>24)-1)
+#define Res_GETTYPE(id) (((id>>16)&0xFF)-1)
+#define Res_GETENTRY(id) (id&0xFFFF)
+
+#define Res_INTERNALID(resid) ((resid&0xFFFF0000) != 0 && (resid&0xFF0000) == 0)
+#define Res_MAKEINTERNAL(entry) (0x01000000 | (entry&0xFFFF))
+#define Res_MAKEARRAY(entry) (0x02000000 | (entry&0xFFFF))
+
+#define Res_MAXPACKAGE 255
+
+/**
+ * Representation of a value in a resource, supplying type
+ * information.
+ */
+struct Res_value
+{
+ // Number of bytes in this structure.
+ uint16_t size;
+
+ // Always set to 0.
+ uint8_t res0;
+
+ // Type of the data value.
+ enum {
+ // Contains no data.
+ TYPE_NULL = 0x00,
+ // The 'data' holds a ResTable_ref, a reference to another resource
+ // table entry.
+ TYPE_REFERENCE = 0x01,
+ // The 'data' holds an attribute resource identifier.
+ TYPE_ATTRIBUTE = 0x02,
+ // The 'data' holds an index into the containing resource table's
+ // global value string pool.
+ TYPE_STRING = 0x03,
+ // The 'data' holds a single-precision floating point number.
+ TYPE_FLOAT = 0x04,
+ // The 'data' holds a complex number encoding a dimension value,
+ // such as "100in".
+ TYPE_DIMENSION = 0x05,
+ // The 'data' holds a complex number encoding a fraction of a
+ // container.
+ TYPE_FRACTION = 0x06,
+
+ // Beginning of integer flavors...
+ TYPE_FIRST_INT = 0x10,
+
+ // The 'data' is a raw integer value of the form n..n.
+ TYPE_INT_DEC = 0x10,
+ // The 'data' is a raw integer value of the form 0xn..n.
+ TYPE_INT_HEX = 0x11,
+ // The 'data' is either 0 or 1, for input "false" or "true" respectively.
+ TYPE_INT_BOOLEAN = 0x12,
+
+ // Beginning of color integer flavors...
+ TYPE_FIRST_COLOR_INT = 0x1c,
+
+ // The 'data' is a raw integer value of the form #aarrggbb.
+ TYPE_INT_COLOR_ARGB8 = 0x1c,
+ // The 'data' is a raw integer value of the form #rrggbb.
+ TYPE_INT_COLOR_RGB8 = 0x1d,
+ // The 'data' is a raw integer value of the form #argb.
+ TYPE_INT_COLOR_ARGB4 = 0x1e,
+ // The 'data' is a raw integer value of the form #rgb.
+ TYPE_INT_COLOR_RGB4 = 0x1f,
+
+ // ...end of integer flavors.
+ TYPE_LAST_COLOR_INT = 0x1f,
+
+ // ...end of integer flavors.
+ TYPE_LAST_INT = 0x1f
+ };
+ uint8_t dataType;
+
+ // Structure of complex data values (TYPE_UNIT and TYPE_FRACTION)
+ enum {
+ // Where the unit type information is. This gives us 16 possible
+ // types, as defined below.
+ COMPLEX_UNIT_SHIFT = 0,
+ COMPLEX_UNIT_MASK = 0xf,
+
+ // TYPE_DIMENSION: Value is raw pixels.
+ COMPLEX_UNIT_PX = 0,
+ // TYPE_DIMENSION: Value is Device Independent Pixels.
+ COMPLEX_UNIT_DIP = 1,
+ // TYPE_DIMENSION: Value is a Scaled device independent Pixels.
+ COMPLEX_UNIT_SP = 2,
+ // TYPE_DIMENSION: Value is in points.
+ COMPLEX_UNIT_PT = 3,
+ // TYPE_DIMENSION: Value is in inches.
+ COMPLEX_UNIT_IN = 4,
+ // TYPE_DIMENSION: Value is in millimeters.
+ COMPLEX_UNIT_MM = 5,
+
+ // TYPE_FRACTION: A basic fraction of the overall size.
+ COMPLEX_UNIT_FRACTION = 0,
+ // TYPE_FRACTION: A fraction of the parent size.
+ COMPLEX_UNIT_FRACTION_PARENT = 1,
+
+ // Where the radix information is, telling where the decimal place
+ // appears in the mantissa. This give us 4 possible fixed point
+ // representations as defined below.
+ COMPLEX_RADIX_SHIFT = 4,
+ COMPLEX_RADIX_MASK = 0x3,
+
+ // The mantissa is an integral number -- i.e., 0xnnnnnn.0
+ COMPLEX_RADIX_23p0 = 0,
+ // The mantissa magnitude is 16 bits -- i.e, 0xnnnn.nn
+ COMPLEX_RADIX_16p7 = 1,
+ // The mantissa magnitude is 8 bits -- i.e, 0xnn.nnnn
+ COMPLEX_RADIX_8p15 = 2,
+ // The mantissa magnitude is 0 bits -- i.e, 0x0.nnnnnn
+ COMPLEX_RADIX_0p23 = 3,
+
+ // Where the actual value is. This gives us 23 bits of
+ // precision. The top bit is the sign.
+ COMPLEX_MANTISSA_SHIFT = 8,
+ COMPLEX_MANTISSA_MASK = 0xffffff
+ };
+
+ // The data for this item, as interpreted according to dataType.
+ uint32_t data;
+
+ void copyFrom_dtoh(const Res_value& src);
+};
+
+/**
+ * This is a reference to a unique entry (a ResTable_entry structure)
+ * in a resource table. The value is structured as: 0xpptteeee,
+ * where pp is the package index, tt is the type index in that
+ * package, and eeee is the entry index in that type. The package
+ * and type values start at 1 for the first item, to help catch cases
+ * where they have not been supplied.
+ */
+struct ResTable_ref
+{
+ uint32_t ident;
+};
+
+/**
+ * Reference to a string in a string pool.
+ */
+struct ResStringPool_ref
+{
+ // Index into the string pool table (uint32_t-offset from the indices
+ // immediately after ResStringPool_header) at which to find the location
+ // of the string data in the pool.
+ uint32_t index;
+};
+
+/** ********************************************************************
+ * String Pool
+ *
+ * A set of strings that can be references by others through a
+ * ResStringPool_ref.
+ *
+ *********************************************************************** */
+
+/**
+ * Definition for a pool of strings. The data of this chunk is an
+ * array of uint32_t providing indices into the pool, relative to
+ * stringsStart. At stringsStart are all of the UTF-16 strings
+ * concatenated together; each starts with a uint16_t of the string's
+ * length and each ends with a 0x0000 terminator. If a string is >
+ * 32767 characters, the high bit of the length is set meaning to take
+ * those 15 bits as a high word and it will be followed by another
+ * uint16_t containing the low word.
+ *
+ * If styleCount is not zero, then immediately following the array of
+ * uint32_t indices into the string table is another array of indices
+ * into a style table starting at stylesStart. Each entry in the
+ * style table is an array of ResStringPool_span structures.
+ */
+struct ResStringPool_header
+{
+ struct ResChunk_header header;
+
+ // Number of strings in this pool (number of uint32_t indices that follow
+ // in the data).
+ uint32_t stringCount;
+
+ // Number of style span arrays in the pool (number of uint32_t indices
+ // follow the string indices).
+ uint32_t styleCount;
+
+ // Flags.
+ enum {
+ // If set, the string index is sorted by the string values (based
+ // on strcmp16()).
+ SORTED_FLAG = 1<<0,
+
+ // String pool is encoded in UTF-8
+ UTF8_FLAG = 1<<8
+ };
+ uint32_t flags;
+
+ // Index from header of the string data.
+ uint32_t stringsStart;
+
+ // Index from header of the style data.
+ uint32_t stylesStart;
+};
+
+/**
+ * This structure defines a span of style information associated with
+ * a string in the pool.
+ */
+struct ResStringPool_span
+{
+ enum {
+ END = 0xFFFFFFFF
+ };
+
+ // This is the name of the span -- that is, the name of the XML
+ // tag that defined it. The special value END (0xFFFFFFFF) indicates
+ // the end of an array of spans.
+ ResStringPool_ref name;
+
+ // The range of characters in the string that this span applies to.
+ uint32_t firstChar, lastChar;
+};
+
+/**
+ * Convenience class for accessing data in a ResStringPool resource.
+ */
+class ResStringPool
+{
+public:
+ ResStringPool();
+ ResStringPool(const void* data, size_t size, bool copyData=false);
+ ~ResStringPool();
+
+ status_t setTo(const void* data, size_t size, bool copyData=false);
+
+ status_t getError() const;
+
+ void uninit();
+
+ // Return string entry as UTF16; if the pool is UTF8, the string will
+ // be converted before returning.
+ inline const char16_t* stringAt(const ResStringPool_ref& ref, size_t* outLen) const {
+ return stringAt(ref.index, outLen);
+ }
+ const char16_t* stringAt(size_t idx, size_t* outLen) const;
+
+ // Note: returns null if the string pool is not UTF8.
+ const char* string8At(size_t idx, size_t* outLen) const;
+
+ // Return string whether the pool is UTF8 or UTF16. Does not allow you
+ // to distinguish null.
+ const String8 string8ObjectAt(size_t idx) const;
+
+ const ResStringPool_span* styleAt(const ResStringPool_ref& ref) const;
+ const ResStringPool_span* styleAt(size_t idx) const;
+
+ ssize_t indexOfString(const char16_t* str, size_t strLen) const;
+
+ size_t size() const;
+ size_t styleCount() const;
+ size_t bytes() const;
+
+ bool isSorted() const;
+ bool isUTF8() const;
+
+private:
+ status_t mError;
+ void* mOwnedData;
+ const ResStringPool_header* mHeader;
+ size_t mSize;
+ mutable Mutex mDecodeLock;
+ const uint32_t* mEntries;
+ const uint32_t* mEntryStyles;
+ const void* mStrings;
+ char16_t mutable** mCache;
+ uint32_t mStringPoolSize; // number of uint16_t
+ const uint32_t* mStyles;
+ uint32_t mStylePoolSize; // number of uint32_t
+};
+
+/** ********************************************************************
+ * XML Tree
+ *
+ * Binary representation of an XML document. This is designed to
+ * express everything in an XML document, in a form that is much
+ * easier to parse on the device.
+ *
+ *********************************************************************** */
+
+/**
+ * XML tree header. This appears at the front of an XML tree,
+ * describing its content. It is followed by a flat array of
+ * ResXMLTree_node structures; the hierarchy of the XML document
+ * is described by the occurrance of RES_XML_START_ELEMENT_TYPE
+ * and corresponding RES_XML_END_ELEMENT_TYPE nodes in the array.
+ */
+struct ResXMLTree_header
+{
+ struct ResChunk_header header;
+};
+
+/**
+ * Basic XML tree node. A single item in the XML document. Extended info
+ * about the node can be found after header.headerSize.
+ */
+struct ResXMLTree_node
+{
+ struct ResChunk_header header;
+
+ // Line number in original source file at which this element appeared.
+ uint32_t lineNumber;
+
+ // Optional XML comment that was associated with this element; -1 if none.
+ struct ResStringPool_ref comment;
+};
+
+/**
+ * Extended XML tree node for CDATA tags -- includes the CDATA string.
+ * Appears header.headerSize bytes after a ResXMLTree_node.
+ */
+struct ResXMLTree_cdataExt
+{
+ // The raw CDATA character data.
+ struct ResStringPool_ref data;
+
+ // The typed value of the character data if this is a CDATA node.
+ struct Res_value typedData;
+};
+
+/**
+ * Extended XML tree node for namespace start/end nodes.
+ * Appears header.headerSize bytes after a ResXMLTree_node.
+ */
+struct ResXMLTree_namespaceExt
+{
+ // The prefix of the namespace.
+ struct ResStringPool_ref prefix;
+
+ // The URI of the namespace.
+ struct ResStringPool_ref uri;
+};
+
+/**
+ * Extended XML tree node for element start/end nodes.
+ * Appears header.headerSize bytes after a ResXMLTree_node.
+ */
+struct ResXMLTree_endElementExt
+{
+ // String of the full namespace of this element.
+ struct ResStringPool_ref ns;
+
+ // String name of this node if it is an ELEMENT; the raw
+ // character data if this is a CDATA node.
+ struct ResStringPool_ref name;
+};
+
+/**
+ * Extended XML tree node for start tags -- includes attribute
+ * information.
+ * Appears header.headerSize bytes after a ResXMLTree_node.
+ */
+struct ResXMLTree_attrExt
+{
+ // String of the full namespace of this element.
+ struct ResStringPool_ref ns;
+
+ // String name of this node if it is an ELEMENT; the raw
+ // character data if this is a CDATA node.
+ struct ResStringPool_ref name;
+
+ // Byte offset from the start of this structure where the attributes start.
+ uint16_t attributeStart;
+
+ // Size of the ResXMLTree_attribute structures that follow.
+ uint16_t attributeSize;
+
+ // Number of attributes associated with an ELEMENT. These are
+ // available as an array of ResXMLTree_attribute structures
+ // immediately following this node.
+ uint16_t attributeCount;
+
+ // Index (1-based) of the "id" attribute. 0 if none.
+ uint16_t idIndex;
+
+ // Index (1-based) of the "class" attribute. 0 if none.
+ uint16_t classIndex;
+
+ // Index (1-based) of the "style" attribute. 0 if none.
+ uint16_t styleIndex;
+};
+
+struct ResXMLTree_attribute
+{
+ // Namespace of this attribute.
+ struct ResStringPool_ref ns;
+
+ // Name of this attribute.
+ struct ResStringPool_ref name;
+
+ // The original raw string value of this attribute.
+ struct ResStringPool_ref rawValue;
+
+ // Processesd typed value of this attribute.
+ struct Res_value typedValue;
+};
+
+class ResXMLTree;
+
+class ResXMLParser
+{
+public:
+ ResXMLParser(const ResXMLTree& tree);
+
+ enum event_code_t {
+ BAD_DOCUMENT = -1,
+ START_DOCUMENT = 0,
+ END_DOCUMENT = 1,
+
+ FIRST_CHUNK_CODE = RES_XML_FIRST_CHUNK_TYPE,
+
+ START_NAMESPACE = RES_XML_START_NAMESPACE_TYPE,
+ END_NAMESPACE = RES_XML_END_NAMESPACE_TYPE,
+ START_TAG = RES_XML_START_ELEMENT_TYPE,
+ END_TAG = RES_XML_END_ELEMENT_TYPE,
+ TEXT = RES_XML_CDATA_TYPE
+ };
+
+ struct ResXMLPosition
+ {
+ event_code_t eventCode;
+ const ResXMLTree_node* curNode;
+ const void* curExt;
+ };
+
+ void restart();
+
+ const ResStringPool& getStrings() const;
+
+ event_code_t getEventType() const;
+ // Note, unlike XmlPullParser, the first call to next() will return
+ // START_TAG of the first element.
+ event_code_t next();
+
+ // These are available for all nodes:
+ int32_t getCommentID() const;
+ const uint16_t* getComment(size_t* outLen) const;
+ uint32_t getLineNumber() const;
+
+ // This is available for TEXT:
+ int32_t getTextID() const;
+ const uint16_t* getText(size_t* outLen) const;
+ ssize_t getTextValue(Res_value* outValue) const;
+
+ // These are available for START_NAMESPACE and END_NAMESPACE:
+ int32_t getNamespacePrefixID() const;
+ const uint16_t* getNamespacePrefix(size_t* outLen) const;
+ int32_t getNamespaceUriID() const;
+ const uint16_t* getNamespaceUri(size_t* outLen) const;
+
+ // These are available for START_TAG and END_TAG:
+ int32_t getElementNamespaceID() const;
+ const uint16_t* getElementNamespace(size_t* outLen) const;
+ int32_t getElementNameID() const;
+ const uint16_t* getElementName(size_t* outLen) const;
+
+ // Remaining methods are for retrieving information about attributes
+ // associated with a START_TAG:
+
+ size_t getAttributeCount() const;
+
+ // Returns -1 if no namespace, -2 if idx out of range.
+ int32_t getAttributeNamespaceID(size_t idx) const;
+ const uint16_t* getAttributeNamespace(size_t idx, size_t* outLen) const;
+
+ int32_t getAttributeNameID(size_t idx) const;
+ const uint16_t* getAttributeName(size_t idx, size_t* outLen) const;
+ uint32_t getAttributeNameResID(size_t idx) const;
+
+ // These will work only if the underlying string pool is UTF-8.
+ const char* getAttributeNamespace8(size_t idx, size_t* outLen) const;
+ const char* getAttributeName8(size_t idx, size_t* outLen) const;
+
+ int32_t getAttributeValueStringID(size_t idx) const;
+ const uint16_t* getAttributeStringValue(size_t idx, size_t* outLen) const;
+
+ int32_t getAttributeDataType(size_t idx) const;
+ int32_t getAttributeData(size_t idx) const;
+ ssize_t getAttributeValue(size_t idx, Res_value* outValue) const;
+
+ ssize_t indexOfAttribute(const char* ns, const char* attr) const;
+ ssize_t indexOfAttribute(const char16_t* ns, size_t nsLen,
+ const char16_t* attr, size_t attrLen) const;
+
+ ssize_t indexOfID() const;
+ ssize_t indexOfClass() const;
+ ssize_t indexOfStyle() const;
+
+ void getPosition(ResXMLPosition* pos) const;
+ void setPosition(const ResXMLPosition& pos);
+
+private:
+ friend class ResXMLTree;
+
+ event_code_t nextNode();
+
+ const ResXMLTree& mTree;
+ event_code_t mEventCode;
+ const ResXMLTree_node* mCurNode;
+ const void* mCurExt;
+};
+
+/**
+ * Convenience class for accessing data in a ResXMLTree resource.
+ */
+class ResXMLTree : public ResXMLParser
+{
+public:
+ ResXMLTree();
+ ResXMLTree(const void* data, size_t size, bool copyData=false);
+ ~ResXMLTree();
+
+ status_t setTo(const void* data, size_t size, bool copyData=false);
+
+ status_t getError() const;
+
+ void uninit();
+
+private:
+ friend class ResXMLParser;
+
+ status_t validateNode(const ResXMLTree_node* node) const;
+
+ status_t mError;
+ void* mOwnedData;
+ const ResXMLTree_header* mHeader;
+ size_t mSize;
+ const uint8_t* mDataEnd;
+ ResStringPool mStrings;
+ const uint32_t* mResIds;
+ size_t mNumResIds;
+ const ResXMLTree_node* mRootNode;
+ const void* mRootExt;
+ event_code_t mRootCode;
+};
+
+/** ********************************************************************
+ * RESOURCE TABLE
+ *
+ *********************************************************************** */
+
+/**
+ * Header for a resource table. Its data contains a series of
+ * additional chunks:
+ * * A ResStringPool_header containing all table values. This string pool
+ * contains all of the string values in the entire resource table (not
+ * the names of entries or type identifiers however).
+ * * One or more ResTable_package chunks.
+ *
+ * Specific entries within a resource table can be uniquely identified
+ * with a single integer as defined by the ResTable_ref structure.
+ */
+struct ResTable_header
+{
+ struct ResChunk_header header;
+
+ // The number of ResTable_package structures.
+ uint32_t packageCount;
+};
+
+/**
+ * A collection of resource data types within a package. Followed by
+ * one or more ResTable_type and ResTable_typeSpec structures containing the
+ * entry values for each resource type.
+ */
+struct ResTable_package
+{
+ struct ResChunk_header header;
+
+ // If this is a base package, its ID. Package IDs start
+ // at 1 (corresponding to the value of the package bits in a
+ // resource identifier). 0 means this is not a base package.
+ uint32_t id;
+
+ // Actual name of this package, \0-terminated.
+ char16_t name[128];
+
+ // Offset to a ResStringPool_header defining the resource
+ // type symbol table. If zero, this package is inheriting from
+ // another base package (overriding specific values in it).
+ uint32_t typeStrings;
+
+ // Last index into typeStrings that is for public use by others.
+ uint32_t lastPublicType;
+
+ // Offset to a ResStringPool_header defining the resource
+ // key symbol table. If zero, this package is inheriting from
+ // another base package (overriding specific values in it).
+ uint32_t keyStrings;
+
+ // Last index into keyStrings that is for public use by others.
+ uint32_t lastPublicKey;
+};
+
+/**
+ * Describes a particular resource configuration.
+ */
+struct ResTable_config
+{
+ // Number of bytes in this structure.
+ uint32_t size;
+
+ union {
+ struct {
+ // Mobile country code (from SIM). 0 means "any".
+ uint16_t mcc;
+ // Mobile network code (from SIM). 0 means "any".
+ uint16_t mnc;
+ };
+ uint32_t imsi;
+ };
+
+ union {
+ struct {
+ // \0\0 means "any". Otherwise, en, fr, etc.
+ char language[2];
+
+ // \0\0 means "any". Otherwise, US, CA, etc.
+ char country[2];
+ };
+ uint32_t locale;
+ };
+
+ enum {
+ ORIENTATION_ANY = ACONFIGURATION_ORIENTATION_ANY,
+ ORIENTATION_PORT = ACONFIGURATION_ORIENTATION_PORT,
+ ORIENTATION_LAND = ACONFIGURATION_ORIENTATION_LAND,
+ ORIENTATION_SQUARE = ACONFIGURATION_ORIENTATION_SQUARE,
+ };
+
+ enum {
+ TOUCHSCREEN_ANY = ACONFIGURATION_TOUCHSCREEN_ANY,
+ TOUCHSCREEN_NOTOUCH = ACONFIGURATION_TOUCHSCREEN_NOTOUCH,
+ TOUCHSCREEN_STYLUS = ACONFIGURATION_TOUCHSCREEN_STYLUS,
+ TOUCHSCREEN_FINGER = ACONFIGURATION_TOUCHSCREEN_FINGER,
+ };
+
+ enum {
+ DENSITY_DEFAULT = ACONFIGURATION_DENSITY_DEFAULT,
+ DENSITY_LOW = ACONFIGURATION_DENSITY_LOW,
+ DENSITY_MEDIUM = ACONFIGURATION_DENSITY_MEDIUM,
+ DENSITY_TV = ACONFIGURATION_DENSITY_TV,
+ DENSITY_HIGH = ACONFIGURATION_DENSITY_HIGH,
+ DENSITY_XHIGH = ACONFIGURATION_DENSITY_XHIGH,
+ DENSITY_XXHIGH = ACONFIGURATION_DENSITY_XXHIGH,
+ DENSITY_XXXHIGH = ACONFIGURATION_DENSITY_XXXHIGH,
+ DENSITY_NONE = ACONFIGURATION_DENSITY_NONE
+ };
+
+ union {
+ struct {
+ uint8_t orientation;
+ uint8_t touchscreen;
+ uint16_t density;
+ };
+ uint32_t screenType;
+ };
+
+ enum {
+ KEYBOARD_ANY = ACONFIGURATION_KEYBOARD_ANY,
+ KEYBOARD_NOKEYS = ACONFIGURATION_KEYBOARD_NOKEYS,
+ KEYBOARD_QWERTY = ACONFIGURATION_KEYBOARD_QWERTY,
+ KEYBOARD_12KEY = ACONFIGURATION_KEYBOARD_12KEY,
+ };
+
+ enum {
+ NAVIGATION_ANY = ACONFIGURATION_NAVIGATION_ANY,
+ NAVIGATION_NONAV = ACONFIGURATION_NAVIGATION_NONAV,
+ NAVIGATION_DPAD = ACONFIGURATION_NAVIGATION_DPAD,
+ NAVIGATION_TRACKBALL = ACONFIGURATION_NAVIGATION_TRACKBALL,
+ NAVIGATION_WHEEL = ACONFIGURATION_NAVIGATION_WHEEL,
+ };
+
+ enum {
+ MASK_KEYSHIDDEN = 0x0003,
+ KEYSHIDDEN_ANY = ACONFIGURATION_KEYSHIDDEN_ANY,
+ KEYSHIDDEN_NO = ACONFIGURATION_KEYSHIDDEN_NO,
+ KEYSHIDDEN_YES = ACONFIGURATION_KEYSHIDDEN_YES,
+ KEYSHIDDEN_SOFT = ACONFIGURATION_KEYSHIDDEN_SOFT,
+ };
+
+ enum {
+ MASK_NAVHIDDEN = 0x000c,
+ SHIFT_NAVHIDDEN = 2,
+ NAVHIDDEN_ANY = ACONFIGURATION_NAVHIDDEN_ANY << SHIFT_NAVHIDDEN,
+ NAVHIDDEN_NO = ACONFIGURATION_NAVHIDDEN_NO << SHIFT_NAVHIDDEN,
+ NAVHIDDEN_YES = ACONFIGURATION_NAVHIDDEN_YES << SHIFT_NAVHIDDEN,
+ };
+
+ union {
+ struct {
+ uint8_t keyboard;
+ uint8_t navigation;
+ uint8_t inputFlags;
+ uint8_t inputPad0;
+ };
+ uint32_t input;
+ };
+
+ enum {
+ SCREENWIDTH_ANY = 0
+ };
+
+ enum {
+ SCREENHEIGHT_ANY = 0
+ };
+
+ union {
+ struct {
+ uint16_t screenWidth;
+ uint16_t screenHeight;
+ };
+ uint32_t screenSize;
+ };
+
+ enum {
+ SDKVERSION_ANY = 0
+ };
+
+ enum {
+ MINORVERSION_ANY = 0
+ };
+
+ union {
+ struct {
+ uint16_t sdkVersion;
+ // For now minorVersion must always be 0!!! Its meaning
+ // is currently undefined.
+ uint16_t minorVersion;
+ };
+ uint32_t version;
+ };
+
+ enum {
+ // screenLayout bits for screen size class.
+ MASK_SCREENSIZE = 0x0f,
+ SCREENSIZE_ANY = ACONFIGURATION_SCREENSIZE_ANY,
+ SCREENSIZE_SMALL = ACONFIGURATION_SCREENSIZE_SMALL,
+ SCREENSIZE_NORMAL = ACONFIGURATION_SCREENSIZE_NORMAL,
+ SCREENSIZE_LARGE = ACONFIGURATION_SCREENSIZE_LARGE,
+ SCREENSIZE_XLARGE = ACONFIGURATION_SCREENSIZE_XLARGE,
+
+ // screenLayout bits for wide/long screen variation.
+ MASK_SCREENLONG = 0x30,
+ SHIFT_SCREENLONG = 4,
+ SCREENLONG_ANY = ACONFIGURATION_SCREENLONG_ANY << SHIFT_SCREENLONG,
+ SCREENLONG_NO = ACONFIGURATION_SCREENLONG_NO << SHIFT_SCREENLONG,
+ SCREENLONG_YES = ACONFIGURATION_SCREENLONG_YES << SHIFT_SCREENLONG,
+
+ // screenLayout bits for layout direction.
+ MASK_LAYOUTDIR = 0xC0,
+ SHIFT_LAYOUTDIR = 6,
+ LAYOUTDIR_ANY = ACONFIGURATION_LAYOUTDIR_ANY << SHIFT_LAYOUTDIR,
+ LAYOUTDIR_LTR = ACONFIGURATION_LAYOUTDIR_LTR << SHIFT_LAYOUTDIR,
+ LAYOUTDIR_RTL = ACONFIGURATION_LAYOUTDIR_RTL << SHIFT_LAYOUTDIR,
+ };
+
+ enum {
+ // uiMode bits for the mode type.
+ MASK_UI_MODE_TYPE = 0x0f,
+ UI_MODE_TYPE_ANY = ACONFIGURATION_UI_MODE_TYPE_ANY,
+ UI_MODE_TYPE_NORMAL = ACONFIGURATION_UI_MODE_TYPE_NORMAL,
+ UI_MODE_TYPE_DESK = ACONFIGURATION_UI_MODE_TYPE_DESK,
+ UI_MODE_TYPE_CAR = ACONFIGURATION_UI_MODE_TYPE_CAR,
+ UI_MODE_TYPE_TELEVISION = ACONFIGURATION_UI_MODE_TYPE_TELEVISION,
+ UI_MODE_TYPE_APPLIANCE = ACONFIGURATION_UI_MODE_TYPE_APPLIANCE,
+
+ // uiMode bits for the night switch.
+ MASK_UI_MODE_NIGHT = 0x30,
+ SHIFT_UI_MODE_NIGHT = 4,
+ UI_MODE_NIGHT_ANY = ACONFIGURATION_UI_MODE_NIGHT_ANY << SHIFT_UI_MODE_NIGHT,
+ UI_MODE_NIGHT_NO = ACONFIGURATION_UI_MODE_NIGHT_NO << SHIFT_UI_MODE_NIGHT,
+ UI_MODE_NIGHT_YES = ACONFIGURATION_UI_MODE_NIGHT_YES << SHIFT_UI_MODE_NIGHT,
+ };
+
+ union {
+ struct {
+ uint8_t screenLayout;
+ uint8_t uiMode;
+ uint16_t smallestScreenWidthDp;
+ };
+ uint32_t screenConfig;
+ };
+
+ union {
+ struct {
+ uint16_t screenWidthDp;
+ uint16_t screenHeightDp;
+ };
+ uint32_t screenSizeDp;
+ };
+
+ void copyFromDeviceNoSwap(const ResTable_config& o);
+
+ void copyFromDtoH(const ResTable_config& o);
+
+ void swapHtoD();
+
+ int compare(const ResTable_config& o) const;
+ int compareLogical(const ResTable_config& o) const;
+
+ // Flags indicating a set of config values. These flag constants must
+ // match the corresponding ones in android.content.pm.ActivityInfo and
+ // attrs_manifest.xml.
+ enum {
+ CONFIG_MCC = ACONFIGURATION_MCC,
+ CONFIG_MNC = ACONFIGURATION_MCC,
+ CONFIG_LOCALE = ACONFIGURATION_LOCALE,
+ CONFIG_TOUCHSCREEN = ACONFIGURATION_TOUCHSCREEN,
+ CONFIG_KEYBOARD = ACONFIGURATION_KEYBOARD,
+ CONFIG_KEYBOARD_HIDDEN = ACONFIGURATION_KEYBOARD_HIDDEN,
+ CONFIG_NAVIGATION = ACONFIGURATION_NAVIGATION,
+ CONFIG_ORIENTATION = ACONFIGURATION_ORIENTATION,
+ CONFIG_DENSITY = ACONFIGURATION_DENSITY,
+ CONFIG_SCREEN_SIZE = ACONFIGURATION_SCREEN_SIZE,
+ CONFIG_SMALLEST_SCREEN_SIZE = ACONFIGURATION_SMALLEST_SCREEN_SIZE,
+ CONFIG_VERSION = ACONFIGURATION_VERSION,
+ CONFIG_SCREEN_LAYOUT = ACONFIGURATION_SCREEN_LAYOUT,
+ CONFIG_UI_MODE = ACONFIGURATION_UI_MODE,
+ CONFIG_LAYOUTDIR = ACONFIGURATION_LAYOUTDIR,
+ };
+
+ // Compare two configuration, returning CONFIG_* flags set for each value
+ // that is different.
+ int diff(const ResTable_config& o) const;
+
+ // Return true if 'this' is more specific than 'o'.
+ bool isMoreSpecificThan(const ResTable_config& o) const;
+
+ // Return true if 'this' is a better match than 'o' for the 'requested'
+ // configuration. This assumes that match() has already been used to
+ // remove any configurations that don't match the requested configuration
+ // at all; if they are not first filtered, non-matching results can be
+ // considered better than matching ones.
+ // The general rule per attribute: if the request cares about an attribute
+ // (it normally does), if the two (this and o) are equal it's a tie. If
+ // they are not equal then one must be generic because only generic and
+ // '==requested' will pass the match() call. So if this is not generic,
+ // it wins. If this IS generic, o wins (return false).
+ bool isBetterThan(const ResTable_config& o, const ResTable_config* requested) const;
+
+ // Return true if 'this' can be considered a match for the parameters in
+ // 'settings'.
+ // Note this is asymetric. A default piece of data will match every request
+ // but a request for the default should not match odd specifics
+ // (ie, request with no mcc should not match a particular mcc's data)
+ // settings is the requested settings
+ bool match(const ResTable_config& settings) const;
+
+ void getLocale(char str[6]) const;
+
+ String8 toString() const;
+};
+
+/**
+ * A specification of the resources defined by a particular type.
+ *
+ * There should be one of these chunks for each resource type.
+ *
+ * This structure is followed by an array of integers providing the set of
+ * configuration change flags (ResTable_config::CONFIG_*) that have multiple
+ * resources for that configuration. In addition, the high bit is set if that
+ * resource has been made public.
+ */
+struct ResTable_typeSpec
+{
+ struct ResChunk_header header;
+
+ // The type identifier this chunk is holding. Type IDs start
+ // at 1 (corresponding to the value of the type bits in a
+ // resource identifier). 0 is invalid.
+ uint8_t id;
+
+ // Must be 0.
+ uint8_t res0;
+ // Must be 0.
+ uint16_t res1;
+
+ // Number of uint32_t entry configuration masks that follow.
+ uint32_t entryCount;
+
+ enum {
+ // Additional flag indicating an entry is public.
+ SPEC_PUBLIC = 0x40000000
+ };
+};
+
+/**
+ * A collection of resource entries for a particular resource data
+ * type. Followed by an array of uint32_t defining the resource
+ * values, corresponding to the array of type strings in the
+ * ResTable_package::typeStrings string block. Each of these hold an
+ * index from entriesStart; a value of NO_ENTRY means that entry is
+ * not defined.
+ *
+ * There may be multiple of these chunks for a particular resource type,
+ * supply different configuration variations for the resource values of
+ * that type.
+ *
+ * It would be nice to have an additional ordered index of entries, so
+ * we can do a binary search if trying to find a resource by string name.
+ */
+struct ResTable_type
+{
+ struct ResChunk_header header;
+
+ enum {
+ NO_ENTRY = 0xFFFFFFFF
+ };
+
+ // The type identifier this chunk is holding. Type IDs start
+ // at 1 (corresponding to the value of the type bits in a
+ // resource identifier). 0 is invalid.
+ uint8_t id;
+
+ // Must be 0.
+ uint8_t res0;
+ // Must be 0.
+ uint16_t res1;
+
+ // Number of uint32_t entry indices that follow.
+ uint32_t entryCount;
+
+ // Offset from header where ResTable_entry data starts.
+ uint32_t entriesStart;
+
+ // Configuration this collection of entries is designed for.
+ ResTable_config config;
+};
+
+/**
+ * This is the beginning of information about an entry in the resource
+ * table. It holds the reference to the name of this entry, and is
+ * immediately followed by one of:
+ * * A Res_value structure, if FLAG_COMPLEX is -not- set.
+ * * An array of ResTable_map structures, if FLAG_COMPLEX is set.
+ * These supply a set of name/value mappings of data.
+ */
+struct ResTable_entry
+{
+ // Number of bytes in this structure.
+ uint16_t size;
+
+ enum {
+ // If set, this is a complex entry, holding a set of name/value
+ // mappings. It is followed by an array of ResTable_map structures.
+ FLAG_COMPLEX = 0x0001,
+ // If set, this resource has been declared public, so libraries
+ // are allowed to reference it.
+ FLAG_PUBLIC = 0x0002
+ };
+ uint16_t flags;
+
+ // Reference into ResTable_package::keyStrings identifying this entry.
+ struct ResStringPool_ref key;
+};
+
+/**
+ * Extended form of a ResTable_entry for map entries, defining a parent map
+ * resource from which to inherit values.
+ */
+struct ResTable_map_entry : public ResTable_entry
+{
+ // Resource identifier of the parent mapping, or 0 if there is none.
+ ResTable_ref parent;
+ // Number of name/value pairs that follow for FLAG_COMPLEX.
+ uint32_t count;
+};
+
+/**
+ * A single name/value mapping that is part of a complex resource
+ * entry.
+ */
+struct ResTable_map
+{
+ // The resource identifier defining this mapping's name. For attribute
+ // resources, 'name' can be one of the following special resource types
+ // to supply meta-data about the attribute; for all other resource types
+ // it must be an attribute resource.
+ ResTable_ref name;
+
+ // Special values for 'name' when defining attribute resources.
+ enum {
+ // This entry holds the attribute's type code.
+ ATTR_TYPE = Res_MAKEINTERNAL(0),
+
+ // For integral attributes, this is the minimum value it can hold.
+ ATTR_MIN = Res_MAKEINTERNAL(1),
+
+ // For integral attributes, this is the maximum value it can hold.
+ ATTR_MAX = Res_MAKEINTERNAL(2),
+
+ // Localization of this resource is can be encouraged or required with
+ // an aapt flag if this is set
+ ATTR_L10N = Res_MAKEINTERNAL(3),
+
+ // for plural support, see android.content.res.PluralRules#attrForQuantity(int)
+ ATTR_OTHER = Res_MAKEINTERNAL(4),
+ ATTR_ZERO = Res_MAKEINTERNAL(5),
+ ATTR_ONE = Res_MAKEINTERNAL(6),
+ ATTR_TWO = Res_MAKEINTERNAL(7),
+ ATTR_FEW = Res_MAKEINTERNAL(8),
+ ATTR_MANY = Res_MAKEINTERNAL(9)
+
+ };
+
+ // Bit mask of allowed types, for use with ATTR_TYPE.
+ enum {
+ // No type has been defined for this attribute, use generic
+ // type handling. The low 16 bits are for types that can be
+ // handled generically; the upper 16 require additional information
+ // in the bag so can not be handled generically for TYPE_ANY.
+ TYPE_ANY = 0x0000FFFF,
+
+ // Attribute holds a references to another resource.
+ TYPE_REFERENCE = 1<<0,
+
+ // Attribute holds a generic string.
+ TYPE_STRING = 1<<1,
+
+ // Attribute holds an integer value. ATTR_MIN and ATTR_MIN can
+ // optionally specify a constrained range of possible integer values.
+ TYPE_INTEGER = 1<<2,
+
+ // Attribute holds a boolean integer.
+ TYPE_BOOLEAN = 1<<3,
+
+ // Attribute holds a color value.
+ TYPE_COLOR = 1<<4,
+
+ // Attribute holds a floating point value.
+ TYPE_FLOAT = 1<<5,
+
+ // Attribute holds a dimension value, such as "20px".
+ TYPE_DIMENSION = 1<<6,
+
+ // Attribute holds a fraction value, such as "20%".
+ TYPE_FRACTION = 1<<7,
+
+ // Attribute holds an enumeration. The enumeration values are
+ // supplied as additional entries in the map.
+ TYPE_ENUM = 1<<16,
+
+ // Attribute holds a bitmaks of flags. The flag bit values are
+ // supplied as additional entries in the map.
+ TYPE_FLAGS = 1<<17
+ };
+
+ // Enum of localization modes, for use with ATTR_L10N.
+ enum {
+ L10N_NOT_REQUIRED = 0,
+ L10N_SUGGESTED = 1
+ };
+
+ // This mapping's value.
+ Res_value value;
+};
+
+/**
+ * Convenience class for accessing data in a ResTable resource.
+ */
+class ResTable
+{
+public:
+ ResTable();
+ ResTable(const void* data, size_t size, void* cookie,
+ bool copyData=false);
+ ~ResTable();
+
+ status_t add(const void* data, size_t size, void* cookie,
+ bool copyData=false, const void* idmap = NULL);
+ status_t add(Asset* asset, void* cookie,
+ bool copyData=false, const void* idmap = NULL);
+ status_t add(ResTable* src);
+
+ status_t getError() const;
+
+ void uninit();
+
+ struct resource_name
+ {
+ const char16_t* package;
+ size_t packageLen;
+ const char16_t* type;
+ const char* type8;
+ size_t typeLen;
+ const char16_t* name;
+ const char* name8;
+ size_t nameLen;
+ };
+
+ bool getResourceName(uint32_t resID, bool allowUtf8, resource_name* outName) const;
+
+ /**
+ * Retrieve the value of a resource. If the resource is found, returns a
+ * value >= 0 indicating the table it is in (for use with
+ * getTableStringBlock() and getTableCookie()) and fills in 'outValue'. If
+ * not found, returns a negative error code.
+ *
+ * Note that this function does not do reference traversal. If you want
+ * to follow references to other resources to get the "real" value to
+ * use, you need to call resolveReference() after this function.
+ *
+ * @param resID The desired resoruce identifier.
+ * @param outValue Filled in with the resource data that was found.
+ *
+ * @return ssize_t Either a >= 0 table index or a negative error code.
+ */
+ ssize_t getResource(uint32_t resID, Res_value* outValue, bool mayBeBag = false,
+ uint16_t density = 0,
+ uint32_t* outSpecFlags = NULL,
+ ResTable_config* outConfig = NULL) const;
+
+ inline ssize_t getResource(const ResTable_ref& res, Res_value* outValue,
+ uint32_t* outSpecFlags=NULL) const {
+ return getResource(res.ident, outValue, false, 0, outSpecFlags, NULL);
+ }
+
+ ssize_t resolveReference(Res_value* inOutValue,
+ ssize_t blockIndex,
+ uint32_t* outLastRef = NULL,
+ uint32_t* inoutTypeSpecFlags = NULL,
+ ResTable_config* outConfig = NULL) const;
+
+ enum {
+ TMP_BUFFER_SIZE = 16
+ };
+ const char16_t* valueToString(const Res_value* value, size_t stringBlock,
+ char16_t tmpBuffer[TMP_BUFFER_SIZE],
+ size_t* outLen);
+
+ struct bag_entry {
+ ssize_t stringBlock;
+ ResTable_map map;
+ };
+
+ /**
+ * Retrieve the bag of a resource. If the resoruce is found, returns the
+ * number of bags it contains and 'outBag' points to an array of their
+ * values. If not found, a negative error code is returned.
+ *
+ * Note that this function -does- do reference traversal of the bag data.
+ *
+ * @param resID The desired resource identifier.
+ * @param outBag Filled inm with a pointer to the bag mappings.
+ *
+ * @return ssize_t Either a >= 0 bag count of negative error code.
+ */
+ ssize_t lockBag(uint32_t resID, const bag_entry** outBag) const;
+
+ void unlockBag(const bag_entry* bag) const;
+
+ void lock() const;
+
+ ssize_t getBagLocked(uint32_t resID, const bag_entry** outBag,
+ uint32_t* outTypeSpecFlags=NULL) const;
+
+ void unlock() const;
+
+ class Theme {
+ public:
+ Theme(const ResTable& table);
+ ~Theme();
+
+ inline const ResTable& getResTable() const { return mTable; }
+
+ status_t applyStyle(uint32_t resID, bool force=false);
+ status_t setTo(const Theme& other);
+
+ /**
+ * Retrieve a value in the theme. If the theme defines this
+ * value, returns a value >= 0 indicating the table it is in
+ * (for use with getTableStringBlock() and getTableCookie) and
+ * fills in 'outValue'. If not found, returns a negative error
+ * code.
+ *
+ * Note that this function does not do reference traversal. If you want
+ * to follow references to other resources to get the "real" value to
+ * use, you need to call resolveReference() after this function.
+ *
+ * @param resID A resource identifier naming the desired theme
+ * attribute.
+ * @param outValue Filled in with the theme value that was
+ * found.
+ *
+ * @return ssize_t Either a >= 0 table index or a negative error code.
+ */
+ ssize_t getAttribute(uint32_t resID, Res_value* outValue,
+ uint32_t* outTypeSpecFlags = NULL) const;
+
+ /**
+ * This is like ResTable::resolveReference(), but also takes
+ * care of resolving attribute references to the theme.
+ */
+ ssize_t resolveAttributeReference(Res_value* inOutValue,
+ ssize_t blockIndex, uint32_t* outLastRef = NULL,
+ uint32_t* inoutTypeSpecFlags = NULL,
+ ResTable_config* inoutConfig = NULL) const;
+
+ void dumpToLog() const;
+
+ private:
+ Theme(const Theme&);
+ Theme& operator=(const Theme&);
+
+ struct theme_entry {
+ ssize_t stringBlock;
+ uint32_t typeSpecFlags;
+ Res_value value;
+ };
+ struct type_info {
+ size_t numEntries;
+ theme_entry* entries;
+ };
+ struct package_info {
+ size_t numTypes;
+ type_info types[];
+ };
+
+ void free_package(package_info* pi);
+ package_info* copy_package(package_info* pi);
+
+ const ResTable& mTable;
+ package_info* mPackages[Res_MAXPACKAGE];
+ };
+
+ void setParameters(const ResTable_config* params);
+ void getParameters(ResTable_config* params) const;
+
+ // Retrieve an identifier (which can be passed to getResource)
+ // for a given resource name. The 'name' can be fully qualified
+ // (<package>:<type>.<basename>) or the package or type components
+ // can be dropped if default values are supplied here.
+ //
+ // Returns 0 if no such resource was found, else a valid resource ID.
+ uint32_t identifierForName(const char16_t* name, size_t nameLen,
+ const char16_t* type = 0, size_t typeLen = 0,
+ const char16_t* defPackage = 0,
+ size_t defPackageLen = 0,
+ uint32_t* outTypeSpecFlags = NULL) const;
+
+ static bool expandResourceRef(const uint16_t* refStr, size_t refLen,
+ String16* outPackage,
+ String16* outType,
+ String16* outName,
+ const String16* defType = NULL,
+ const String16* defPackage = NULL,
+ const char** outErrorMsg = NULL,
+ bool* outPublicOnly = NULL);
+
+ static bool stringToInt(const char16_t* s, size_t len, Res_value* outValue);
+ static bool stringToFloat(const char16_t* s, size_t len, Res_value* outValue);
+
+ // Used with stringToValue.
+ class Accessor
+ {
+ public:
+ inline virtual ~Accessor() { }
+
+ virtual uint32_t getCustomResource(const String16& package,
+ const String16& type,
+ const String16& name) const = 0;
+ virtual uint32_t getCustomResourceWithCreation(const String16& package,
+ const String16& type,
+ const String16& name,
+ const bool createIfNeeded = false) = 0;
+ virtual uint32_t getRemappedPackage(uint32_t origPackage) const = 0;
+ virtual bool getAttributeType(uint32_t attrID, uint32_t* outType) = 0;
+ virtual bool getAttributeMin(uint32_t attrID, uint32_t* outMin) = 0;
+ virtual bool getAttributeMax(uint32_t attrID, uint32_t* outMax) = 0;
+ virtual bool getAttributeEnum(uint32_t attrID,
+ const char16_t* name, size_t nameLen,
+ Res_value* outValue) = 0;
+ virtual bool getAttributeFlags(uint32_t attrID,
+ const char16_t* name, size_t nameLen,
+ Res_value* outValue) = 0;
+ virtual uint32_t getAttributeL10N(uint32_t attrID) = 0;
+ virtual bool getLocalizationSetting() = 0;
+ virtual void reportError(void* accessorCookie, const char* fmt, ...) = 0;
+ };
+
+ // Convert a string to a resource value. Handles standard "@res",
+ // "#color", "123", and "0x1bd" types; performs escaping of strings.
+ // The resulting value is placed in 'outValue'; if it is a string type,
+ // 'outString' receives the string. If 'attrID' is supplied, the value is
+ // type checked against this attribute and it is used to perform enum
+ // evaluation. If 'acccessor' is supplied, it will be used to attempt to
+ // resolve resources that do not exist in this ResTable. If 'attrType' is
+ // supplied, the value will be type checked for this format if 'attrID'
+ // is not supplied or found.
+ bool stringToValue(Res_value* outValue, String16* outString,
+ const char16_t* s, size_t len,
+ bool preserveSpaces, bool coerceType,
+ uint32_t attrID = 0,
+ const String16* defType = NULL,
+ const String16* defPackage = NULL,
+ Accessor* accessor = NULL,
+ void* accessorCookie = NULL,
+ uint32_t attrType = ResTable_map::TYPE_ANY,
+ bool enforcePrivate = true) const;
+
+ // Perform processing of escapes and quotes in a string.
+ static bool collectString(String16* outString,
+ const char16_t* s, size_t len,
+ bool preserveSpaces,
+ const char** outErrorMsg = NULL,
+ bool append = false);
+
+ size_t getBasePackageCount() const;
+ const char16_t* getBasePackageName(size_t idx) const;
+ uint32_t getBasePackageId(size_t idx) const;
+
+ // Return the number of resource tables that the object contains.
+ size_t getTableCount() const;
+ // Return the values string pool for the resource table at the given
+ // index. This string pool contains all of the strings for values
+ // contained in the resource table -- that is the item values themselves,
+ // but not the names their entries or types.
+ const ResStringPool* getTableStringBlock(size_t index) const;
+ // Return unique cookie identifier for the given resource table.
+ void* getTableCookie(size_t index) const;
+
+ // Return the configurations (ResTable_config) that we know about
+ void getConfigurations(Vector<ResTable_config>* configs) const;
+
+ void getLocales(Vector<String8>* locales) const;
+
+ // Generate an idmap.
+ //
+ // Return value: on success: NO_ERROR; caller is responsible for free-ing
+ // outData (using free(3)). On failure, any status_t value other than
+ // NO_ERROR; the caller should not free outData.
+ status_t createIdmap(const ResTable& overlay, uint32_t originalCrc, uint32_t overlayCrc,
+ void** outData, size_t* outSize) const;
+
+ enum {
+ IDMAP_HEADER_SIZE_BYTES = 3 * sizeof(uint32_t),
+ };
+ // Retrieve idmap meta-data.
+ //
+ // This function only requires the idmap header (the first
+ // IDMAP_HEADER_SIZE_BYTES) bytes of an idmap file.
+ static bool getIdmapInfo(const void* idmap, size_t size,
+ uint32_t* pOriginalCrc, uint32_t* pOverlayCrc);
+
+ void print(bool inclValues) const;
+ static String8 normalizeForOutput(const char* input);
+
+private:
+ struct Header;
+ struct Type;
+ struct Package;
+ struct PackageGroup;
+ struct bag_set;
+
+ status_t add(const void* data, size_t size, void* cookie,
+ Asset* asset, bool copyData, const Asset* idmap);
+
+ ssize_t getResourcePackageIndex(uint32_t resID) const;
+ ssize_t getEntry(
+ const Package* package, int typeIndex, int entryIndex,
+ const ResTable_config* config,
+ const ResTable_type** outType, const ResTable_entry** outEntry,
+ const Type** outTypeClass) const;
+ status_t parsePackage(
+ const ResTable_package* const pkg, const Header* const header, uint32_t idmap_id);
+
+ void print_value(const Package* pkg, const Res_value& value) const;
+
+ mutable Mutex mLock;
+
+ status_t mError;
+
+ ResTable_config mParams;
+
+ // Array of all resource tables.
+ Vector<Header*> mHeaders;
+
+ // Array of packages in all resource tables.
+ Vector<PackageGroup*> mPackageGroups;
+
+ // Mapping from resource package IDs to indices into the internal
+ // package array.
+ uint8_t mPackageMap[256];
+};
+
+} // namespace android
+
+#endif // _LIBS_UTILS_RESOURCE_TYPES_H
diff --git a/include/androidfw/StreamingZipInflater.h b/include/androidfw/StreamingZipInflater.h
new file mode 100644
index 0000000..3ace5d5
--- /dev/null
+++ b/include/androidfw/StreamingZipInflater.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#ifndef __LIBS_STREAMINGZIPINFLATER_H
+#define __LIBS_STREAMINGZIPINFLATER_H
+
+#include <unistd.h>
+#include <inttypes.h>
+#include <zlib.h>
+
+#include <utils/Compat.h>
+
+namespace android {
+
+class StreamingZipInflater {
+public:
+ static const size_t INPUT_CHUNK_SIZE = 64 * 1024;
+ static const size_t OUTPUT_CHUNK_SIZE = 64 * 1024;
+
+ // Flavor that pages in the compressed data from a fd
+ StreamingZipInflater(int fd, off64_t compDataStart, size_t uncompSize, size_t compSize);
+
+ // Flavor that gets the compressed data from an in-memory buffer
+ StreamingZipInflater(class FileMap* dataMap, size_t uncompSize);
+
+ ~StreamingZipInflater();
+
+ // read 'count' bytes of uncompressed data from the current position. outBuf may
+ // be NULL, in which case the data is consumed and discarded.
+ ssize_t read(void* outBuf, size_t count);
+
+ // seeking backwards requires uncompressing fom the beginning, so is very
+ // expensive. seeking forwards only requires uncompressing from the current
+ // position to the destination.
+ off64_t seekAbsolute(off64_t absoluteInputPosition);
+
+private:
+ void initInflateState();
+ int readNextChunk();
+
+ // where to find the uncompressed data
+ int mFd;
+ off64_t mInFileStart; // where the compressed data lives in the file
+ class FileMap* mDataMap;
+
+ z_stream mInflateState;
+ bool mStreamNeedsInit;
+
+ // output invariants for this asset
+ uint8_t* mOutBuf; // output buf for decompressed bytes
+ size_t mOutBufSize; // allocated size of mOutBuf
+ size_t mOutTotalSize; // total uncompressed size of the blob
+
+ // current output state bookkeeping
+ off64_t mOutCurPosition; // current position in total offset
+ size_t mOutLastDecoded; // last decoded byte + 1 in mOutbuf
+ size_t mOutDeliverable; // next undelivered byte of decoded output in mOutBuf
+
+ // input invariants
+ uint8_t* mInBuf;
+ size_t mInBufSize; // allocated size of mInBuf;
+ size_t mInTotalSize; // total size of compressed data for this blob
+
+ // input state bookkeeping
+ size_t mInNextChunkOffset; // offset from start of blob at which the next input chunk lies
+ // the z_stream contains state about input block consumption
+};
+
+}
+
+#endif
diff --git a/include/androidfw/ZipFileRO.h b/include/androidfw/ZipFileRO.h
new file mode 100644
index 0000000..547e36a
--- /dev/null
+++ b/include/androidfw/ZipFileRO.h
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2007 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.
+ */
+
+/*
+ * Read-only access to Zip archives, with minimal heap allocation.
+ *
+ * This is similar to the more-complete ZipFile class, but no attempt
+ * has been made to make them interchangeable. This class operates under
+ * a very different set of assumptions and constraints.
+ *
+ * One such assumption is that if you're getting file descriptors for
+ * use with this class as a child of a fork() operation, you must be on
+ * a pread() to guarantee correct operation. This is because pread() can
+ * atomically read at a file offset without worrying about a lock around an
+ * lseek() + read() pair.
+ */
+#ifndef __LIBS_ZIPFILERO_H
+#define __LIBS_ZIPFILERO_H
+
+#include <utils/Compat.h>
+#include <utils/Errors.h>
+#include <utils/FileMap.h>
+#include <utils/threads.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+
+namespace android {
+
+/*
+ * Trivial typedef to ensure that ZipEntryRO is not treated as a simple
+ * integer. We use NULL to indicate an invalid value.
+ */
+typedef void* ZipEntryRO;
+
+/*
+ * Open a Zip archive for reading.
+ *
+ * We want "open" and "find entry by name" to be fast operations, and we
+ * want to use as little memory as possible. We memory-map the file,
+ * and load a hash table with pointers to the filenames (which aren't
+ * null-terminated). The other fields are at a fixed offset from the
+ * filename, so we don't need to extract those (but we do need to byte-read
+ * and endian-swap them every time we want them).
+ *
+ * To speed comparisons when doing a lookup by name, we could make the mapping
+ * "private" (copy-on-write) and null-terminate the filenames after verifying
+ * the record structure. However, this requires a private mapping of
+ * every page that the Central Directory touches. Easier to tuck a copy
+ * of the string length into the hash table entry.
+ *
+ * NOTE: If this is used on file descriptors inherited from a fork() operation,
+ * you must be on a platform that implements pread() to guarantee correctness
+ * on the shared file descriptors.
+ */
+class ZipFileRO {
+public:
+ ZipFileRO()
+ : mFd(-1), mFileName(NULL), mFileLength(-1),
+ mDirectoryMap(NULL),
+ mNumEntries(-1), mDirectoryOffset(-1),
+ mHashTableSize(-1), mHashTable(NULL)
+ {}
+
+ ~ZipFileRO();
+
+ /*
+ * Open an archive.
+ */
+ status_t open(const char* zipFileName);
+
+ /*
+ * Find an entry, by name. Returns the entry identifier, or NULL if
+ * not found.
+ *
+ * If two entries have the same name, one will be chosen at semi-random.
+ */
+ ZipEntryRO findEntryByName(const char* fileName) const;
+
+ /*
+ * Return the #of entries in the Zip archive.
+ */
+ int getNumEntries(void) const {
+ return mNumEntries;
+ }
+
+ /*
+ * Return the Nth entry. Zip file entries are not stored in sorted
+ * order, and updated entries may appear at the end, so anyone walking
+ * the archive needs to avoid making ordering assumptions. We take
+ * that further by returning the Nth non-empty entry in the hash table
+ * rather than the Nth entry in the archive.
+ *
+ * Valid values are [0..numEntries).
+ *
+ * [This is currently O(n). If it needs to be fast we can allocate an
+ * additional data structure or provide an iterator interface.]
+ */
+ ZipEntryRO findEntryByIndex(int idx) const;
+
+ /*
+ * Copy the filename into the supplied buffer. Returns 0 on success,
+ * -1 if "entry" is invalid, or the filename length if it didn't fit. The
+ * length, and the returned string, include the null-termination.
+ */
+ int getEntryFileName(ZipEntryRO entry, char* buffer, int bufLen) const;
+
+ /*
+ * Get the vital stats for an entry. Pass in NULL pointers for anything
+ * you don't need.
+ *
+ * "*pOffset" holds the Zip file offset of the entry's data.
+ *
+ * Returns "false" if "entry" is bogus or if the data in the Zip file
+ * appears to be bad.
+ */
+ bool getEntryInfo(ZipEntryRO entry, int* pMethod, size_t* pUncompLen,
+ size_t* pCompLen, off64_t* pOffset, long* pModWhen, long* pCrc32) const;
+
+ /*
+ * Create a new FileMap object that maps a subset of the archive. For
+ * an uncompressed entry this effectively provides a pointer to the
+ * actual data, for a compressed entry this provides the input buffer
+ * for inflate().
+ */
+ FileMap* createEntryFileMap(ZipEntryRO entry) const;
+
+ /*
+ * Uncompress the data into a buffer. Depending on the compression
+ * format, this is either an "inflate" operation or a memcpy.
+ *
+ * Use "uncompLen" from getEntryInfo() to determine the required
+ * buffer size.
+ *
+ * Returns "true" on success.
+ */
+ bool uncompressEntry(ZipEntryRO entry, void* buffer) const;
+
+ /*
+ * Uncompress the data to an open file descriptor.
+ */
+ bool uncompressEntry(ZipEntryRO entry, int fd) const;
+
+ /* Zip compression methods we support */
+ enum {
+ kCompressStored = 0, // no compression
+ kCompressDeflated = 8, // standard deflate
+ };
+
+ /*
+ * Utility function: uncompress deflated data, buffer to buffer.
+ */
+ static bool inflateBuffer(void* outBuf, const void* inBuf,
+ size_t uncompLen, size_t compLen);
+
+ /*
+ * Utility function: uncompress deflated data, buffer to fd.
+ */
+ static bool inflateBuffer(int fd, const void* inBuf,
+ size_t uncompLen, size_t compLen);
+
+ /*
+ * Utility function to convert ZIP's time format to a timespec struct.
+ */
+ static inline void zipTimeToTimespec(long when, struct tm* timespec) {
+ const long date = when >> 16;
+ timespec->tm_year = ((date >> 9) & 0x7F) + 80; // Zip is years since 1980
+ timespec->tm_mon = (date >> 5) & 0x0F;
+ timespec->tm_mday = date & 0x1F;
+
+ timespec->tm_hour = (when >> 11) & 0x1F;
+ timespec->tm_min = (when >> 5) & 0x3F;
+ timespec->tm_sec = (when & 0x1F) << 1;
+ }
+
+ /*
+ * Some basic functions for raw data manipulation. "LE" means
+ * Little Endian.
+ */
+ static inline unsigned short get2LE(const unsigned char* buf) {
+ return buf[0] | (buf[1] << 8);
+ }
+ static inline unsigned long get4LE(const unsigned char* buf) {
+ return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
+ }
+
+private:
+ /* these are private and not defined */
+ ZipFileRO(const ZipFileRO& src);
+ ZipFileRO& operator=(const ZipFileRO& src);
+
+ /* locate and parse the central directory */
+ bool mapCentralDirectory(void);
+
+ /* parse the archive, prepping internal structures */
+ bool parseZipArchive(void);
+
+ /* add a new entry to the hash table */
+ void addToHash(const char* str, int strLen, unsigned int hash);
+
+ /* compute string hash code */
+ static unsigned int computeHash(const char* str, int len);
+
+ /* convert a ZipEntryRO back to a hash table index */
+ int entryToIndex(const ZipEntryRO entry) const;
+
+ /*
+ * One entry in the hash table.
+ */
+ typedef struct HashEntry {
+ const char* name;
+ unsigned short nameLen;
+ //unsigned int hash;
+ } HashEntry;
+
+ /* open Zip archive */
+ int mFd;
+
+ /* Lock for handling the file descriptor (seeks, etc) */
+ mutable Mutex mFdLock;
+
+ /* zip file name */
+ char* mFileName;
+
+ /* length of file */
+ size_t mFileLength;
+
+ /* mapped file */
+ FileMap* mDirectoryMap;
+
+ /* number of entries in the Zip archive */
+ int mNumEntries;
+
+ /* CD directory offset in the Zip archive */
+ off64_t mDirectoryOffset;
+
+ /*
+ * We know how many entries are in the Zip archive, so we have a
+ * fixed-size hash table. We probe for an empty slot.
+ */
+ int mHashTableSize;
+ HashEntry* mHashTable;
+};
+
+}; // namespace android
+
+#endif /*__LIBS_ZIPFILERO_H*/
diff --git a/include/androidfw/ZipUtils.h b/include/androidfw/ZipUtils.h
new file mode 100644
index 0000000..42c42b6
--- /dev/null
+++ b/include/androidfw/ZipUtils.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2007 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.
+ */
+
+//
+// Miscellaneous zip/gzip utility functions.
+//
+#ifndef __LIBS_ZIPUTILS_H
+#define __LIBS_ZIPUTILS_H
+
+#include <stdio.h>
+
+namespace android {
+
+/*
+ * Container class for utility functions, primarily for namespace reasons.
+ */
+class ZipUtils {
+public:
+ /*
+ * General utility function for uncompressing "deflate" data from a file
+ * to a buffer.
+ */
+ static bool inflateToBuffer(int fd, void* buf, long uncompressedLen,
+ long compressedLen);
+ static bool inflateToBuffer(FILE* fp, void* buf, long uncompressedLen,
+ long compressedLen);
+
+ /*
+ * Someday we might want to make this generic and handle bzip2 ".bz2"
+ * files too.
+ *
+ * We could declare gzip to be a sub-class of zip that has exactly
+ * one always-compressed entry, but we currently want to treat Zip
+ * and gzip as distinct, so there's no value.
+ *
+ * The zlib library has some gzip utilities, but it has no interface
+ * for extracting the uncompressed length of the file (you do *not*
+ * want to gzseek to the end).
+ *
+ * Pass in a seeked file pointer for the gzip file. If this is a gzip
+ * file, we set our return values appropriately and return "true" with
+ * the file seeked to the start of the compressed data.
+ */
+ static bool examineGzip(FILE* fp, int* pCompressionMethod,
+ long* pUncompressedLen, long* pCompressedLen, unsigned long* pCRC32);
+
+private:
+ ZipUtils() {}
+ ~ZipUtils() {}
+};
+
+}; // namespace android
+
+#endif /*__LIBS_ZIPUTILS_H*/
diff --git a/include/androidfw/misc.h b/include/androidfw/misc.h
new file mode 100644
index 0000000..5a5a0e2
--- /dev/null
+++ b/include/androidfw/misc.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2005 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.
+ */
+
+#include <sys/types.h>
+
+//
+// Handy utility functions and portability code.
+//
+#ifndef _LIBS_ANDROID_FW_MISC_H
+#define _LIBS_ANDROID_FW_MISC_H
+
+namespace android {
+
+/*
+ * Some utility functions for working with files. These could be made
+ * part of a "File" class.
+ */
+typedef enum FileType {
+ kFileTypeUnknown = 0,
+ kFileTypeNonexistent, // i.e. ENOENT
+ kFileTypeRegular,
+ kFileTypeDirectory,
+ kFileTypeCharDev,
+ kFileTypeBlockDev,
+ kFileTypeFifo,
+ kFileTypeSymlink,
+ kFileTypeSocket,
+} FileType;
+/* get the file's type; follows symlinks */
+FileType getFileType(const char* fileName);
+/* get the file's modification date; returns -1 w/errno set on failure */
+time_t getFileModDate(const char* fileName);
+
+}; // namespace android
+
+#endif // _LIBS_ANDROID_FW_MISC_H
diff --git a/include/batteryservice/BatteryService.h b/include/batteryservice/BatteryService.h
index 829061a..a2530b6 100644
--- a/include/batteryservice/BatteryService.h
+++ b/include/batteryservice/BatteryService.h
@@ -43,6 +43,13 @@
BATTERY_HEALTH_COLD = 7, // equals BatteryManager.BATTERY_HEALTH_COLD constant
};
+// must be kept in sync with definitions in BatteryProperty.java
+enum {
+ BATTERY_PROP_CHARGE_COUNTER = 1, // equals BatteryProperty.BATTERY_PROP_CHARGE_COUNTER constant
+ BATTERY_PROP_CURRENT_NOW = 2, // equals BatteryProperty.BATTERY_PROP_CURRENT_NOW constant
+ BATTERY_PROP_CURRENT_AVG = 3, // equals BatteryProperty.BATTERY_PROP_CURRENT_AVG constant
+};
+
struct BatteryProperties {
bool chargerAcOnline;
bool chargerUsbOnline;
@@ -61,6 +68,18 @@
status_t readFromParcel(Parcel* parcel);
};
+struct BatteryExtraProperties {
+ int batteryCurrentNow;
+ int batteryChargeCounter;
+};
+
+struct BatteryProperty {
+ int valueInt;
+
+ status_t writeToParcel(Parcel* parcel) const;
+ status_t readFromParcel(Parcel* parcel);
+};
+
}; // namespace android
#endif // ANDROID_BATTERYSERVICE_H
diff --git a/include/batteryservice/IBatteryPropertiesRegistrar.h b/include/batteryservice/IBatteryPropertiesRegistrar.h
index 8d28b1d..eca075d 100644
--- a/include/batteryservice/IBatteryPropertiesRegistrar.h
+++ b/include/batteryservice/IBatteryPropertiesRegistrar.h
@@ -26,6 +26,7 @@
enum {
REGISTER_LISTENER = IBinder::FIRST_CALL_TRANSACTION,
UNREGISTER_LISTENER,
+ GET_PROPERTY,
};
class IBatteryPropertiesRegistrar : public IInterface {
@@ -34,6 +35,7 @@
virtual void registerListener(const sp<IBatteryPropertiesListener>& listener) = 0;
virtual void unregisterListener(const sp<IBatteryPropertiesListener>& listener) = 0;
+ virtual status_t getProperty(int id, struct BatteryProperty *val) = 0;
};
class BnBatteryPropertiesRegistrar : public BnInterface<IBatteryPropertiesRegistrar> {
diff --git a/include/binder/IBatteryStats.h b/include/binder/IBatteryStats.h
new file mode 100644
index 0000000..f4a8aa3
--- /dev/null
+++ b/include/binder/IBatteryStats.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#ifndef ANDROID_IBATTERYSTATS_H
+#define ANDROID_IBATTERYSTATS_H
+
+#include <binder/IInterface.h>
+
+namespace android {
+
+// ----------------------------------------------------------------------
+
+class IBatteryStats : public IInterface
+{
+public:
+ DECLARE_META_INTERFACE(BatteryStats);
+
+ virtual void noteStartSensor(int uid, int sensor) = 0;
+ virtual void noteStopSensor(int uid, int sensor) = 0;
+
+ enum {
+ NOTE_START_SENSOR_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION,
+ NOTE_STOP_SENSOR_TRANSACTION,
+ };
+};
+
+// ----------------------------------------------------------------------
+
+class BnBatteryStats : public BnInterface<IBatteryStats>
+{
+public:
+ virtual status_t onTransact( uint32_t code,
+ const Parcel& data,
+ Parcel* reply,
+ uint32_t flags = 0);
+};
+
+// ----------------------------------------------------------------------
+
+}; // namespace android
+
+#endif // ANDROID_IBATTERYSTATS_H
diff --git a/include/binder/IBinder.h b/include/binder/IBinder.h
index 8b84951..43b6543 100644
--- a/include/binder/IBinder.h
+++ b/include/binder/IBinder.h
@@ -81,14 +81,6 @@
Parcel* reply,
uint32_t flags = 0) = 0;
- /**
- * This method allows you to add data that is transported through
- * IPC along with your IBinder pointer. When implementing a Binder
- * object, override it to write your desired data in to @a outData.
- * You can then call getConstantData() on your IBinder to retrieve
- * that data, from any process. You MUST return the number of bytes
- * written in to the parcel (including padding).
- */
class DeathRecipient : public virtual RefBase
{
public:
diff --git a/include/binder/Parcel.h b/include/binder/Parcel.h
index 7a782f5..0e20da9 100644
--- a/include/binder/Parcel.h
+++ b/include/binder/Parcel.h
@@ -124,6 +124,11 @@
// will be closed once the parcel is destroyed.
status_t writeDupFileDescriptor(int fd);
+ // Writes a raw fd and optional comm channel fd to the parcel as a ParcelFileDescriptor.
+ // A dup's of the fds are made, which will be closed once the parcel is destroyed.
+ // Null values are passed as -1.
+ status_t writeParcelFileDescriptor(int fd, int commChannel = -1);
+
// Writes a blob to the parcel.
// If the blob is small, then it is stored in-place, otherwise it is
// transferred by way of an anonymous shared memory region.
@@ -183,6 +188,11 @@
// in the parcel, which you do not own -- use dup() to get your own copy.
int readFileDescriptor() const;
+ // Reads a ParcelFileDescriptor from the parcel. Returns the raw fd as
+ // the result, and the optional comm channel fd in outCommChannel.
+ // Null values are returned as -1.
+ int readParcelFileDescriptor(int& outCommChannel) const;
+
// Reads a blob from the parcel.
// The caller should call release() on the blob after reading its contents.
status_t readBlob(size_t len, ReadableBlob* outBlob) const;
diff --git a/include/input/IInputFlinger.h b/include/input/IInputFlinger.h
new file mode 100644
index 0000000..79ff12a
--- /dev/null
+++ b/include/input/IInputFlinger.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#ifndef _LIBINPUT_IINPUT_FLINGER_H
+#define _LIBINPUT_IINPUT_FLINGER_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <binder/IInterface.h>
+
+namespace android {
+
+/*
+ * This class defines the Binder IPC interface for accessing various
+ * InputFlinger features.
+ */
+class IInputFlinger : public IInterface {
+public:
+ DECLARE_META_INTERFACE(InputFlinger);
+
+ virtual status_t doSomething() = 0;
+};
+
+
+/**
+ * Binder implementation.
+ */
+class BnInputFlinger : public BnInterface<IInputFlinger> {
+public:
+ enum {
+ DO_SOMETHING_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION,
+ };
+
+ virtual status_t onTransact(uint32_t code, const Parcel& data,
+ Parcel* reply, uint32_t flags = 0);
+};
+
+} // namespace android
+
+#endif // _LIBINPUT_IINPUT_FLINGER_H
diff --git a/include/media/hardware/HDCPAPI.h b/include/media/hardware/HDCPAPI.h
index d4abb3f..3a53e9f 100644
--- a/include/media/hardware/HDCPAPI.h
+++ b/include/media/hardware/HDCPAPI.h
@@ -88,6 +88,11 @@
// Request to shutdown the active HDCP session.
virtual status_t shutdownAsync() = 0;
+ // Returns the capability bitmask of this HDCP session.
+ virtual uint32_t getCaps() {
+ return HDCP_CAPS_ENCRYPT;
+ }
+
// ENCRYPTION only:
// Encrypt data according to the HDCP spec. "size" bytes of data are
// available at "inData" (virtual address), "size" may not be a multiple
diff --git a/libs/androidfw/Android.mk b/libs/androidfw/Android.mk
new file mode 100644
index 0000000..d80612b
--- /dev/null
+++ b/libs/androidfw/Android.mk
@@ -0,0 +1,93 @@
+# Copyright (C) 2010 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+# libandroidfw is partially built for the host (used by obbtool and others)
+# These files are common to host and target builds.
+
+commonSources := \
+ Asset.cpp \
+ AssetDir.cpp \
+ AssetManager.cpp \
+ misc.cpp \
+ ObbFile.cpp \
+ ResourceTypes.cpp \
+ StreamingZipInflater.cpp \
+ ZipFileRO.cpp \
+ ZipUtils.cpp
+
+deviceSources := \
+ $(commonSources) \
+ BackupData.cpp \
+ BackupHelpers.cpp \
+ CursorWindow.cpp
+
+hostSources := \
+ $(commonSources)
+
+# For the host
+# =====================================================
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= $(hostSources)
+
+LOCAL_MODULE:= libandroidfw
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_CFLAGS += -DSTATIC_ANDROIDFW_FOR_TOOLS
+
+LOCAL_C_INCLUDES := \
+ external/zlib
+
+LOCAL_STATIC_LIBRARIES := liblog
+
+include $(BUILD_HOST_STATIC_LIBRARY)
+
+
+# For the device
+# =====================================================
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= $(deviceSources)
+
+LOCAL_SHARED_LIBRARIES := \
+ libbinder \
+ liblog \
+ libcutils \
+ libutils \
+ libz
+
+LOCAL_C_INCLUDES := \
+ external/icu4c/common \
+ external/zlib
+
+LOCAL_MODULE:= libandroidfw
+
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_SHARED_LIBRARY)
+
+
+# Include subdirectory makefiles
+# ============================================================
+
+# If we're building with ONE_SHOT_MAKEFILE (mm, mmm), then what the framework
+# team really wants is to build the stuff defined by this makefile.
+ifeq (,$(ONE_SHOT_MAKEFILE))
+include $(call first-makefiles-under,$(LOCAL_PATH))
+endif
diff --git a/libs/androidfw/Asset.cpp b/libs/androidfw/Asset.cpp
new file mode 100644
index 0000000..cb7628d
--- /dev/null
+++ b/libs/androidfw/Asset.cpp
@@ -0,0 +1,897 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Provide access to a read-only asset.
+//
+
+#define LOG_TAG "asset"
+//#define NDEBUG 0
+
+#include <androidfw/Asset.h>
+#include <androidfw/StreamingZipInflater.h>
+#include <androidfw/ZipFileRO.h>
+#include <androidfw/ZipUtils.h>
+#include <utils/Atomic.h>
+#include <utils/FileMap.h>
+#include <utils/Log.h>
+#include <utils/threads.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <memory.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+using namespace android;
+
+#ifndef O_BINARY
+# define O_BINARY 0
+#endif
+
+static Mutex gAssetLock;
+static int32_t gCount = 0;
+static Asset* gHead = NULL;
+static Asset* gTail = NULL;
+
+int32_t Asset::getGlobalCount()
+{
+ AutoMutex _l(gAssetLock);
+ return gCount;
+}
+
+String8 Asset::getAssetAllocations()
+{
+ AutoMutex _l(gAssetLock);
+ String8 res;
+ Asset* cur = gHead;
+ while (cur != NULL) {
+ if (cur->isAllocated()) {
+ res.append(" ");
+ res.append(cur->getAssetSource());
+ off64_t size = (cur->getLength()+512)/1024;
+ char buf[64];
+ sprintf(buf, ": %dK\n", (int)size);
+ res.append(buf);
+ }
+ cur = cur->mNext;
+ }
+
+ return res;
+}
+
+Asset::Asset(void)
+ : mAccessMode(ACCESS_UNKNOWN)
+{
+ AutoMutex _l(gAssetLock);
+ gCount++;
+ mNext = mPrev = NULL;
+ if (gTail == NULL) {
+ gHead = gTail = this;
+ } else {
+ mPrev = gTail;
+ gTail->mNext = this;
+ gTail = this;
+ }
+ //ALOGI("Creating Asset %p #%d\n", this, gCount);
+}
+
+Asset::~Asset(void)
+{
+ AutoMutex _l(gAssetLock);
+ gCount--;
+ if (gHead == this) {
+ gHead = mNext;
+ }
+ if (gTail == this) {
+ gTail = mPrev;
+ }
+ if (mNext != NULL) {
+ mNext->mPrev = mPrev;
+ }
+ if (mPrev != NULL) {
+ mPrev->mNext = mNext;
+ }
+ mNext = mPrev = NULL;
+ //ALOGI("Destroying Asset in %p #%d\n", this, gCount);
+}
+
+/*
+ * Create a new Asset from a file on disk. There is a fair chance that
+ * the file doesn't actually exist.
+ *
+ * We can use "mode" to decide how we want to go about it.
+ */
+/*static*/ Asset* Asset::createFromFile(const char* fileName, AccessMode mode)
+{
+ _FileAsset* pAsset;
+ status_t result;
+ off64_t length;
+ int fd;
+
+ fd = open(fileName, O_RDONLY | O_BINARY);
+ if (fd < 0)
+ return NULL;
+
+ /*
+ * Under Linux, the lseek fails if we actually opened a directory. To
+ * be correct we should test the file type explicitly, but since we
+ * always open things read-only it doesn't really matter, so there's
+ * no value in incurring the extra overhead of an fstat() call.
+ */
+ // TODO(kroot): replace this with fstat despite the plea above.
+#if 1
+ length = lseek64(fd, 0, SEEK_END);
+ if (length < 0) {
+ ::close(fd);
+ return NULL;
+ }
+ (void) lseek64(fd, 0, SEEK_SET);
+#else
+ struct stat st;
+ if (fstat(fd, &st) < 0) {
+ ::close(fd);
+ return NULL;
+ }
+
+ if (!S_ISREG(st.st_mode)) {
+ ::close(fd);
+ return NULL;
+ }
+#endif
+
+ pAsset = new _FileAsset;
+ result = pAsset->openChunk(fileName, fd, 0, length);
+ if (result != NO_ERROR) {
+ delete pAsset;
+ return NULL;
+ }
+
+ pAsset->mAccessMode = mode;
+ return pAsset;
+}
+
+
+/*
+ * Create a new Asset from a compressed file on disk. There is a fair chance
+ * that the file doesn't actually exist.
+ *
+ * We currently support gzip files. We might want to handle .bz2 someday.
+ */
+/*static*/ Asset* Asset::createFromCompressedFile(const char* fileName,
+ AccessMode mode)
+{
+ _CompressedAsset* pAsset;
+ status_t result;
+ off64_t fileLen;
+ bool scanResult;
+ long offset;
+ int method;
+ long uncompressedLen, compressedLen;
+ int fd;
+
+ fd = open(fileName, O_RDONLY | O_BINARY);
+ if (fd < 0)
+ return NULL;
+
+ fileLen = lseek(fd, 0, SEEK_END);
+ if (fileLen < 0) {
+ ::close(fd);
+ return NULL;
+ }
+ (void) lseek(fd, 0, SEEK_SET);
+
+ /* want buffered I/O for the file scan; must dup so fclose() is safe */
+ FILE* fp = fdopen(dup(fd), "rb");
+ if (fp == NULL) {
+ ::close(fd);
+ return NULL;
+ }
+
+ unsigned long crc32;
+ scanResult = ZipUtils::examineGzip(fp, &method, &uncompressedLen,
+ &compressedLen, &crc32);
+ offset = ftell(fp);
+ fclose(fp);
+ if (!scanResult) {
+ ALOGD("File '%s' is not in gzip format\n", fileName);
+ ::close(fd);
+ return NULL;
+ }
+
+ pAsset = new _CompressedAsset;
+ result = pAsset->openChunk(fd, offset, method, uncompressedLen,
+ compressedLen);
+ if (result != NO_ERROR) {
+ delete pAsset;
+ return NULL;
+ }
+
+ pAsset->mAccessMode = mode;
+ return pAsset;
+}
+
+
+#if 0
+/*
+ * Create a new Asset from part of an open file.
+ */
+/*static*/ Asset* Asset::createFromFileSegment(int fd, off64_t offset,
+ size_t length, AccessMode mode)
+{
+ _FileAsset* pAsset;
+ status_t result;
+
+ pAsset = new _FileAsset;
+ result = pAsset->openChunk(NULL, fd, offset, length);
+ if (result != NO_ERROR)
+ return NULL;
+
+ pAsset->mAccessMode = mode;
+ return pAsset;
+}
+
+/*
+ * Create a new Asset from compressed data in an open file.
+ */
+/*static*/ Asset* Asset::createFromCompressedData(int fd, off64_t offset,
+ int compressionMethod, size_t uncompressedLen, size_t compressedLen,
+ AccessMode mode)
+{
+ _CompressedAsset* pAsset;
+ status_t result;
+
+ pAsset = new _CompressedAsset;
+ result = pAsset->openChunk(fd, offset, compressionMethod,
+ uncompressedLen, compressedLen);
+ if (result != NO_ERROR)
+ return NULL;
+
+ pAsset->mAccessMode = mode;
+ return pAsset;
+}
+#endif
+
+/*
+ * Create a new Asset from a memory mapping.
+ */
+/*static*/ Asset* Asset::createFromUncompressedMap(FileMap* dataMap,
+ AccessMode mode)
+{
+ _FileAsset* pAsset;
+ status_t result;
+
+ pAsset = new _FileAsset;
+ result = pAsset->openChunk(dataMap);
+ if (result != NO_ERROR)
+ return NULL;
+
+ pAsset->mAccessMode = mode;
+ return pAsset;
+}
+
+/*
+ * Create a new Asset from compressed data in a memory mapping.
+ */
+/*static*/ Asset* Asset::createFromCompressedMap(FileMap* dataMap,
+ int method, size_t uncompressedLen, AccessMode mode)
+{
+ _CompressedAsset* pAsset;
+ status_t result;
+
+ pAsset = new _CompressedAsset;
+ result = pAsset->openChunk(dataMap, method, uncompressedLen);
+ if (result != NO_ERROR)
+ return NULL;
+
+ pAsset->mAccessMode = mode;
+ return pAsset;
+}
+
+
+/*
+ * Do generic seek() housekeeping. Pass in the offset/whence values from
+ * the seek request, along with the current chunk offset and the chunk
+ * length.
+ *
+ * Returns the new chunk offset, or -1 if the seek is illegal.
+ */
+off64_t Asset::handleSeek(off64_t offset, int whence, off64_t curPosn, off64_t maxPosn)
+{
+ off64_t newOffset;
+
+ switch (whence) {
+ case SEEK_SET:
+ newOffset = offset;
+ break;
+ case SEEK_CUR:
+ newOffset = curPosn + offset;
+ break;
+ case SEEK_END:
+ newOffset = maxPosn + offset;
+ break;
+ default:
+ ALOGW("unexpected whence %d\n", whence);
+ // this was happening due to an off64_t size mismatch
+ assert(false);
+ return (off64_t) -1;
+ }
+
+ if (newOffset < 0 || newOffset > maxPosn) {
+ ALOGW("seek out of range: want %ld, end=%ld\n",
+ (long) newOffset, (long) maxPosn);
+ return (off64_t) -1;
+ }
+
+ return newOffset;
+}
+
+
+/*
+ * ===========================================================================
+ * _FileAsset
+ * ===========================================================================
+ */
+
+/*
+ * Constructor.
+ */
+_FileAsset::_FileAsset(void)
+ : mStart(0), mLength(0), mOffset(0), mFp(NULL), mFileName(NULL), mMap(NULL), mBuf(NULL)
+{
+}
+
+/*
+ * Destructor. Release resources.
+ */
+_FileAsset::~_FileAsset(void)
+{
+ close();
+}
+
+/*
+ * Operate on a chunk of an uncompressed file.
+ *
+ * Zero-length chunks are allowed.
+ */
+status_t _FileAsset::openChunk(const char* fileName, int fd, off64_t offset, size_t length)
+{
+ assert(mFp == NULL); // no reopen
+ assert(mMap == NULL);
+ assert(fd >= 0);
+ assert(offset >= 0);
+
+ /*
+ * Seek to end to get file length.
+ */
+ off64_t fileLength;
+ fileLength = lseek64(fd, 0, SEEK_END);
+ if (fileLength == (off64_t) -1) {
+ // probably a bad file descriptor
+ ALOGD("failed lseek (errno=%d)\n", errno);
+ return UNKNOWN_ERROR;
+ }
+
+ if ((off64_t) (offset + length) > fileLength) {
+ ALOGD("start (%ld) + len (%ld) > end (%ld)\n",
+ (long) offset, (long) length, (long) fileLength);
+ return BAD_INDEX;
+ }
+
+ /* after fdopen, the fd will be closed on fclose() */
+ mFp = fdopen(fd, "rb");
+ if (mFp == NULL)
+ return UNKNOWN_ERROR;
+
+ mStart = offset;
+ mLength = length;
+ assert(mOffset == 0);
+
+ /* seek the FILE* to the start of chunk */
+ if (fseek(mFp, mStart, SEEK_SET) != 0) {
+ assert(false);
+ }
+
+ mFileName = fileName != NULL ? strdup(fileName) : NULL;
+
+ return NO_ERROR;
+}
+
+/*
+ * Create the chunk from the map.
+ */
+status_t _FileAsset::openChunk(FileMap* dataMap)
+{
+ assert(mFp == NULL); // no reopen
+ assert(mMap == NULL);
+ assert(dataMap != NULL);
+
+ mMap = dataMap;
+ mStart = -1; // not used
+ mLength = dataMap->getDataLength();
+ assert(mOffset == 0);
+
+ return NO_ERROR;
+}
+
+/*
+ * Read a chunk of data.
+ */
+ssize_t _FileAsset::read(void* buf, size_t count)
+{
+ size_t maxLen;
+ size_t actual;
+
+ assert(mOffset >= 0 && mOffset <= mLength);
+
+ if (getAccessMode() == ACCESS_BUFFER) {
+ /*
+ * On first access, read or map the entire file. The caller has
+ * requested buffer access, either because they're going to be
+ * using the buffer or because what they're doing has appropriate
+ * performance needs and access patterns.
+ */
+ if (mBuf == NULL)
+ getBuffer(false);
+ }
+
+ /* adjust count if we're near EOF */
+ maxLen = mLength - mOffset;
+ if (count > maxLen)
+ count = maxLen;
+
+ if (!count)
+ return 0;
+
+ if (mMap != NULL) {
+ /* copy from mapped area */
+ //printf("map read\n");
+ memcpy(buf, (char*)mMap->getDataPtr() + mOffset, count);
+ actual = count;
+ } else if (mBuf != NULL) {
+ /* copy from buffer */
+ //printf("buf read\n");
+ memcpy(buf, (char*)mBuf + mOffset, count);
+ actual = count;
+ } else {
+ /* read from the file */
+ //printf("file read\n");
+ if (ftell(mFp) != mStart + mOffset) {
+ ALOGE("Hosed: %ld != %ld+%ld\n",
+ ftell(mFp), (long) mStart, (long) mOffset);
+ assert(false);
+ }
+
+ /*
+ * This returns 0 on error or eof. We need to use ferror() or feof()
+ * to tell the difference, but we don't currently have those on the
+ * device. However, we know how much data is *supposed* to be in the
+ * file, so if we don't read the full amount we know something is
+ * hosed.
+ */
+ actual = fread(buf, 1, count, mFp);
+ if (actual == 0) // something failed -- I/O error?
+ return -1;
+
+ assert(actual == count);
+ }
+
+ mOffset += actual;
+ return actual;
+}
+
+/*
+ * Seek to a new position.
+ */
+off64_t _FileAsset::seek(off64_t offset, int whence)
+{
+ off64_t newPosn;
+ off64_t actualOffset;
+
+ // compute new position within chunk
+ newPosn = handleSeek(offset, whence, mOffset, mLength);
+ if (newPosn == (off64_t) -1)
+ return newPosn;
+
+ actualOffset = mStart + newPosn;
+
+ if (mFp != NULL) {
+ if (fseek(mFp, (long) actualOffset, SEEK_SET) != 0)
+ return (off64_t) -1;
+ }
+
+ mOffset = actualOffset - mStart;
+ return mOffset;
+}
+
+/*
+ * Close the asset.
+ */
+void _FileAsset::close(void)
+{
+ if (mMap != NULL) {
+ mMap->release();
+ mMap = NULL;
+ }
+ if (mBuf != NULL) {
+ delete[] mBuf;
+ mBuf = NULL;
+ }
+
+ if (mFileName != NULL) {
+ free(mFileName);
+ mFileName = NULL;
+ }
+
+ if (mFp != NULL) {
+ // can only be NULL when called from destructor
+ // (otherwise we would never return this object)
+ fclose(mFp);
+ mFp = NULL;
+ }
+}
+
+/*
+ * Return a read-only pointer to a buffer.
+ *
+ * We can either read the whole thing in or map the relevant piece of
+ * the source file. Ideally a map would be established at a higher
+ * level and we'd be using a different object, but we didn't, so we
+ * deal with it here.
+ */
+const void* _FileAsset::getBuffer(bool wordAligned)
+{
+ /* subsequent requests just use what we did previously */
+ if (mBuf != NULL)
+ return mBuf;
+ if (mMap != NULL) {
+ if (!wordAligned) {
+ return mMap->getDataPtr();
+ }
+ return ensureAlignment(mMap);
+ }
+
+ assert(mFp != NULL);
+
+ if (mLength < kReadVsMapThreshold) {
+ unsigned char* buf;
+ long allocLen;
+
+ /* zero-length files are allowed; not sure about zero-len allocs */
+ /* (works fine with gcc + x86linux) */
+ allocLen = mLength;
+ if (mLength == 0)
+ allocLen = 1;
+
+ buf = new unsigned char[allocLen];
+ if (buf == NULL) {
+ ALOGE("alloc of %ld bytes failed\n", (long) allocLen);
+ return NULL;
+ }
+
+ ALOGV("Asset %p allocating buffer size %d (smaller than threshold)", this, (int)allocLen);
+ if (mLength > 0) {
+ long oldPosn = ftell(mFp);
+ fseek(mFp, mStart, SEEK_SET);
+ if (fread(buf, 1, mLength, mFp) != (size_t) mLength) {
+ ALOGE("failed reading %ld bytes\n", (long) mLength);
+ delete[] buf;
+ return NULL;
+ }
+ fseek(mFp, oldPosn, SEEK_SET);
+ }
+
+ ALOGV(" getBuffer: loaded into buffer\n");
+
+ mBuf = buf;
+ return mBuf;
+ } else {
+ FileMap* map;
+
+ map = new FileMap;
+ if (!map->create(NULL, fileno(mFp), mStart, mLength, true)) {
+ map->release();
+ return NULL;
+ }
+
+ ALOGV(" getBuffer: mapped\n");
+
+ mMap = map;
+ if (!wordAligned) {
+ return mMap->getDataPtr();
+ }
+ return ensureAlignment(mMap);
+ }
+}
+
+int _FileAsset::openFileDescriptor(off64_t* outStart, off64_t* outLength) const
+{
+ if (mMap != NULL) {
+ const char* fname = mMap->getFileName();
+ if (fname == NULL) {
+ fname = mFileName;
+ }
+ if (fname == NULL) {
+ return -1;
+ }
+ *outStart = mMap->getDataOffset();
+ *outLength = mMap->getDataLength();
+ return open(fname, O_RDONLY | O_BINARY);
+ }
+ if (mFileName == NULL) {
+ return -1;
+ }
+ *outStart = mStart;
+ *outLength = mLength;
+ return open(mFileName, O_RDONLY | O_BINARY);
+}
+
+const void* _FileAsset::ensureAlignment(FileMap* map)
+{
+ void* data = map->getDataPtr();
+ if ((((size_t)data)&0x3) == 0) {
+ // We can return this directly if it is aligned on a word
+ // boundary.
+ ALOGV("Returning aligned FileAsset %p (%s).", this,
+ getAssetSource());
+ return data;
+ }
+ // If not aligned on a word boundary, then we need to copy it into
+ // our own buffer.
+ ALOGV("Copying FileAsset %p (%s) to buffer size %d to make it aligned.", this,
+ getAssetSource(), (int)mLength);
+ unsigned char* buf = new unsigned char[mLength];
+ if (buf == NULL) {
+ ALOGE("alloc of %ld bytes failed\n", (long) mLength);
+ return NULL;
+ }
+ memcpy(buf, data, mLength);
+ mBuf = buf;
+ return buf;
+}
+
+/*
+ * ===========================================================================
+ * _CompressedAsset
+ * ===========================================================================
+ */
+
+/*
+ * Constructor.
+ */
+_CompressedAsset::_CompressedAsset(void)
+ : mStart(0), mCompressedLen(0), mUncompressedLen(0), mOffset(0),
+ mMap(NULL), mFd(-1), mZipInflater(NULL), mBuf(NULL)
+{
+}
+
+/*
+ * Destructor. Release resources.
+ */
+_CompressedAsset::~_CompressedAsset(void)
+{
+ close();
+}
+
+/*
+ * Open a chunk of compressed data inside a file.
+ *
+ * This currently just sets up some values and returns. On the first
+ * read, we expand the entire file into a buffer and return data from it.
+ */
+status_t _CompressedAsset::openChunk(int fd, off64_t offset,
+ int compressionMethod, size_t uncompressedLen, size_t compressedLen)
+{
+ assert(mFd < 0); // no re-open
+ assert(mMap == NULL);
+ assert(fd >= 0);
+ assert(offset >= 0);
+ assert(compressedLen > 0);
+
+ if (compressionMethod != ZipFileRO::kCompressDeflated) {
+ assert(false);
+ return UNKNOWN_ERROR;
+ }
+
+ mStart = offset;
+ mCompressedLen = compressedLen;
+ mUncompressedLen = uncompressedLen;
+ assert(mOffset == 0);
+ mFd = fd;
+ assert(mBuf == NULL);
+
+ if (uncompressedLen > StreamingZipInflater::OUTPUT_CHUNK_SIZE) {
+ mZipInflater = new StreamingZipInflater(mFd, offset, uncompressedLen, compressedLen);
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Open a chunk of compressed data in a mapped region.
+ *
+ * Nothing is expanded until the first read call.
+ */
+status_t _CompressedAsset::openChunk(FileMap* dataMap, int compressionMethod,
+ size_t uncompressedLen)
+{
+ assert(mFd < 0); // no re-open
+ assert(mMap == NULL);
+ assert(dataMap != NULL);
+
+ if (compressionMethod != ZipFileRO::kCompressDeflated) {
+ assert(false);
+ return UNKNOWN_ERROR;
+ }
+
+ mMap = dataMap;
+ mStart = -1; // not used
+ mCompressedLen = dataMap->getDataLength();
+ mUncompressedLen = uncompressedLen;
+ assert(mOffset == 0);
+
+ if (uncompressedLen > StreamingZipInflater::OUTPUT_CHUNK_SIZE) {
+ mZipInflater = new StreamingZipInflater(dataMap, uncompressedLen);
+ }
+ return NO_ERROR;
+}
+
+/*
+ * Read data from a chunk of compressed data.
+ *
+ * [For now, that's just copying data out of a buffer.]
+ */
+ssize_t _CompressedAsset::read(void* buf, size_t count)
+{
+ size_t maxLen;
+ size_t actual;
+
+ assert(mOffset >= 0 && mOffset <= mUncompressedLen);
+
+ /* If we're relying on a streaming inflater, go through that */
+ if (mZipInflater) {
+ actual = mZipInflater->read(buf, count);
+ } else {
+ if (mBuf == NULL) {
+ if (getBuffer(false) == NULL)
+ return -1;
+ }
+ assert(mBuf != NULL);
+
+ /* adjust count if we're near EOF */
+ maxLen = mUncompressedLen - mOffset;
+ if (count > maxLen)
+ count = maxLen;
+
+ if (!count)
+ return 0;
+
+ /* copy from buffer */
+ //printf("comp buf read\n");
+ memcpy(buf, (char*)mBuf + mOffset, count);
+ actual = count;
+ }
+
+ mOffset += actual;
+ return actual;
+}
+
+/*
+ * Handle a seek request.
+ *
+ * If we're working in a streaming mode, this is going to be fairly
+ * expensive, because it requires plowing through a bunch of compressed
+ * data.
+ */
+off64_t _CompressedAsset::seek(off64_t offset, int whence)
+{
+ off64_t newPosn;
+
+ // compute new position within chunk
+ newPosn = handleSeek(offset, whence, mOffset, mUncompressedLen);
+ if (newPosn == (off64_t) -1)
+ return newPosn;
+
+ if (mZipInflater) {
+ mZipInflater->seekAbsolute(newPosn);
+ }
+ mOffset = newPosn;
+ return mOffset;
+}
+
+/*
+ * Close the asset.
+ */
+void _CompressedAsset::close(void)
+{
+ if (mMap != NULL) {
+ mMap->release();
+ mMap = NULL;
+ }
+
+ delete[] mBuf;
+ mBuf = NULL;
+
+ delete mZipInflater;
+ mZipInflater = NULL;
+
+ if (mFd > 0) {
+ ::close(mFd);
+ mFd = -1;
+ }
+}
+
+/*
+ * Get a pointer to a read-only buffer of data.
+ *
+ * The first time this is called, we expand the compressed data into a
+ * buffer.
+ */
+const void* _CompressedAsset::getBuffer(bool wordAligned)
+{
+ unsigned char* buf = NULL;
+
+ if (mBuf != NULL)
+ return mBuf;
+
+ /*
+ * Allocate a buffer and read the file into it.
+ */
+ buf = new unsigned char[mUncompressedLen];
+ if (buf == NULL) {
+ ALOGW("alloc %ld bytes failed\n", (long) mUncompressedLen);
+ goto bail;
+ }
+
+ if (mMap != NULL) {
+ if (!ZipFileRO::inflateBuffer(buf, mMap->getDataPtr(),
+ mUncompressedLen, mCompressedLen))
+ goto bail;
+ } else {
+ assert(mFd >= 0);
+
+ /*
+ * Seek to the start of the compressed data.
+ */
+ if (lseek(mFd, mStart, SEEK_SET) != mStart)
+ goto bail;
+
+ /*
+ * Expand the data into it.
+ */
+ if (!ZipUtils::inflateToBuffer(mFd, buf, mUncompressedLen,
+ mCompressedLen))
+ goto bail;
+ }
+
+ /*
+ * Success - now that we have the full asset in RAM we
+ * no longer need the streaming inflater
+ */
+ delete mZipInflater;
+ mZipInflater = NULL;
+
+ mBuf = buf;
+ buf = NULL;
+
+bail:
+ delete[] buf;
+ return mBuf;
+}
+
diff --git a/libs/androidfw/AssetDir.cpp b/libs/androidfw/AssetDir.cpp
new file mode 100644
index 0000000..475f521
--- /dev/null
+++ b/libs/androidfw/AssetDir.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Provide access to a virtual directory in "asset space". Most of the
+// implementation is in the header file or in friend functions in
+// AssetManager.
+//
+#include <androidfw/AssetDir.h>
+
+using namespace android;
+
+
+/*
+ * Find a matching entry in a vector of FileInfo. Because it's sorted, we
+ * can use a binary search.
+ *
+ * Assumes the vector is sorted in ascending order.
+ */
+/*static*/ int AssetDir::FileInfo::findEntry(const SortedVector<FileInfo>* pVector,
+ const String8& fileName)
+{
+ FileInfo tmpInfo;
+
+ tmpInfo.setFileName(fileName);
+ return pVector->indexOf(tmpInfo);
+
+#if 0 // don't need this after all (uses 1/2 compares of SortedVector though)
+ int lo, hi, cur;
+
+ lo = 0;
+ hi = pVector->size() -1;
+ while (lo <= hi) {
+ int cmp;
+
+ cur = (hi + lo) / 2;
+ cmp = strcmp(pVector->itemAt(cur).getFileName(), fileName);
+ if (cmp == 0) {
+ /* match, bail */
+ return cur;
+ } else if (cmp < 0) {
+ /* too low */
+ lo = cur + 1;
+ } else {
+ /* too high */
+ hi = cur -1;
+ }
+ }
+
+ return -1;
+#endif
+}
+
diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp
new file mode 100644
index 0000000..6667daf
--- /dev/null
+++ b/libs/androidfw/AssetManager.cpp
@@ -0,0 +1,2034 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Provide access to read-only assets.
+//
+
+#define LOG_TAG "asset"
+#define ATRACE_TAG ATRACE_TAG_RESOURCES
+//#define LOG_NDEBUG 0
+
+#include <androidfw/Asset.h>
+#include <androidfw/AssetDir.h>
+#include <androidfw/AssetManager.h>
+#include <androidfw/misc.h>
+#include <androidfw/ResourceTypes.h>
+#include <androidfw/ZipFileRO.h>
+#include <utils/Atomic.h>
+#include <utils/Log.h>
+#include <utils/String8.h>
+#include <utils/String8.h>
+#include <utils/threads.h>
+#include <utils/Timers.h>
+#ifdef HAVE_ANDROID_OS
+#include <cutils/trace.h>
+#endif
+
+#include <assert.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <strings.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#ifndef TEMP_FAILURE_RETRY
+/* Used to retry syscalls that can return EINTR. */
+#define TEMP_FAILURE_RETRY(exp) ({ \
+ typeof (exp) _rc; \
+ do { \
+ _rc = (exp); \
+ } while (_rc == -1 && errno == EINTR); \
+ _rc; })
+#endif
+
+#ifdef HAVE_ANDROID_OS
+#define MY_TRACE_BEGIN(x) ATRACE_BEGIN(x)
+#define MY_TRACE_END() ATRACE_END()
+#else
+#define MY_TRACE_BEGIN(x)
+#define MY_TRACE_END()
+#endif
+
+using namespace android;
+
+/*
+ * Names for default app, locale, and vendor. We might want to change
+ * these to be an actual locale, e.g. always use en-US as the default.
+ */
+static const char* kDefaultLocale = "default";
+static const char* kDefaultVendor = "default";
+static const char* kAssetsRoot = "assets";
+static const char* kAppZipName = NULL; //"classes.jar";
+static const char* kSystemAssets = "framework/framework-res.apk";
+static const char* kIdmapCacheDir = "resource-cache";
+
+static const char* kExcludeExtension = ".EXCLUDE";
+
+static Asset* const kExcludedAsset = (Asset*) 0xd000000d;
+
+static volatile int32_t gCount = 0;
+
+namespace {
+ // Transform string /a/b/c.apk to /data/resource-cache/a@b@c.apk@idmap
+ String8 idmapPathForPackagePath(const String8& pkgPath)
+ {
+ const char* root = getenv("ANDROID_DATA");
+ LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_DATA not set");
+ String8 path(root);
+ path.appendPath(kIdmapCacheDir);
+
+ char buf[256]; // 256 chars should be enough for anyone...
+ strncpy(buf, pkgPath.string(), 255);
+ buf[255] = '\0';
+ char* filename = buf;
+ while (*filename && *filename == '/') {
+ ++filename;
+ }
+ char* p = filename;
+ while (*p) {
+ if (*p == '/') {
+ *p = '@';
+ }
+ ++p;
+ }
+ path.appendPath(filename);
+ path.append("@idmap");
+
+ return path;
+ }
+
+ /*
+ * Like strdup(), but uses C++ "new" operator instead of malloc.
+ */
+ static char* strdupNew(const char* str)
+ {
+ char* newStr;
+ int len;
+
+ if (str == NULL)
+ return NULL;
+
+ len = strlen(str);
+ newStr = new char[len+1];
+ memcpy(newStr, str, len+1);
+
+ return newStr;
+ }
+}
+
+/*
+ * ===========================================================================
+ * AssetManager
+ * ===========================================================================
+ */
+
+int32_t AssetManager::getGlobalCount()
+{
+ return gCount;
+}
+
+AssetManager::AssetManager(CacheMode cacheMode)
+ : mLocale(NULL), mVendor(NULL),
+ mResources(NULL), mConfig(new ResTable_config),
+ mCacheMode(cacheMode), mCacheValid(false)
+{
+ int count = android_atomic_inc(&gCount)+1;
+ //ALOGI("Creating AssetManager %p #%d\n", this, count);
+ memset(mConfig, 0, sizeof(ResTable_config));
+}
+
+AssetManager::~AssetManager(void)
+{
+ int count = android_atomic_dec(&gCount);
+ //ALOGI("Destroying AssetManager in %p #%d\n", this, count);
+
+ delete mConfig;
+ delete mResources;
+
+ // don't have a String class yet, so make sure we clean up
+ delete[] mLocale;
+ delete[] mVendor;
+}
+
+bool AssetManager::addAssetPath(const String8& path, void** cookie)
+{
+ AutoMutex _l(mLock);
+
+ asset_path ap;
+
+ String8 realPath(path);
+ if (kAppZipName) {
+ realPath.appendPath(kAppZipName);
+ }
+ ap.type = ::getFileType(realPath.string());
+ if (ap.type == kFileTypeRegular) {
+ ap.path = realPath;
+ } else {
+ ap.path = path;
+ ap.type = ::getFileType(path.string());
+ if (ap.type != kFileTypeDirectory && ap.type != kFileTypeRegular) {
+ ALOGW("Asset path %s is neither a directory nor file (type=%d).",
+ path.string(), (int)ap.type);
+ return false;
+ }
+ }
+
+ // Skip if we have it already.
+ for (size_t i=0; i<mAssetPaths.size(); i++) {
+ if (mAssetPaths[i].path == ap.path) {
+ if (cookie) {
+ *cookie = (void*)(i+1);
+ }
+ return true;
+ }
+ }
+
+ ALOGV("In %p Asset %s path: %s", this,
+ ap.type == kFileTypeDirectory ? "dir" : "zip", ap.path.string());
+
+ mAssetPaths.add(ap);
+
+ // new paths are always added at the end
+ if (cookie) {
+ *cookie = (void*)mAssetPaths.size();
+ }
+
+ // add overlay packages for /system/framework; apps are handled by the
+ // (Java) package manager
+ if (strncmp(path.string(), "/system/framework/", 18) == 0) {
+ // When there is an environment variable for /vendor, this
+ // should be changed to something similar to how ANDROID_ROOT
+ // and ANDROID_DATA are used in this file.
+ String8 overlayPath("/vendor/overlay/framework/");
+ overlayPath.append(path.getPathLeaf());
+ if (TEMP_FAILURE_RETRY(access(overlayPath.string(), R_OK)) == 0) {
+ asset_path oap;
+ oap.path = overlayPath;
+ oap.type = ::getFileType(overlayPath.string());
+ bool addOverlay = (oap.type == kFileTypeRegular); // only .apks supported as overlay
+ if (addOverlay) {
+ oap.idmap = idmapPathForPackagePath(overlayPath);
+
+ if (isIdmapStaleLocked(ap.path, oap.path, oap.idmap)) {
+ addOverlay = createIdmapFileLocked(ap.path, oap.path, oap.idmap);
+ }
+ }
+ if (addOverlay) {
+ mAssetPaths.add(oap);
+ } else {
+ ALOGW("failed to add overlay package %s\n", overlayPath.string());
+ }
+ }
+ }
+
+ return true;
+}
+
+bool AssetManager::isIdmapStaleLocked(const String8& originalPath, const String8& overlayPath,
+ const String8& idmapPath)
+{
+ struct stat st;
+ if (TEMP_FAILURE_RETRY(stat(idmapPath.string(), &st)) == -1) {
+ if (errno == ENOENT) {
+ return true; // non-existing idmap is always stale
+ } else {
+ ALOGW("failed to stat file %s: %s\n", idmapPath.string(), strerror(errno));
+ return false;
+ }
+ }
+ if (st.st_size < ResTable::IDMAP_HEADER_SIZE_BYTES) {
+ ALOGW("file %s has unexpectedly small size=%zd\n", idmapPath.string(), (size_t)st.st_size);
+ return false;
+ }
+ int fd = TEMP_FAILURE_RETRY(::open(idmapPath.string(), O_RDONLY));
+ if (fd == -1) {
+ ALOGW("failed to open file %s: %s\n", idmapPath.string(), strerror(errno));
+ return false;
+ }
+ char buf[ResTable::IDMAP_HEADER_SIZE_BYTES];
+ ssize_t bytesLeft = ResTable::IDMAP_HEADER_SIZE_BYTES;
+ for (;;) {
+ ssize_t r = TEMP_FAILURE_RETRY(read(fd, buf + ResTable::IDMAP_HEADER_SIZE_BYTES - bytesLeft,
+ bytesLeft));
+ if (r < 0) {
+ TEMP_FAILURE_RETRY(close(fd));
+ return false;
+ }
+ bytesLeft -= r;
+ if (bytesLeft == 0) {
+ break;
+ }
+ }
+ TEMP_FAILURE_RETRY(close(fd));
+
+ uint32_t cachedOriginalCrc, cachedOverlayCrc;
+ if (!ResTable::getIdmapInfo(buf, ResTable::IDMAP_HEADER_SIZE_BYTES,
+ &cachedOriginalCrc, &cachedOverlayCrc)) {
+ return false;
+ }
+
+ uint32_t actualOriginalCrc, actualOverlayCrc;
+ if (!getZipEntryCrcLocked(originalPath, "resources.arsc", &actualOriginalCrc)) {
+ return false;
+ }
+ if (!getZipEntryCrcLocked(overlayPath, "resources.arsc", &actualOverlayCrc)) {
+ return false;
+ }
+ return cachedOriginalCrc != actualOriginalCrc || cachedOverlayCrc != actualOverlayCrc;
+}
+
+bool AssetManager::getZipEntryCrcLocked(const String8& zipPath, const char* entryFilename,
+ uint32_t* pCrc)
+{
+ asset_path ap;
+ ap.path = zipPath;
+ const ZipFileRO* zip = getZipFileLocked(ap);
+ if (zip == NULL) {
+ return false;
+ }
+ const ZipEntryRO entry = zip->findEntryByName(entryFilename);
+ if (entry == NULL) {
+ return false;
+ }
+ if (!zip->getEntryInfo(entry, NULL, NULL, NULL, NULL, NULL, (long*)pCrc)) {
+ return false;
+ }
+ return true;
+}
+
+bool AssetManager::createIdmapFileLocked(const String8& originalPath, const String8& overlayPath,
+ const String8& idmapPath)
+{
+ ALOGD("%s: originalPath=%s overlayPath=%s idmapPath=%s\n",
+ __FUNCTION__, originalPath.string(), overlayPath.string(), idmapPath.string());
+ ResTable tables[2];
+ const String8* paths[2] = { &originalPath, &overlayPath };
+ uint32_t originalCrc, overlayCrc;
+ bool retval = false;
+ ssize_t offset = 0;
+ int fd = 0;
+ uint32_t* data = NULL;
+ size_t size;
+
+ for (int i = 0; i < 2; ++i) {
+ asset_path ap;
+ ap.type = kFileTypeRegular;
+ ap.path = *paths[i];
+ Asset* ass = openNonAssetInPathLocked("resources.arsc", Asset::ACCESS_BUFFER, ap);
+ if (ass == NULL) {
+ ALOGW("failed to find resources.arsc in %s\n", ap.path.string());
+ goto error;
+ }
+ tables[i].add(ass, (void*)1, false);
+ }
+
+ if (!getZipEntryCrcLocked(originalPath, "resources.arsc", &originalCrc)) {
+ ALOGW("failed to retrieve crc for resources.arsc in %s\n", originalPath.string());
+ goto error;
+ }
+ if (!getZipEntryCrcLocked(overlayPath, "resources.arsc", &overlayCrc)) {
+ ALOGW("failed to retrieve crc for resources.arsc in %s\n", overlayPath.string());
+ goto error;
+ }
+
+ if (tables[0].createIdmap(tables[1], originalCrc, overlayCrc,
+ (void**)&data, &size) != NO_ERROR) {
+ ALOGW("failed to generate idmap data for file %s\n", idmapPath.string());
+ goto error;
+ }
+
+ // This should be abstracted (eg replaced by a stand-alone
+ // application like dexopt, triggered by something equivalent to
+ // installd).
+ fd = TEMP_FAILURE_RETRY(::open(idmapPath.string(), O_WRONLY | O_CREAT | O_TRUNC, 0644));
+ if (fd == -1) {
+ ALOGW("failed to write idmap file %s (open: %s)\n", idmapPath.string(), strerror(errno));
+ goto error_free;
+ }
+ for (;;) {
+ ssize_t written = TEMP_FAILURE_RETRY(write(fd, data + offset, size));
+ if (written < 0) {
+ ALOGW("failed to write idmap file %s (write: %s)\n", idmapPath.string(),
+ strerror(errno));
+ goto error_close;
+ }
+ size -= (size_t)written;
+ offset += written;
+ if (size == 0) {
+ break;
+ }
+ }
+
+ retval = true;
+error_close:
+ TEMP_FAILURE_RETRY(close(fd));
+error_free:
+ free(data);
+error:
+ return retval;
+}
+
+bool AssetManager::addDefaultAssets()
+{
+ const char* root = getenv("ANDROID_ROOT");
+ LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_ROOT not set");
+
+ String8 path(root);
+ path.appendPath(kSystemAssets);
+
+ return addAssetPath(path, NULL);
+}
+
+void* AssetManager::nextAssetPath(void* cookie) const
+{
+ AutoMutex _l(mLock);
+ size_t next = ((size_t)cookie)+1;
+ return next > mAssetPaths.size() ? NULL : (void*)next;
+}
+
+String8 AssetManager::getAssetPath(void* cookie) const
+{
+ AutoMutex _l(mLock);
+ const size_t which = ((size_t)cookie)-1;
+ if (which < mAssetPaths.size()) {
+ return mAssetPaths[which].path;
+ }
+ return String8();
+}
+
+/*
+ * Set the current locale. Use NULL to indicate no locale.
+ *
+ * Close and reopen Zip archives as appropriate, and reset cached
+ * information in the locale-specific sections of the tree.
+ */
+void AssetManager::setLocale(const char* locale)
+{
+ AutoMutex _l(mLock);
+ setLocaleLocked(locale);
+}
+
+void AssetManager::setLocaleLocked(const char* locale)
+{
+ if (mLocale != NULL) {
+ /* previously set, purge cached data */
+ purgeFileNameCacheLocked();
+ //mZipSet.purgeLocale();
+ delete[] mLocale;
+ }
+ mLocale = strdupNew(locale);
+
+ updateResourceParamsLocked();
+}
+
+/*
+ * Set the current vendor. Use NULL to indicate no vendor.
+ *
+ * Close and reopen Zip archives as appropriate, and reset cached
+ * information in the vendor-specific sections of the tree.
+ */
+void AssetManager::setVendor(const char* vendor)
+{
+ AutoMutex _l(mLock);
+
+ if (mVendor != NULL) {
+ /* previously set, purge cached data */
+ purgeFileNameCacheLocked();
+ //mZipSet.purgeVendor();
+ delete[] mVendor;
+ }
+ mVendor = strdupNew(vendor);
+}
+
+void AssetManager::setConfiguration(const ResTable_config& config, const char* locale)
+{
+ AutoMutex _l(mLock);
+ *mConfig = config;
+ if (locale) {
+ setLocaleLocked(locale);
+ } else if (config.language[0] != 0) {
+ char spec[9];
+ spec[0] = config.language[0];
+ spec[1] = config.language[1];
+ if (config.country[0] != 0) {
+ spec[2] = '_';
+ spec[3] = config.country[0];
+ spec[4] = config.country[1];
+ spec[5] = 0;
+ } else {
+ spec[3] = 0;
+ }
+ setLocaleLocked(spec);
+ } else {
+ updateResourceParamsLocked();
+ }
+}
+
+void AssetManager::getConfiguration(ResTable_config* outConfig) const
+{
+ AutoMutex _l(mLock);
+ *outConfig = *mConfig;
+}
+
+/*
+ * Open an asset.
+ *
+ * The data could be;
+ * - In a file on disk (assetBase + fileName).
+ * - In a compressed file on disk (assetBase + fileName.gz).
+ * - In a Zip archive, uncompressed or compressed.
+ *
+ * It can be in a number of different directories and Zip archives.
+ * The search order is:
+ * - [appname]
+ * - locale + vendor
+ * - "default" + vendor
+ * - locale + "default"
+ * - "default + "default"
+ * - "common"
+ * - (same as above)
+ *
+ * To find a particular file, we have to try up to eight paths with
+ * all three forms of data.
+ *
+ * We should probably reject requests for "illegal" filenames, e.g. those
+ * with illegal characters or "../" backward relative paths.
+ */
+Asset* AssetManager::open(const char* fileName, AccessMode mode)
+{
+ AutoMutex _l(mLock);
+
+ LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager");
+
+
+ if (mCacheMode != CACHE_OFF && !mCacheValid)
+ loadFileNameCacheLocked();
+
+ String8 assetName(kAssetsRoot);
+ assetName.appendPath(fileName);
+
+ /*
+ * For each top-level asset path, search for the asset.
+ */
+
+ size_t i = mAssetPaths.size();
+ while (i > 0) {
+ i--;
+ ALOGV("Looking for asset '%s' in '%s'\n",
+ assetName.string(), mAssetPaths.itemAt(i).path.string());
+ Asset* pAsset = openNonAssetInPathLocked(assetName.string(), mode, mAssetPaths.itemAt(i));
+ if (pAsset != NULL) {
+ return pAsset != kExcludedAsset ? pAsset : NULL;
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * Open a non-asset file as if it were an asset.
+ *
+ * The "fileName" is the partial path starting from the application
+ * name.
+ */
+Asset* AssetManager::openNonAsset(const char* fileName, AccessMode mode)
+{
+ AutoMutex _l(mLock);
+
+ LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager");
+
+
+ if (mCacheMode != CACHE_OFF && !mCacheValid)
+ loadFileNameCacheLocked();
+
+ /*
+ * For each top-level asset path, search for the asset.
+ */
+
+ size_t i = mAssetPaths.size();
+ while (i > 0) {
+ i--;
+ ALOGV("Looking for non-asset '%s' in '%s'\n", fileName, mAssetPaths.itemAt(i).path.string());
+ Asset* pAsset = openNonAssetInPathLocked(
+ fileName, mode, mAssetPaths.itemAt(i));
+ if (pAsset != NULL) {
+ return pAsset != kExcludedAsset ? pAsset : NULL;
+ }
+ }
+
+ return NULL;
+}
+
+Asset* AssetManager::openNonAsset(void* cookie, const char* fileName, AccessMode mode)
+{
+ const size_t which = ((size_t)cookie)-1;
+
+ AutoMutex _l(mLock);
+
+ LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager");
+
+
+ if (mCacheMode != CACHE_OFF && !mCacheValid)
+ loadFileNameCacheLocked();
+
+ if (which < mAssetPaths.size()) {
+ ALOGV("Looking for non-asset '%s' in '%s'\n", fileName,
+ mAssetPaths.itemAt(which).path.string());
+ Asset* pAsset = openNonAssetInPathLocked(
+ fileName, mode, mAssetPaths.itemAt(which));
+ if (pAsset != NULL) {
+ return pAsset != kExcludedAsset ? pAsset : NULL;
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * Get the type of a file in the asset namespace.
+ *
+ * This currently only works for regular files. All others (including
+ * directories) will return kFileTypeNonexistent.
+ */
+FileType AssetManager::getFileType(const char* fileName)
+{
+ Asset* pAsset = NULL;
+
+ /*
+ * Open the asset. This is less efficient than simply finding the
+ * file, but it's not too bad (we don't uncompress or mmap data until
+ * the first read() call).
+ */
+ pAsset = open(fileName, Asset::ACCESS_STREAMING);
+ delete pAsset;
+
+ if (pAsset == NULL)
+ return kFileTypeNonexistent;
+ else
+ return kFileTypeRegular;
+}
+
+const ResTable* AssetManager::getResTable(bool required) const
+{
+ ResTable* rt = mResources;
+ if (rt) {
+ return rt;
+ }
+
+ // Iterate through all asset packages, collecting resources from each.
+
+ AutoMutex _l(mLock);
+
+ if (mResources != NULL) {
+ return mResources;
+ }
+
+ if (required) {
+ LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager");
+ }
+
+ if (mCacheMode != CACHE_OFF && !mCacheValid)
+ const_cast<AssetManager*>(this)->loadFileNameCacheLocked();
+
+ const size_t N = mAssetPaths.size();
+ for (size_t i=0; i<N; i++) {
+ Asset* ass = NULL;
+ ResTable* sharedRes = NULL;
+ bool shared = true;
+ const asset_path& ap = mAssetPaths.itemAt(i);
+ MY_TRACE_BEGIN(ap.path.string());
+ Asset* idmap = openIdmapLocked(ap);
+ ALOGV("Looking for resource asset in '%s'\n", ap.path.string());
+ if (ap.type != kFileTypeDirectory) {
+ if (i == 0) {
+ // The first item is typically the framework resources,
+ // which we want to avoid parsing every time.
+ sharedRes = const_cast<AssetManager*>(this)->
+ mZipSet.getZipResourceTable(ap.path);
+ }
+ if (sharedRes == NULL) {
+ ass = const_cast<AssetManager*>(this)->
+ mZipSet.getZipResourceTableAsset(ap.path);
+ if (ass == NULL) {
+ ALOGV("loading resource table %s\n", ap.path.string());
+ ass = const_cast<AssetManager*>(this)->
+ openNonAssetInPathLocked("resources.arsc",
+ Asset::ACCESS_BUFFER,
+ ap);
+ if (ass != NULL && ass != kExcludedAsset) {
+ ass = const_cast<AssetManager*>(this)->
+ mZipSet.setZipResourceTableAsset(ap.path, ass);
+ }
+ }
+
+ if (i == 0 && ass != NULL) {
+ // If this is the first resource table in the asset
+ // manager, then we are going to cache it so that we
+ // can quickly copy it out for others.
+ ALOGV("Creating shared resources for %s", ap.path.string());
+ sharedRes = new ResTable();
+ sharedRes->add(ass, (void*)(i+1), false, idmap);
+ sharedRes = const_cast<AssetManager*>(this)->
+ mZipSet.setZipResourceTable(ap.path, sharedRes);
+ }
+ }
+ } else {
+ ALOGV("loading resource table %s\n", ap.path.string());
+ Asset* ass = const_cast<AssetManager*>(this)->
+ openNonAssetInPathLocked("resources.arsc",
+ Asset::ACCESS_BUFFER,
+ ap);
+ shared = false;
+ }
+ if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) {
+ if (rt == NULL) {
+ mResources = rt = new ResTable();
+ updateResourceParamsLocked();
+ }
+ ALOGV("Installing resource asset %p in to table %p\n", ass, mResources);
+ if (sharedRes != NULL) {
+ ALOGV("Copying existing resources for %s", ap.path.string());
+ rt->add(sharedRes);
+ } else {
+ ALOGV("Parsing resources for %s", ap.path.string());
+ rt->add(ass, (void*)(i+1), !shared, idmap);
+ }
+
+ if (!shared) {
+ delete ass;
+ }
+ }
+ if (idmap != NULL) {
+ delete idmap;
+ }
+ MY_TRACE_END();
+ }
+
+ if (required && !rt) ALOGW("Unable to find resources file resources.arsc");
+ if (!rt) {
+ mResources = rt = new ResTable();
+ }
+ return rt;
+}
+
+void AssetManager::updateResourceParamsLocked() const
+{
+ ResTable* res = mResources;
+ if (!res) {
+ return;
+ }
+
+ size_t llen = mLocale ? strlen(mLocale) : 0;
+ mConfig->language[0] = 0;
+ mConfig->language[1] = 0;
+ mConfig->country[0] = 0;
+ mConfig->country[1] = 0;
+ if (llen >= 2) {
+ mConfig->language[0] = mLocale[0];
+ mConfig->language[1] = mLocale[1];
+ }
+ if (llen >= 5) {
+ mConfig->country[0] = mLocale[3];
+ mConfig->country[1] = mLocale[4];
+ }
+ mConfig->size = sizeof(*mConfig);
+
+ res->setParameters(mConfig);
+}
+
+Asset* AssetManager::openIdmapLocked(const struct asset_path& ap) const
+{
+ Asset* ass = NULL;
+ if (ap.idmap.size() != 0) {
+ ass = const_cast<AssetManager*>(this)->
+ openAssetFromFileLocked(ap.idmap, Asset::ACCESS_BUFFER);
+ if (ass) {
+ ALOGV("loading idmap %s\n", ap.idmap.string());
+ } else {
+ ALOGW("failed to load idmap %s\n", ap.idmap.string());
+ }
+ }
+ return ass;
+}
+
+const ResTable& AssetManager::getResources(bool required) const
+{
+ const ResTable* rt = getResTable(required);
+ return *rt;
+}
+
+bool AssetManager::isUpToDate()
+{
+ AutoMutex _l(mLock);
+ return mZipSet.isUpToDate();
+}
+
+void AssetManager::getLocales(Vector<String8>* locales) const
+{
+ ResTable* res = mResources;
+ if (res != NULL) {
+ res->getLocales(locales);
+ }
+}
+
+/*
+ * Open a non-asset file as if it were an asset, searching for it in the
+ * specified app.
+ *
+ * Pass in a NULL values for "appName" if the common app directory should
+ * be used.
+ */
+Asset* AssetManager::openNonAssetInPathLocked(const char* fileName, AccessMode mode,
+ const asset_path& ap)
+{
+ Asset* pAsset = NULL;
+
+ /* look at the filesystem on disk */
+ if (ap.type == kFileTypeDirectory) {
+ String8 path(ap.path);
+ path.appendPath(fileName);
+
+ pAsset = openAssetFromFileLocked(path, mode);
+
+ if (pAsset == NULL) {
+ /* try again, this time with ".gz" */
+ path.append(".gz");
+ pAsset = openAssetFromFileLocked(path, mode);
+ }
+
+ if (pAsset != NULL) {
+ //printf("FOUND NA '%s' on disk\n", fileName);
+ pAsset->setAssetSource(path);
+ }
+
+ /* look inside the zip file */
+ } else {
+ String8 path(fileName);
+
+ /* check the appropriate Zip file */
+ ZipFileRO* pZip;
+ ZipEntryRO entry;
+
+ pZip = getZipFileLocked(ap);
+ if (pZip != NULL) {
+ //printf("GOT zip, checking NA '%s'\n", (const char*) path);
+ entry = pZip->findEntryByName(path.string());
+ if (entry != NULL) {
+ //printf("FOUND NA in Zip file for %s\n", appName ? appName : kAppCommon);
+ pAsset = openAssetFromZipLocked(pZip, entry, mode, path);
+ }
+ }
+
+ if (pAsset != NULL) {
+ /* create a "source" name, for debug/display */
+ pAsset->setAssetSource(
+ createZipSourceNameLocked(ZipSet::getPathName(ap.path.string()), String8(""),
+ String8(fileName)));
+ }
+ }
+
+ return pAsset;
+}
+
+/*
+ * Open an asset, searching for it in the directory hierarchy for the
+ * specified app.
+ *
+ * Pass in a NULL values for "appName" if the common app directory should
+ * be used.
+ */
+Asset* AssetManager::openInPathLocked(const char* fileName, AccessMode mode,
+ const asset_path& ap)
+{
+ Asset* pAsset = NULL;
+
+ /*
+ * Try various combinations of locale and vendor.
+ */
+ if (mLocale != NULL && mVendor != NULL)
+ pAsset = openInLocaleVendorLocked(fileName, mode, ap, mLocale, mVendor);
+ if (pAsset == NULL && mVendor != NULL)
+ pAsset = openInLocaleVendorLocked(fileName, mode, ap, NULL, mVendor);
+ if (pAsset == NULL && mLocale != NULL)
+ pAsset = openInLocaleVendorLocked(fileName, mode, ap, mLocale, NULL);
+ if (pAsset == NULL)
+ pAsset = openInLocaleVendorLocked(fileName, mode, ap, NULL, NULL);
+
+ return pAsset;
+}
+
+/*
+ * Open an asset, searching for it in the directory hierarchy for the
+ * specified locale and vendor.
+ *
+ * We also search in "app.jar".
+ *
+ * Pass in NULL values for "appName", "locale", and "vendor" if the
+ * defaults should be used.
+ */
+Asset* AssetManager::openInLocaleVendorLocked(const char* fileName, AccessMode mode,
+ const asset_path& ap, const char* locale, const char* vendor)
+{
+ Asset* pAsset = NULL;
+
+ if (ap.type == kFileTypeDirectory) {
+ if (mCacheMode == CACHE_OFF) {
+ /* look at the filesystem on disk */
+ String8 path(createPathNameLocked(ap, locale, vendor));
+ path.appendPath(fileName);
+
+ String8 excludeName(path);
+ excludeName.append(kExcludeExtension);
+ if (::getFileType(excludeName.string()) != kFileTypeNonexistent) {
+ /* say no more */
+ //printf("+++ excluding '%s'\n", (const char*) excludeName);
+ return kExcludedAsset;
+ }
+
+ pAsset = openAssetFromFileLocked(path, mode);
+
+ if (pAsset == NULL) {
+ /* try again, this time with ".gz" */
+ path.append(".gz");
+ pAsset = openAssetFromFileLocked(path, mode);
+ }
+
+ if (pAsset != NULL)
+ pAsset->setAssetSource(path);
+ } else {
+ /* find in cache */
+ String8 path(createPathNameLocked(ap, locale, vendor));
+ path.appendPath(fileName);
+
+ AssetDir::FileInfo tmpInfo;
+ bool found = false;
+
+ String8 excludeName(path);
+ excludeName.append(kExcludeExtension);
+
+ if (mCache.indexOf(excludeName) != NAME_NOT_FOUND) {
+ /* go no farther */
+ //printf("+++ Excluding '%s'\n", (const char*) excludeName);
+ return kExcludedAsset;
+ }
+
+ /*
+ * File compression extensions (".gz") don't get stored in the
+ * name cache, so we have to try both here.
+ */
+ if (mCache.indexOf(path) != NAME_NOT_FOUND) {
+ found = true;
+ pAsset = openAssetFromFileLocked(path, mode);
+ if (pAsset == NULL) {
+ /* try again, this time with ".gz" */
+ path.append(".gz");
+ pAsset = openAssetFromFileLocked(path, mode);
+ }
+ }
+
+ if (pAsset != NULL)
+ pAsset->setAssetSource(path);
+
+ /*
+ * Don't continue the search into the Zip files. Our cached info
+ * said it was a file on disk; to be consistent with openDir()
+ * we want to return the loose asset. If the cached file gets
+ * removed, we fail.
+ *
+ * The alternative is to update our cache when files get deleted,
+ * or make some sort of "best effort" promise, but for now I'm
+ * taking the hard line.
+ */
+ if (found) {
+ if (pAsset == NULL)
+ ALOGD("Expected file not found: '%s'\n", path.string());
+ return pAsset;
+ }
+ }
+ }
+
+ /*
+ * Either it wasn't found on disk or on the cached view of the disk.
+ * Dig through the currently-opened set of Zip files. If caching
+ * is disabled, the Zip file may get reopened.
+ */
+ if (pAsset == NULL && ap.type == kFileTypeRegular) {
+ String8 path;
+
+ path.appendPath((locale != NULL) ? locale : kDefaultLocale);
+ path.appendPath((vendor != NULL) ? vendor : kDefaultVendor);
+ path.appendPath(fileName);
+
+ /* check the appropriate Zip file */
+ ZipFileRO* pZip;
+ ZipEntryRO entry;
+
+ pZip = getZipFileLocked(ap);
+ if (pZip != NULL) {
+ //printf("GOT zip, checking '%s'\n", (const char*) path);
+ entry = pZip->findEntryByName(path.string());
+ if (entry != NULL) {
+ //printf("FOUND in Zip file for %s/%s-%s\n",
+ // appName, locale, vendor);
+ pAsset = openAssetFromZipLocked(pZip, entry, mode, path);
+ }
+ }
+
+ if (pAsset != NULL) {
+ /* create a "source" name, for debug/display */
+ pAsset->setAssetSource(createZipSourceNameLocked(ZipSet::getPathName(ap.path.string()),
+ String8(""), String8(fileName)));
+ }
+ }
+
+ return pAsset;
+}
+
+/*
+ * Create a "source name" for a file from a Zip archive.
+ */
+String8 AssetManager::createZipSourceNameLocked(const String8& zipFileName,
+ const String8& dirName, const String8& fileName)
+{
+ String8 sourceName("zip:");
+ sourceName.append(zipFileName);
+ sourceName.append(":");
+ if (dirName.length() > 0) {
+ sourceName.appendPath(dirName);
+ }
+ sourceName.appendPath(fileName);
+ return sourceName;
+}
+
+/*
+ * Create a path to a loose asset (asset-base/app/locale/vendor).
+ */
+String8 AssetManager::createPathNameLocked(const asset_path& ap, const char* locale,
+ const char* vendor)
+{
+ String8 path(ap.path);
+ path.appendPath((locale != NULL) ? locale : kDefaultLocale);
+ path.appendPath((vendor != NULL) ? vendor : kDefaultVendor);
+ return path;
+}
+
+/*
+ * Create a path to a loose asset (asset-base/app/rootDir).
+ */
+String8 AssetManager::createPathNameLocked(const asset_path& ap, const char* rootDir)
+{
+ String8 path(ap.path);
+ if (rootDir != NULL) path.appendPath(rootDir);
+ return path;
+}
+
+/*
+ * Return a pointer to one of our open Zip archives. Returns NULL if no
+ * matching Zip file exists.
+ *
+ * Right now we have 2 possible Zip files (1 each in app/"common").
+ *
+ * If caching is set to CACHE_OFF, to get the expected behavior we
+ * need to reopen the Zip file on every request. That would be silly
+ * and expensive, so instead we just check the file modification date.
+ *
+ * Pass in NULL values for "appName", "locale", and "vendor" if the
+ * generics should be used.
+ */
+ZipFileRO* AssetManager::getZipFileLocked(const asset_path& ap)
+{
+ ALOGV("getZipFileLocked() in %p\n", this);
+
+ return mZipSet.getZip(ap.path);
+}
+
+/*
+ * Try to open an asset from a file on disk.
+ *
+ * If the file is compressed with gzip, we seek to the start of the
+ * deflated data and pass that in (just like we would for a Zip archive).
+ *
+ * For uncompressed data, we may already have an mmap()ed version sitting
+ * around. If so, we want to hand that to the Asset instead.
+ *
+ * This returns NULL if the file doesn't exist, couldn't be opened, or
+ * claims to be a ".gz" but isn't.
+ */
+Asset* AssetManager::openAssetFromFileLocked(const String8& pathName,
+ AccessMode mode)
+{
+ Asset* pAsset = NULL;
+
+ if (strcasecmp(pathName.getPathExtension().string(), ".gz") == 0) {
+ //printf("TRYING '%s'\n", (const char*) pathName);
+ pAsset = Asset::createFromCompressedFile(pathName.string(), mode);
+ } else {
+ //printf("TRYING '%s'\n", (const char*) pathName);
+ pAsset = Asset::createFromFile(pathName.string(), mode);
+ }
+
+ return pAsset;
+}
+
+/*
+ * Given an entry in a Zip archive, create a new Asset object.
+ *
+ * If the entry is uncompressed, we may want to create or share a
+ * slice of shared memory.
+ */
+Asset* AssetManager::openAssetFromZipLocked(const ZipFileRO* pZipFile,
+ const ZipEntryRO entry, AccessMode mode, const String8& entryName)
+{
+ Asset* pAsset = NULL;
+
+ // TODO: look for previously-created shared memory slice?
+ int method;
+ size_t uncompressedLen;
+
+ //printf("USING Zip '%s'\n", pEntry->getFileName());
+
+ //pZipFile->getEntryInfo(entry, &method, &uncompressedLen, &compressedLen,
+ // &offset);
+ if (!pZipFile->getEntryInfo(entry, &method, &uncompressedLen, NULL, NULL,
+ NULL, NULL))
+ {
+ ALOGW("getEntryInfo failed\n");
+ return NULL;
+ }
+
+ FileMap* dataMap = pZipFile->createEntryFileMap(entry);
+ if (dataMap == NULL) {
+ ALOGW("create map from entry failed\n");
+ return NULL;
+ }
+
+ if (method == ZipFileRO::kCompressStored) {
+ pAsset = Asset::createFromUncompressedMap(dataMap, mode);
+ ALOGV("Opened uncompressed entry %s in zip %s mode %d: %p", entryName.string(),
+ dataMap->getFileName(), mode, pAsset);
+ } else {
+ pAsset = Asset::createFromCompressedMap(dataMap, method,
+ uncompressedLen, mode);
+ ALOGV("Opened compressed entry %s in zip %s mode %d: %p", entryName.string(),
+ dataMap->getFileName(), mode, pAsset);
+ }
+ if (pAsset == NULL) {
+ /* unexpected */
+ ALOGW("create from segment failed\n");
+ }
+
+ return pAsset;
+}
+
+
+
+/*
+ * Open a directory in the asset namespace.
+ *
+ * An "asset directory" is simply the combination of all files in all
+ * locations, with ".gz" stripped for loose files. With app, locale, and
+ * vendor defined, we have 8 directories and 2 Zip archives to scan.
+ *
+ * Pass in "" for the root dir.
+ */
+AssetDir* AssetManager::openDir(const char* dirName)
+{
+ AutoMutex _l(mLock);
+
+ AssetDir* pDir = NULL;
+ SortedVector<AssetDir::FileInfo>* pMergedInfo = NULL;
+
+ LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager");
+ assert(dirName != NULL);
+
+ //printf("+++ openDir(%s) in '%s'\n", dirName, (const char*) mAssetBase);
+
+ if (mCacheMode != CACHE_OFF && !mCacheValid)
+ loadFileNameCacheLocked();
+
+ pDir = new AssetDir;
+
+ /*
+ * Scan the various directories, merging what we find into a single
+ * vector. We want to scan them in reverse priority order so that
+ * the ".EXCLUDE" processing works correctly. Also, if we decide we
+ * want to remember where the file is coming from, we'll get the right
+ * version.
+ *
+ * We start with Zip archives, then do loose files.
+ */
+ pMergedInfo = new SortedVector<AssetDir::FileInfo>;
+
+ size_t i = mAssetPaths.size();
+ while (i > 0) {
+ i--;
+ const asset_path& ap = mAssetPaths.itemAt(i);
+ if (ap.type == kFileTypeRegular) {
+ ALOGV("Adding directory %s from zip %s", dirName, ap.path.string());
+ scanAndMergeZipLocked(pMergedInfo, ap, kAssetsRoot, dirName);
+ } else {
+ ALOGV("Adding directory %s from dir %s", dirName, ap.path.string());
+ scanAndMergeDirLocked(pMergedInfo, ap, kAssetsRoot, dirName);
+ }
+ }
+
+#if 0
+ printf("FILE LIST:\n");
+ for (i = 0; i < (size_t) pMergedInfo->size(); i++) {
+ printf(" %d: (%d) '%s'\n", i,
+ pMergedInfo->itemAt(i).getFileType(),
+ (const char*) pMergedInfo->itemAt(i).getFileName());
+ }
+#endif
+
+ pDir->setFileList(pMergedInfo);
+ return pDir;
+}
+
+/*
+ * Open a directory in the non-asset namespace.
+ *
+ * An "asset directory" is simply the combination of all files in all
+ * locations, with ".gz" stripped for loose files. With app, locale, and
+ * vendor defined, we have 8 directories and 2 Zip archives to scan.
+ *
+ * Pass in "" for the root dir.
+ */
+AssetDir* AssetManager::openNonAssetDir(void* cookie, const char* dirName)
+{
+ AutoMutex _l(mLock);
+
+ AssetDir* pDir = NULL;
+ SortedVector<AssetDir::FileInfo>* pMergedInfo = NULL;
+
+ LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager");
+ assert(dirName != NULL);
+
+ //printf("+++ openDir(%s) in '%s'\n", dirName, (const char*) mAssetBase);
+
+ if (mCacheMode != CACHE_OFF && !mCacheValid)
+ loadFileNameCacheLocked();
+
+ pDir = new AssetDir;
+
+ pMergedInfo = new SortedVector<AssetDir::FileInfo>;
+
+ const size_t which = ((size_t)cookie)-1;
+
+ if (which < mAssetPaths.size()) {
+ const asset_path& ap = mAssetPaths.itemAt(which);
+ if (ap.type == kFileTypeRegular) {
+ ALOGV("Adding directory %s from zip %s", dirName, ap.path.string());
+ scanAndMergeZipLocked(pMergedInfo, ap, NULL, dirName);
+ } else {
+ ALOGV("Adding directory %s from dir %s", dirName, ap.path.string());
+ scanAndMergeDirLocked(pMergedInfo, ap, NULL, dirName);
+ }
+ }
+
+#if 0
+ printf("FILE LIST:\n");
+ for (i = 0; i < (size_t) pMergedInfo->size(); i++) {
+ printf(" %d: (%d) '%s'\n", i,
+ pMergedInfo->itemAt(i).getFileType(),
+ (const char*) pMergedInfo->itemAt(i).getFileName());
+ }
+#endif
+
+ pDir->setFileList(pMergedInfo);
+ return pDir;
+}
+
+/*
+ * Scan the contents of the specified directory and merge them into the
+ * "pMergedInfo" vector, removing previous entries if we find "exclude"
+ * directives.
+ *
+ * Returns "false" if we found nothing to contribute.
+ */
+bool AssetManager::scanAndMergeDirLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo,
+ const asset_path& ap, const char* rootDir, const char* dirName)
+{
+ SortedVector<AssetDir::FileInfo>* pContents;
+ String8 path;
+
+ assert(pMergedInfo != NULL);
+
+ //printf("scanAndMergeDir: %s %s %s %s\n", appName, locale, vendor,dirName);
+
+ if (mCacheValid) {
+ int i, start, count;
+
+ pContents = new SortedVector<AssetDir::FileInfo>;
+
+ /*
+ * Get the basic partial path and find it in the cache. That's
+ * the start point for the search.
+ */
+ path = createPathNameLocked(ap, rootDir);
+ if (dirName[0] != '\0')
+ path.appendPath(dirName);
+
+ start = mCache.indexOf(path);
+ if (start == NAME_NOT_FOUND) {
+ //printf("+++ not found in cache: dir '%s'\n", (const char*) path);
+ delete pContents;
+ return false;
+ }
+
+ /*
+ * The match string looks like "common/default/default/foo/bar/".
+ * The '/' on the end ensures that we don't match on the directory
+ * itself or on ".../foo/barfy/".
+ */
+ path.append("/");
+
+ count = mCache.size();
+
+ /*
+ * Pick out the stuff in the current dir by examining the pathname.
+ * It needs to match the partial pathname prefix, and not have a '/'
+ * (fssep) anywhere after the prefix.
+ */
+ for (i = start+1; i < count; i++) {
+ if (mCache[i].getFileName().length() > path.length() &&
+ strncmp(mCache[i].getFileName().string(), path.string(), path.length()) == 0)
+ {
+ const char* name = mCache[i].getFileName().string();
+ // XXX THIS IS BROKEN! Looks like we need to store the full
+ // path prefix separately from the file path.
+ if (strchr(name + path.length(), '/') == NULL) {
+ /* grab it, reducing path to just the filename component */
+ AssetDir::FileInfo tmp = mCache[i];
+ tmp.setFileName(tmp.getFileName().getPathLeaf());
+ pContents->add(tmp);
+ }
+ } else {
+ /* no longer in the dir or its subdirs */
+ break;
+ }
+
+ }
+ } else {
+ path = createPathNameLocked(ap, rootDir);
+ if (dirName[0] != '\0')
+ path.appendPath(dirName);
+ pContents = scanDirLocked(path);
+ if (pContents == NULL)
+ return false;
+ }
+
+ // if we wanted to do an incremental cache fill, we would do it here
+
+ /*
+ * Process "exclude" directives. If we find a filename that ends with
+ * ".EXCLUDE", we look for a matching entry in the "merged" set, and
+ * remove it if we find it. We also delete the "exclude" entry.
+ */
+ int i, count, exclExtLen;
+
+ count = pContents->size();
+ exclExtLen = strlen(kExcludeExtension);
+ for (i = 0; i < count; i++) {
+ const char* name;
+ int nameLen;
+
+ name = pContents->itemAt(i).getFileName().string();
+ nameLen = strlen(name);
+ if (nameLen > exclExtLen &&
+ strcmp(name + (nameLen - exclExtLen), kExcludeExtension) == 0)
+ {
+ String8 match(name, nameLen - exclExtLen);
+ int matchIdx;
+
+ matchIdx = AssetDir::FileInfo::findEntry(pMergedInfo, match);
+ if (matchIdx > 0) {
+ ALOGV("Excluding '%s' [%s]\n",
+ pMergedInfo->itemAt(matchIdx).getFileName().string(),
+ pMergedInfo->itemAt(matchIdx).getSourceName().string());
+ pMergedInfo->removeAt(matchIdx);
+ } else {
+ //printf("+++ no match on '%s'\n", (const char*) match);
+ }
+
+ ALOGD("HEY: size=%d removing %d\n", (int)pContents->size(), i);
+ pContents->removeAt(i);
+ i--; // adjust "for" loop
+ count--; // and loop limit
+ }
+ }
+
+ mergeInfoLocked(pMergedInfo, pContents);
+
+ delete pContents;
+
+ return true;
+}
+
+/*
+ * Scan the contents of the specified directory, and stuff what we find
+ * into a newly-allocated vector.
+ *
+ * Files ending in ".gz" will have their extensions removed.
+ *
+ * We should probably think about skipping files with "illegal" names,
+ * e.g. illegal characters (/\:) or excessive length.
+ *
+ * Returns NULL if the specified directory doesn't exist.
+ */
+SortedVector<AssetDir::FileInfo>* AssetManager::scanDirLocked(const String8& path)
+{
+ SortedVector<AssetDir::FileInfo>* pContents = NULL;
+ DIR* dir;
+ struct dirent* entry;
+ FileType fileType;
+
+ ALOGV("Scanning dir '%s'\n", path.string());
+
+ dir = opendir(path.string());
+ if (dir == NULL)
+ return NULL;
+
+ pContents = new SortedVector<AssetDir::FileInfo>;
+
+ while (1) {
+ entry = readdir(dir);
+ if (entry == NULL)
+ break;
+
+ if (strcmp(entry->d_name, ".") == 0 ||
+ strcmp(entry->d_name, "..") == 0)
+ continue;
+
+#ifdef _DIRENT_HAVE_D_TYPE
+ if (entry->d_type == DT_REG)
+ fileType = kFileTypeRegular;
+ else if (entry->d_type == DT_DIR)
+ fileType = kFileTypeDirectory;
+ else
+ fileType = kFileTypeUnknown;
+#else
+ // stat the file
+ fileType = ::getFileType(path.appendPathCopy(entry->d_name).string());
+#endif
+
+ if (fileType != kFileTypeRegular && fileType != kFileTypeDirectory)
+ continue;
+
+ AssetDir::FileInfo info;
+ info.set(String8(entry->d_name), fileType);
+ if (strcasecmp(info.getFileName().getPathExtension().string(), ".gz") == 0)
+ info.setFileName(info.getFileName().getBasePath());
+ info.setSourceName(path.appendPathCopy(info.getFileName()));
+ pContents->add(info);
+ }
+
+ closedir(dir);
+ return pContents;
+}
+
+/*
+ * Scan the contents out of the specified Zip archive, and merge what we
+ * find into "pMergedInfo". If the Zip archive in question doesn't exist,
+ * we return immediately.
+ *
+ * Returns "false" if we found nothing to contribute.
+ */
+bool AssetManager::scanAndMergeZipLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo,
+ const asset_path& ap, const char* rootDir, const char* baseDirName)
+{
+ ZipFileRO* pZip;
+ Vector<String8> dirs;
+ AssetDir::FileInfo info;
+ SortedVector<AssetDir::FileInfo> contents;
+ String8 sourceName, zipName, dirName;
+
+ pZip = mZipSet.getZip(ap.path);
+ if (pZip == NULL) {
+ ALOGW("Failure opening zip %s\n", ap.path.string());
+ return false;
+ }
+
+ zipName = ZipSet::getPathName(ap.path.string());
+
+ /* convert "sounds" to "rootDir/sounds" */
+ if (rootDir != NULL) dirName = rootDir;
+ dirName.appendPath(baseDirName);
+
+ /*
+ * Scan through the list of files, looking for a match. The files in
+ * the Zip table of contents are not in sorted order, so we have to
+ * process the entire list. We're looking for a string that begins
+ * with the characters in "dirName", is followed by a '/', and has no
+ * subsequent '/' in the stuff that follows.
+ *
+ * What makes this especially fun is that directories are not stored
+ * explicitly in Zip archives, so we have to infer them from context.
+ * When we see "sounds/foo.wav" we have to leave a note to ourselves
+ * to insert a directory called "sounds" into the list. We store
+ * these in temporary vector so that we only return each one once.
+ *
+ * Name comparisons are case-sensitive to match UNIX filesystem
+ * semantics.
+ */
+ int dirNameLen = dirName.length();
+ for (int i = 0; i < pZip->getNumEntries(); i++) {
+ ZipEntryRO entry;
+ char nameBuf[256];
+
+ entry = pZip->findEntryByIndex(i);
+ if (pZip->getEntryFileName(entry, nameBuf, sizeof(nameBuf)) != 0) {
+ // TODO: fix this if we expect to have long names
+ ALOGE("ARGH: name too long?\n");
+ continue;
+ }
+ //printf("Comparing %s in %s?\n", nameBuf, dirName.string());
+ if (dirNameLen == 0 ||
+ (strncmp(nameBuf, dirName.string(), dirNameLen) == 0 &&
+ nameBuf[dirNameLen] == '/'))
+ {
+ const char* cp;
+ const char* nextSlash;
+
+ cp = nameBuf + dirNameLen;
+ if (dirNameLen != 0)
+ cp++; // advance past the '/'
+
+ nextSlash = strchr(cp, '/');
+//xxx this may break if there are bare directory entries
+ if (nextSlash == NULL) {
+ /* this is a file in the requested directory */
+
+ info.set(String8(nameBuf).getPathLeaf(), kFileTypeRegular);
+
+ info.setSourceName(
+ createZipSourceNameLocked(zipName, dirName, info.getFileName()));
+
+ contents.add(info);
+ //printf("FOUND: file '%s'\n", info.getFileName().string());
+ } else {
+ /* this is a subdir; add it if we don't already have it*/
+ String8 subdirName(cp, nextSlash - cp);
+ size_t j;
+ size_t N = dirs.size();
+
+ for (j = 0; j < N; j++) {
+ if (subdirName == dirs[j]) {
+ break;
+ }
+ }
+ if (j == N) {
+ dirs.add(subdirName);
+ }
+
+ //printf("FOUND: dir '%s'\n", subdirName.string());
+ }
+ }
+ }
+
+ /*
+ * Add the set of unique directories.
+ */
+ for (int i = 0; i < (int) dirs.size(); i++) {
+ info.set(dirs[i], kFileTypeDirectory);
+ info.setSourceName(
+ createZipSourceNameLocked(zipName, dirName, info.getFileName()));
+ contents.add(info);
+ }
+
+ mergeInfoLocked(pMergedInfo, &contents);
+
+ return true;
+}
+
+
+/*
+ * Merge two vectors of FileInfo.
+ *
+ * The merged contents will be stuffed into *pMergedInfo.
+ *
+ * If an entry for a file exists in both "pMergedInfo" and "pContents",
+ * we use the newer "pContents" entry.
+ */
+void AssetManager::mergeInfoLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo,
+ const SortedVector<AssetDir::FileInfo>* pContents)
+{
+ /*
+ * Merge what we found in this directory with what we found in
+ * other places.
+ *
+ * Two basic approaches:
+ * (1) Create a new array that holds the unique values of the two
+ * arrays.
+ * (2) Take the elements from pContents and shove them into pMergedInfo.
+ *
+ * Because these are vectors of complex objects, moving elements around
+ * inside the vector requires constructing new objects and allocating
+ * storage for members. With approach #1, we're always adding to the
+ * end, whereas with #2 we could be inserting multiple elements at the
+ * front of the vector. Approach #1 requires a full copy of the
+ * contents of pMergedInfo, but approach #2 requires the same copy for
+ * every insertion at the front of pMergedInfo.
+ *
+ * (We should probably use a SortedVector interface that allows us to
+ * just stuff items in, trusting us to maintain the sort order.)
+ */
+ SortedVector<AssetDir::FileInfo>* pNewSorted;
+ int mergeMax, contMax;
+ int mergeIdx, contIdx;
+
+ pNewSorted = new SortedVector<AssetDir::FileInfo>;
+ mergeMax = pMergedInfo->size();
+ contMax = pContents->size();
+ mergeIdx = contIdx = 0;
+
+ while (mergeIdx < mergeMax || contIdx < contMax) {
+ if (mergeIdx == mergeMax) {
+ /* hit end of "merge" list, copy rest of "contents" */
+ pNewSorted->add(pContents->itemAt(contIdx));
+ contIdx++;
+ } else if (contIdx == contMax) {
+ /* hit end of "cont" list, copy rest of "merge" */
+ pNewSorted->add(pMergedInfo->itemAt(mergeIdx));
+ mergeIdx++;
+ } else if (pMergedInfo->itemAt(mergeIdx) == pContents->itemAt(contIdx))
+ {
+ /* items are identical, add newer and advance both indices */
+ pNewSorted->add(pContents->itemAt(contIdx));
+ mergeIdx++;
+ contIdx++;
+ } else if (pMergedInfo->itemAt(mergeIdx) < pContents->itemAt(contIdx))
+ {
+ /* "merge" is lower, add that one */
+ pNewSorted->add(pMergedInfo->itemAt(mergeIdx));
+ mergeIdx++;
+ } else {
+ /* "cont" is lower, add that one */
+ assert(pContents->itemAt(contIdx) < pMergedInfo->itemAt(mergeIdx));
+ pNewSorted->add(pContents->itemAt(contIdx));
+ contIdx++;
+ }
+ }
+
+ /*
+ * Overwrite the "merged" list with the new stuff.
+ */
+ *pMergedInfo = *pNewSorted;
+ delete pNewSorted;
+
+#if 0 // for Vector, rather than SortedVector
+ int i, j;
+ for (i = pContents->size() -1; i >= 0; i--) {
+ bool add = true;
+
+ for (j = pMergedInfo->size() -1; j >= 0; j--) {
+ /* case-sensitive comparisons, to behave like UNIX fs */
+ if (strcmp(pContents->itemAt(i).mFileName,
+ pMergedInfo->itemAt(j).mFileName) == 0)
+ {
+ /* match, don't add this entry */
+ add = false;
+ break;
+ }
+ }
+
+ if (add)
+ pMergedInfo->add(pContents->itemAt(i));
+ }
+#endif
+}
+
+
+/*
+ * Load all files into the file name cache. We want to do this across
+ * all combinations of { appname, locale, vendor }, performing a recursive
+ * directory traversal.
+ *
+ * This is not the most efficient data structure. Also, gathering the
+ * information as we needed it (file-by-file or directory-by-directory)
+ * would be faster. However, on the actual device, 99% of the files will
+ * live in Zip archives, so this list will be very small. The trouble
+ * is that we have to check the "loose" files first, so it's important
+ * that we don't beat the filesystem silly looking for files that aren't
+ * there.
+ *
+ * Note on thread safety: this is the only function that causes updates
+ * to mCache, and anybody who tries to use it will call here if !mCacheValid,
+ * so we need to employ a mutex here.
+ */
+void AssetManager::loadFileNameCacheLocked(void)
+{
+ assert(!mCacheValid);
+ assert(mCache.size() == 0);
+
+#ifdef DO_TIMINGS // need to link against -lrt for this now
+ DurationTimer timer;
+ timer.start();
+#endif
+
+ fncScanLocked(&mCache, "");
+
+#ifdef DO_TIMINGS
+ timer.stop();
+ ALOGD("Cache scan took %.3fms\n",
+ timer.durationUsecs() / 1000.0);
+#endif
+
+#if 0
+ int i;
+ printf("CACHED FILE LIST (%d entries):\n", mCache.size());
+ for (i = 0; i < (int) mCache.size(); i++) {
+ printf(" %d: (%d) '%s'\n", i,
+ mCache.itemAt(i).getFileType(),
+ (const char*) mCache.itemAt(i).getFileName());
+ }
+#endif
+
+ mCacheValid = true;
+}
+
+/*
+ * Scan up to 8 versions of the specified directory.
+ */
+void AssetManager::fncScanLocked(SortedVector<AssetDir::FileInfo>* pMergedInfo,
+ const char* dirName)
+{
+ size_t i = mAssetPaths.size();
+ while (i > 0) {
+ i--;
+ const asset_path& ap = mAssetPaths.itemAt(i);
+ fncScanAndMergeDirLocked(pMergedInfo, ap, NULL, NULL, dirName);
+ if (mLocale != NULL)
+ fncScanAndMergeDirLocked(pMergedInfo, ap, mLocale, NULL, dirName);
+ if (mVendor != NULL)
+ fncScanAndMergeDirLocked(pMergedInfo, ap, NULL, mVendor, dirName);
+ if (mLocale != NULL && mVendor != NULL)
+ fncScanAndMergeDirLocked(pMergedInfo, ap, mLocale, mVendor, dirName);
+ }
+}
+
+/*
+ * Recursively scan this directory and all subdirs.
+ *
+ * This is similar to scanAndMergeDir, but we don't remove the .EXCLUDE
+ * files, and we prepend the extended partial path to the filenames.
+ */
+bool AssetManager::fncScanAndMergeDirLocked(
+ SortedVector<AssetDir::FileInfo>* pMergedInfo,
+ const asset_path& ap, const char* locale, const char* vendor,
+ const char* dirName)
+{
+ SortedVector<AssetDir::FileInfo>* pContents;
+ String8 partialPath;
+ String8 fullPath;
+
+ // XXX This is broken -- the filename cache needs to hold the base
+ // asset path separately from its filename.
+
+ partialPath = createPathNameLocked(ap, locale, vendor);
+ if (dirName[0] != '\0') {
+ partialPath.appendPath(dirName);
+ }
+
+ fullPath = partialPath;
+ pContents = scanDirLocked(fullPath);
+ if (pContents == NULL) {
+ return false; // directory did not exist
+ }
+
+ /*
+ * Scan all subdirectories of the current dir, merging what we find
+ * into "pMergedInfo".
+ */
+ for (int i = 0; i < (int) pContents->size(); i++) {
+ if (pContents->itemAt(i).getFileType() == kFileTypeDirectory) {
+ String8 subdir(dirName);
+ subdir.appendPath(pContents->itemAt(i).getFileName());
+
+ fncScanAndMergeDirLocked(pMergedInfo, ap, locale, vendor, subdir.string());
+ }
+ }
+
+ /*
+ * To be consistent, we want entries for the root directory. If
+ * we're the root, add one now.
+ */
+ if (dirName[0] == '\0') {
+ AssetDir::FileInfo tmpInfo;
+
+ tmpInfo.set(String8(""), kFileTypeDirectory);
+ tmpInfo.setSourceName(createPathNameLocked(ap, locale, vendor));
+ pContents->add(tmpInfo);
+ }
+
+ /*
+ * We want to prepend the extended partial path to every entry in
+ * "pContents". It's the same value for each entry, so this will
+ * not change the sorting order of the vector contents.
+ */
+ for (int i = 0; i < (int) pContents->size(); i++) {
+ const AssetDir::FileInfo& info = pContents->itemAt(i);
+ pContents->editItemAt(i).setFileName(partialPath.appendPathCopy(info.getFileName()));
+ }
+
+ mergeInfoLocked(pMergedInfo, pContents);
+ return true;
+}
+
+/*
+ * Trash the cache.
+ */
+void AssetManager::purgeFileNameCacheLocked(void)
+{
+ mCacheValid = false;
+ mCache.clear();
+}
+
+/*
+ * ===========================================================================
+ * AssetManager::SharedZip
+ * ===========================================================================
+ */
+
+
+Mutex AssetManager::SharedZip::gLock;
+DefaultKeyedVector<String8, wp<AssetManager::SharedZip> > AssetManager::SharedZip::gOpen;
+
+AssetManager::SharedZip::SharedZip(const String8& path, time_t modWhen)
+ : mPath(path), mZipFile(NULL), mModWhen(modWhen),
+ mResourceTableAsset(NULL), mResourceTable(NULL)
+{
+ //ALOGI("Creating SharedZip %p %s\n", this, (const char*)mPath);
+ mZipFile = new ZipFileRO;
+ ALOGV("+++ opening zip '%s'\n", mPath.string());
+ if (mZipFile->open(mPath.string()) != NO_ERROR) {
+ ALOGD("failed to open Zip archive '%s'\n", mPath.string());
+ delete mZipFile;
+ mZipFile = NULL;
+ }
+}
+
+sp<AssetManager::SharedZip> AssetManager::SharedZip::get(const String8& path)
+{
+ AutoMutex _l(gLock);
+ time_t modWhen = getFileModDate(path);
+ sp<SharedZip> zip = gOpen.valueFor(path).promote();
+ if (zip != NULL && zip->mModWhen == modWhen) {
+ return zip;
+ }
+ zip = new SharedZip(path, modWhen);
+ gOpen.add(path, zip);
+ return zip;
+
+}
+
+ZipFileRO* AssetManager::SharedZip::getZip()
+{
+ return mZipFile;
+}
+
+Asset* AssetManager::SharedZip::getResourceTableAsset()
+{
+ ALOGV("Getting from SharedZip %p resource asset %p\n", this, mResourceTableAsset);
+ return mResourceTableAsset;
+}
+
+Asset* AssetManager::SharedZip::setResourceTableAsset(Asset* asset)
+{
+ {
+ AutoMutex _l(gLock);
+ if (mResourceTableAsset == NULL) {
+ mResourceTableAsset = asset;
+ // This is not thread safe the first time it is called, so
+ // do it here with the global lock held.
+ asset->getBuffer(true);
+ return asset;
+ }
+ }
+ delete asset;
+ return mResourceTableAsset;
+}
+
+ResTable* AssetManager::SharedZip::getResourceTable()
+{
+ ALOGV("Getting from SharedZip %p resource table %p\n", this, mResourceTable);
+ return mResourceTable;
+}
+
+ResTable* AssetManager::SharedZip::setResourceTable(ResTable* res)
+{
+ {
+ AutoMutex _l(gLock);
+ if (mResourceTable == NULL) {
+ mResourceTable = res;
+ return res;
+ }
+ }
+ delete res;
+ return mResourceTable;
+}
+
+bool AssetManager::SharedZip::isUpToDate()
+{
+ time_t modWhen = getFileModDate(mPath.string());
+ return mModWhen == modWhen;
+}
+
+AssetManager::SharedZip::~SharedZip()
+{
+ //ALOGI("Destroying SharedZip %p %s\n", this, (const char*)mPath);
+ if (mResourceTable != NULL) {
+ delete mResourceTable;
+ }
+ if (mResourceTableAsset != NULL) {
+ delete mResourceTableAsset;
+ }
+ if (mZipFile != NULL) {
+ delete mZipFile;
+ ALOGV("Closed '%s'\n", mPath.string());
+ }
+}
+
+/*
+ * ===========================================================================
+ * AssetManager::ZipSet
+ * ===========================================================================
+ */
+
+/*
+ * Constructor.
+ */
+AssetManager::ZipSet::ZipSet(void)
+{
+}
+
+/*
+ * Destructor. Close any open archives.
+ */
+AssetManager::ZipSet::~ZipSet(void)
+{
+ size_t N = mZipFile.size();
+ for (size_t i = 0; i < N; i++)
+ closeZip(i);
+}
+
+/*
+ * Close a Zip file and reset the entry.
+ */
+void AssetManager::ZipSet::closeZip(int idx)
+{
+ mZipFile.editItemAt(idx) = NULL;
+}
+
+
+/*
+ * Retrieve the appropriate Zip file from the set.
+ */
+ZipFileRO* AssetManager::ZipSet::getZip(const String8& path)
+{
+ int idx = getIndex(path);
+ sp<SharedZip> zip = mZipFile[idx];
+ if (zip == NULL) {
+ zip = SharedZip::get(path);
+ mZipFile.editItemAt(idx) = zip;
+ }
+ return zip->getZip();
+}
+
+Asset* AssetManager::ZipSet::getZipResourceTableAsset(const String8& path)
+{
+ int idx = getIndex(path);
+ sp<SharedZip> zip = mZipFile[idx];
+ if (zip == NULL) {
+ zip = SharedZip::get(path);
+ mZipFile.editItemAt(idx) = zip;
+ }
+ return zip->getResourceTableAsset();
+}
+
+Asset* AssetManager::ZipSet::setZipResourceTableAsset(const String8& path,
+ Asset* asset)
+{
+ int idx = getIndex(path);
+ sp<SharedZip> zip = mZipFile[idx];
+ // doesn't make sense to call before previously accessing.
+ return zip->setResourceTableAsset(asset);
+}
+
+ResTable* AssetManager::ZipSet::getZipResourceTable(const String8& path)
+{
+ int idx = getIndex(path);
+ sp<SharedZip> zip = mZipFile[idx];
+ if (zip == NULL) {
+ zip = SharedZip::get(path);
+ mZipFile.editItemAt(idx) = zip;
+ }
+ return zip->getResourceTable();
+}
+
+ResTable* AssetManager::ZipSet::setZipResourceTable(const String8& path,
+ ResTable* res)
+{
+ int idx = getIndex(path);
+ sp<SharedZip> zip = mZipFile[idx];
+ // doesn't make sense to call before previously accessing.
+ return zip->setResourceTable(res);
+}
+
+/*
+ * Generate the partial pathname for the specified archive. The caller
+ * gets to prepend the asset root directory.
+ *
+ * Returns something like "common/en-US-noogle.jar".
+ */
+/*static*/ String8 AssetManager::ZipSet::getPathName(const char* zipPath)
+{
+ return String8(zipPath);
+}
+
+bool AssetManager::ZipSet::isUpToDate()
+{
+ const size_t N = mZipFile.size();
+ for (size_t i=0; i<N; i++) {
+ if (mZipFile[i] != NULL && !mZipFile[i]->isUpToDate()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/*
+ * Compute the zip file's index.
+ *
+ * "appName", "locale", and "vendor" should be set to NULL to indicate the
+ * default directory.
+ */
+int AssetManager::ZipSet::getIndex(const String8& zip) const
+{
+ const size_t N = mZipPath.size();
+ for (size_t i=0; i<N; i++) {
+ if (mZipPath[i] == zip) {
+ return i;
+ }
+ }
+
+ mZipPath.add(zip);
+ mZipFile.add(NULL);
+
+ return mZipPath.size()-1;
+}
diff --git a/libs/androidfw/BackupData.cpp b/libs/androidfw/BackupData.cpp
new file mode 100644
index 0000000..4e3b522
--- /dev/null
+++ b/libs/androidfw/BackupData.cpp
@@ -0,0 +1,382 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "backup_data"
+
+#include <androidfw/BackupHelpers.h>
+#include <utils/ByteOrder.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <cutils/log.h>
+
+namespace android {
+
+static const bool DEBUG = false;
+
+/*
+ * File Format (v1):
+ *
+ * All ints are stored little-endian.
+ *
+ * - An app_header_v1 struct.
+ * - The name of the package, utf-8, null terminated, padded to 4-byte boundary.
+ * - A sequence of zero or more key/value paires (entities), each with
+ * - A entity_header_v1 struct
+ * - The key, utf-8, null terminated, padded to 4-byte boundary.
+ * - The value, padded to 4 byte boundary
+ */
+
+const static int ROUND_UP[4] = { 0, 3, 2, 1 };
+
+static inline size_t
+round_up(size_t n)
+{
+ return n + ROUND_UP[n % 4];
+}
+
+static inline size_t
+padding_extra(size_t n)
+{
+ return ROUND_UP[n % 4];
+}
+
+BackupDataWriter::BackupDataWriter(int fd)
+ :m_fd(fd),
+ m_status(NO_ERROR),
+ m_pos(0),
+ m_entityCount(0)
+{
+}
+
+BackupDataWriter::~BackupDataWriter()
+{
+}
+
+// Pad out anything they've previously written to the next 4 byte boundary.
+status_t
+BackupDataWriter::write_padding_for(int n)
+{
+ ssize_t amt;
+ ssize_t paddingSize;
+
+ paddingSize = padding_extra(n);
+ if (paddingSize > 0) {
+ uint32_t padding = 0xbcbcbcbc;
+ if (DEBUG) ALOGI("writing %d padding bytes for %d", paddingSize, n);
+ amt = write(m_fd, &padding, paddingSize);
+ if (amt != paddingSize) {
+ m_status = errno;
+ return m_status;
+ }
+ m_pos += amt;
+ }
+ return NO_ERROR;
+}
+
+status_t
+BackupDataWriter::WriteEntityHeader(const String8& key, size_t dataSize)
+{
+ if (m_status != NO_ERROR) {
+ return m_status;
+ }
+
+ ssize_t amt;
+
+ amt = write_padding_for(m_pos);
+ if (amt != 0) {
+ return amt;
+ }
+
+ String8 k;
+ if (m_keyPrefix.length() > 0) {
+ k = m_keyPrefix;
+ k += ":";
+ k += key;
+ } else {
+ k = key;
+ }
+ if (DEBUG) {
+ ALOGD("Writing header: prefix='%s' key='%s' dataSize=%d", m_keyPrefix.string(),
+ key.string(), dataSize);
+ }
+
+ entity_header_v1 header;
+ ssize_t keyLen;
+
+ keyLen = k.length();
+
+ header.type = tolel(BACKUP_HEADER_ENTITY_V1);
+ header.keyLen = tolel(keyLen);
+ header.dataSize = tolel(dataSize);
+
+ if (DEBUG) ALOGI("writing entity header, %d bytes", sizeof(entity_header_v1));
+ amt = write(m_fd, &header, sizeof(entity_header_v1));
+ if (amt != sizeof(entity_header_v1)) {
+ m_status = errno;
+ return m_status;
+ }
+ m_pos += amt;
+
+ if (DEBUG) ALOGI("writing entity header key, %d bytes", keyLen+1);
+ amt = write(m_fd, k.string(), keyLen+1);
+ if (amt != keyLen+1) {
+ m_status = errno;
+ return m_status;
+ }
+ m_pos += amt;
+
+ amt = write_padding_for(keyLen+1);
+
+ m_entityCount++;
+
+ return amt;
+}
+
+status_t
+BackupDataWriter::WriteEntityData(const void* data, size_t size)
+{
+ if (DEBUG) ALOGD("Writing data: size=%lu", (unsigned long) size);
+
+ if (m_status != NO_ERROR) {
+ if (DEBUG) {
+ ALOGD("Not writing data - stream in error state %d (%s)", m_status, strerror(m_status));
+ }
+ return m_status;
+ }
+
+ // We don't write padding here, because they're allowed to call this several
+ // times with smaller buffers. We write it at the end of WriteEntityHeader
+ // instead.
+ ssize_t amt = write(m_fd, data, size);
+ if (amt != (ssize_t)size) {
+ m_status = errno;
+ if (DEBUG) ALOGD("write returned error %d (%s)", m_status, strerror(m_status));
+ return m_status;
+ }
+ m_pos += amt;
+ return NO_ERROR;
+}
+
+void
+BackupDataWriter::SetKeyPrefix(const String8& keyPrefix)
+{
+ m_keyPrefix = keyPrefix;
+}
+
+
+BackupDataReader::BackupDataReader(int fd)
+ :m_fd(fd),
+ m_done(false),
+ m_status(NO_ERROR),
+ m_pos(0),
+ m_entityCount(0)
+{
+ memset(&m_header, 0, sizeof(m_header));
+}
+
+BackupDataReader::~BackupDataReader()
+{
+}
+
+status_t
+BackupDataReader::Status()
+{
+ return m_status;
+}
+
+#define CHECK_SIZE(actual, expected) \
+ do { \
+ if ((actual) != (expected)) { \
+ if ((actual) == 0) { \
+ m_status = EIO; \
+ m_done = true; \
+ } else { \
+ m_status = errno; \
+ ALOGD("CHECK_SIZE(a=%ld e=%ld) failed at line %d m_status='%s'", \
+ long(actual), long(expected), __LINE__, strerror(m_status)); \
+ } \
+ return m_status; \
+ } \
+ } while(0)
+#define SKIP_PADDING() \
+ do { \
+ status_t err = skip_padding(); \
+ if (err != NO_ERROR) { \
+ ALOGD("SKIP_PADDING FAILED at line %d", __LINE__); \
+ m_status = err; \
+ return err; \
+ } \
+ } while(0)
+
+status_t
+BackupDataReader::ReadNextHeader(bool* done, int* type)
+{
+ *done = m_done;
+ if (m_status != NO_ERROR) {
+ return m_status;
+ }
+
+ int amt;
+
+ amt = skip_padding();
+ if (amt == EIO) {
+ *done = m_done = true;
+ return NO_ERROR;
+ }
+ else if (amt != NO_ERROR) {
+ return amt;
+ }
+ amt = read(m_fd, &m_header, sizeof(m_header));
+ *done = m_done = (amt == 0);
+ if (*done) {
+ return NO_ERROR;
+ }
+ CHECK_SIZE(amt, sizeof(m_header));
+ m_pos += sizeof(m_header);
+ if (type) {
+ *type = m_header.type;
+ }
+
+ // validate and fix up the fields.
+ m_header.type = fromlel(m_header.type);
+ switch (m_header.type)
+ {
+ case BACKUP_HEADER_ENTITY_V1:
+ {
+ m_header.entity.keyLen = fromlel(m_header.entity.keyLen);
+ if (m_header.entity.keyLen <= 0) {
+ ALOGD("Entity header at %d has keyLen<=0: 0x%08x\n", (int)m_pos,
+ (int)m_header.entity.keyLen);
+ m_status = EINVAL;
+ }
+ m_header.entity.dataSize = fromlel(m_header.entity.dataSize);
+ m_entityCount++;
+
+ // read the rest of the header (filename)
+ size_t size = m_header.entity.keyLen;
+ char* buf = m_key.lockBuffer(size);
+ if (buf == NULL) {
+ m_status = ENOMEM;
+ return m_status;
+ }
+ int amt = read(m_fd, buf, size+1);
+ CHECK_SIZE(amt, (int)size+1);
+ m_key.unlockBuffer(size);
+ m_pos += size+1;
+ SKIP_PADDING();
+ m_dataEndPos = m_pos + m_header.entity.dataSize;
+
+ break;
+ }
+ default:
+ ALOGD("Chunk header at %d has invalid type: 0x%08x",
+ (int)(m_pos - sizeof(m_header)), (int)m_header.type);
+ m_status = EINVAL;
+ }
+
+ return m_status;
+}
+
+bool
+BackupDataReader::HasEntities()
+{
+ return m_status == NO_ERROR && m_header.type == BACKUP_HEADER_ENTITY_V1;
+}
+
+status_t
+BackupDataReader::ReadEntityHeader(String8* key, size_t* dataSize)
+{
+ if (m_status != NO_ERROR) {
+ return m_status;
+ }
+ if (m_header.type != BACKUP_HEADER_ENTITY_V1) {
+ return EINVAL;
+ }
+ *key = m_key;
+ *dataSize = m_header.entity.dataSize;
+ return NO_ERROR;
+}
+
+status_t
+BackupDataReader::SkipEntityData()
+{
+ if (m_status != NO_ERROR) {
+ return m_status;
+ }
+ if (m_header.type != BACKUP_HEADER_ENTITY_V1) {
+ return EINVAL;
+ }
+ if (m_header.entity.dataSize > 0) {
+ int pos = lseek(m_fd, m_dataEndPos, SEEK_SET);
+ if (pos == -1) {
+ return errno;
+ }
+ m_pos = pos;
+ }
+ SKIP_PADDING();
+ return NO_ERROR;
+}
+
+ssize_t
+BackupDataReader::ReadEntityData(void* data, size_t size)
+{
+ if (m_status != NO_ERROR) {
+ return -1;
+ }
+ int remaining = m_dataEndPos - m_pos;
+ //ALOGD("ReadEntityData size=%d m_pos=0x%x m_dataEndPos=0x%x remaining=%d\n",
+ // size, m_pos, m_dataEndPos, remaining);
+ if (remaining <= 0) {
+ return 0;
+ }
+ if (((int)size) > remaining) {
+ size = remaining;
+ }
+ //ALOGD(" reading %d bytes", size);
+ int amt = read(m_fd, data, size);
+ if (amt < 0) {
+ m_status = errno;
+ return -1;
+ }
+ if (amt == 0) {
+ m_status = EIO;
+ m_done = true;
+ }
+ m_pos += amt;
+ return amt;
+}
+
+status_t
+BackupDataReader::skip_padding()
+{
+ ssize_t amt;
+ ssize_t paddingSize;
+
+ paddingSize = padding_extra(m_pos);
+ if (paddingSize > 0) {
+ uint32_t padding;
+ amt = read(m_fd, &padding, paddingSize);
+ CHECK_SIZE(amt, paddingSize);
+ m_pos += amt;
+ }
+ return NO_ERROR;
+}
+
+
+} // namespace android
diff --git a/libs/androidfw/BackupHelpers.cpp b/libs/androidfw/BackupHelpers.cpp
new file mode 100644
index 0000000..b8d3f48
--- /dev/null
+++ b/libs/androidfw/BackupHelpers.cpp
@@ -0,0 +1,1591 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "file_backup_helper"
+
+#include <androidfw/BackupHelpers.h>
+
+#include <utils/KeyedVector.h>
+#include <utils/ByteOrder.h>
+#include <utils/String8.h>
+
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <sys/stat.h>
+#include <sys/time.h> // for utimes
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <utime.h>
+#include <fcntl.h>
+#include <zlib.h>
+
+#include <cutils/log.h>
+
+namespace android {
+
+#define MAGIC0 0x70616e53 // Snap
+#define MAGIC1 0x656c6946 // File
+
+/*
+ * File entity data format (v1):
+ *
+ * - 4-byte version number of the metadata, little endian (0x00000001 for v1)
+ * - 12 bytes of metadata
+ * - the file data itself
+ *
+ * i.e. a 16-byte metadata header followed by the raw file data. If the
+ * restore code does not recognize the metadata version, it can still
+ * interpret the file data itself correctly.
+ *
+ * file_metadata_v1:
+ *
+ * - 4 byte version number === 0x00000001 (little endian)
+ * - 4-byte access mode (little-endian)
+ * - undefined (8 bytes)
+ */
+
+struct file_metadata_v1 {
+ int version;
+ int mode;
+ int undefined_1;
+ int undefined_2;
+};
+
+const static int CURRENT_METADATA_VERSION = 1;
+
+#if 1
+#define LOGP(f, x...)
+#else
+#if TEST_BACKUP_HELPERS
+#define LOGP(f, x...) printf(f "\n", x)
+#else
+#define LOGP(x...) ALOGD(x)
+#endif
+#endif
+
+const static int ROUND_UP[4] = { 0, 3, 2, 1 };
+
+static inline int
+round_up(int n)
+{
+ return n + ROUND_UP[n % 4];
+}
+
+static int
+read_snapshot_file(int fd, KeyedVector<String8,FileState>* snapshot)
+{
+ int bytesRead = 0;
+ int amt;
+ SnapshotHeader header;
+
+ amt = read(fd, &header, sizeof(header));
+ if (amt != sizeof(header)) {
+ return errno;
+ }
+ bytesRead += amt;
+
+ if (header.magic0 != MAGIC0 || header.magic1 != MAGIC1) {
+ ALOGW("read_snapshot_file header.magic0=0x%08x magic1=0x%08x", header.magic0, header.magic1);
+ return 1;
+ }
+
+ for (int i=0; i<header.fileCount; i++) {
+ FileState file;
+ char filenameBuf[128];
+
+ amt = read(fd, &file, sizeof(FileState));
+ if (amt != sizeof(FileState)) {
+ ALOGW("read_snapshot_file FileState truncated/error with read at %d bytes\n", bytesRead);
+ return 1;
+ }
+ bytesRead += amt;
+
+ // filename is not NULL terminated, but it is padded
+ int nameBufSize = round_up(file.nameLen);
+ char* filename = nameBufSize <= (int)sizeof(filenameBuf)
+ ? filenameBuf
+ : (char*)malloc(nameBufSize);
+ amt = read(fd, filename, nameBufSize);
+ if (amt == nameBufSize) {
+ snapshot->add(String8(filename, file.nameLen), file);
+ }
+ bytesRead += amt;
+ if (filename != filenameBuf) {
+ free(filename);
+ }
+ if (amt != nameBufSize) {
+ ALOGW("read_snapshot_file filename truncated/error with read at %d bytes\n", bytesRead);
+ return 1;
+ }
+ }
+
+ if (header.totalSize != bytesRead) {
+ ALOGW("read_snapshot_file length mismatch: header.totalSize=%d bytesRead=%d\n",
+ header.totalSize, bytesRead);
+ return 1;
+ }
+
+ return 0;
+}
+
+static int
+write_snapshot_file(int fd, const KeyedVector<String8,FileRec>& snapshot)
+{
+ int fileCount = 0;
+ int bytesWritten = sizeof(SnapshotHeader);
+ // preflight size
+ const int N = snapshot.size();
+ for (int i=0; i<N; i++) {
+ const FileRec& g = snapshot.valueAt(i);
+ if (!g.deleted) {
+ const String8& name = snapshot.keyAt(i);
+ bytesWritten += sizeof(FileState) + round_up(name.length());
+ fileCount++;
+ }
+ }
+
+ LOGP("write_snapshot_file fd=%d\n", fd);
+
+ int amt;
+ SnapshotHeader header = { MAGIC0, fileCount, MAGIC1, bytesWritten };
+
+ amt = write(fd, &header, sizeof(header));
+ if (amt != sizeof(header)) {
+ ALOGW("write_snapshot_file error writing header %s", strerror(errno));
+ return errno;
+ }
+
+ for (int i=0; i<N; i++) {
+ FileRec r = snapshot.valueAt(i);
+ if (!r.deleted) {
+ const String8& name = snapshot.keyAt(i);
+ int nameLen = r.s.nameLen = name.length();
+
+ amt = write(fd, &r.s, sizeof(FileState));
+ if (amt != sizeof(FileState)) {
+ ALOGW("write_snapshot_file error writing header %s", strerror(errno));
+ return 1;
+ }
+
+ // filename is not NULL terminated, but it is padded
+ amt = write(fd, name.string(), nameLen);
+ if (amt != nameLen) {
+ ALOGW("write_snapshot_file error writing filename %s", strerror(errno));
+ return 1;
+ }
+ int paddingLen = ROUND_UP[nameLen % 4];
+ if (paddingLen != 0) {
+ int padding = 0xabababab;
+ amt = write(fd, &padding, paddingLen);
+ if (amt != paddingLen) {
+ ALOGW("write_snapshot_file error writing %d bytes of filename padding %s",
+ paddingLen, strerror(errno));
+ return 1;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int
+write_delete_file(BackupDataWriter* dataStream, const String8& key)
+{
+ LOGP("write_delete_file %s\n", key.string());
+ return dataStream->WriteEntityHeader(key, -1);
+}
+
+static int
+write_update_file(BackupDataWriter* dataStream, int fd, int mode, const String8& key,
+ char const* realFilename)
+{
+ LOGP("write_update_file %s (%s) : mode 0%o\n", realFilename, key.string(), mode);
+
+ const int bufsize = 4*1024;
+ int err;
+ int amt;
+ int fileSize;
+ int bytesLeft;
+ file_metadata_v1 metadata;
+
+ char* buf = (char*)malloc(bufsize);
+ int crc = crc32(0L, Z_NULL, 0);
+
+
+ fileSize = lseek(fd, 0, SEEK_END);
+ lseek(fd, 0, SEEK_SET);
+
+ if (sizeof(metadata) != 16) {
+ ALOGE("ERROR: metadata block is the wrong size!");
+ }
+
+ bytesLeft = fileSize + sizeof(metadata);
+ err = dataStream->WriteEntityHeader(key, bytesLeft);
+ if (err != 0) {
+ free(buf);
+ return err;
+ }
+
+ // store the file metadata first
+ metadata.version = tolel(CURRENT_METADATA_VERSION);
+ metadata.mode = tolel(mode);
+ metadata.undefined_1 = metadata.undefined_2 = 0;
+ err = dataStream->WriteEntityData(&metadata, sizeof(metadata));
+ if (err != 0) {
+ free(buf);
+ return err;
+ }
+ bytesLeft -= sizeof(metadata); // bytesLeft should == fileSize now
+
+ // now store the file content
+ while ((amt = read(fd, buf, bufsize)) != 0 && bytesLeft > 0) {
+ bytesLeft -= amt;
+ if (bytesLeft < 0) {
+ amt += bytesLeft; // Plus a negative is minus. Don't write more than we promised.
+ }
+ err = dataStream->WriteEntityData(buf, amt);
+ if (err != 0) {
+ free(buf);
+ return err;
+ }
+ }
+ if (bytesLeft != 0) {
+ if (bytesLeft > 0) {
+ // Pad out the space we promised in the buffer. We can't corrupt the buffer,
+ // even though the data we're sending is probably bad.
+ memset(buf, 0, bufsize);
+ while (bytesLeft > 0) {
+ amt = bytesLeft < bufsize ? bytesLeft : bufsize;
+ bytesLeft -= amt;
+ err = dataStream->WriteEntityData(buf, amt);
+ if (err != 0) {
+ free(buf);
+ return err;
+ }
+ }
+ }
+ ALOGE("write_update_file size mismatch for %s. expected=%d actual=%d."
+ " You aren't doing proper locking!", realFilename, fileSize, fileSize-bytesLeft);
+ }
+
+ free(buf);
+ return NO_ERROR;
+}
+
+static int
+write_update_file(BackupDataWriter* dataStream, const String8& key, char const* realFilename)
+{
+ int err;
+ struct stat st;
+
+ err = stat(realFilename, &st);
+ if (err < 0) {
+ return errno;
+ }
+
+ int fd = open(realFilename, O_RDONLY);
+ if (fd == -1) {
+ return errno;
+ }
+
+ err = write_update_file(dataStream, fd, st.st_mode, key, realFilename);
+ close(fd);
+ return err;
+}
+
+static int
+compute_crc32(int fd)
+{
+ const int bufsize = 4*1024;
+ int amt;
+
+ char* buf = (char*)malloc(bufsize);
+ int crc = crc32(0L, Z_NULL, 0);
+
+ lseek(fd, 0, SEEK_SET);
+
+ while ((amt = read(fd, buf, bufsize)) != 0) {
+ crc = crc32(crc, (Bytef*)buf, amt);
+ }
+
+ free(buf);
+ return crc;
+}
+
+int
+back_up_files(int oldSnapshotFD, BackupDataWriter* dataStream, int newSnapshotFD,
+ char const* const* files, char const* const* keys, int fileCount)
+{
+ int err;
+ KeyedVector<String8,FileState> oldSnapshot;
+ KeyedVector<String8,FileRec> newSnapshot;
+
+ if (oldSnapshotFD != -1) {
+ err = read_snapshot_file(oldSnapshotFD, &oldSnapshot);
+ if (err != 0) {
+ // On an error, treat this as a full backup.
+ oldSnapshot.clear();
+ }
+ }
+
+ for (int i=0; i<fileCount; i++) {
+ String8 key(keys[i]);
+ FileRec r;
+ char const* file = files[i];
+ r.file = file;
+ struct stat st;
+
+ err = stat(file, &st);
+ if (err != 0) {
+ r.deleted = true;
+ } else {
+ r.deleted = false;
+ r.s.modTime_sec = st.st_mtime;
+ r.s.modTime_nsec = 0; // workaround sim breakage
+ //r.s.modTime_nsec = st.st_mtime_nsec;
+ r.s.mode = st.st_mode;
+ r.s.size = st.st_size;
+ // we compute the crc32 later down below, when we already have the file open.
+
+ if (newSnapshot.indexOfKey(key) >= 0) {
+ LOGP("back_up_files key already in use '%s'", key.string());
+ return -1;
+ }
+ }
+ newSnapshot.add(key, r);
+ }
+
+ int n = 0;
+ int N = oldSnapshot.size();
+ int m = 0;
+
+ while (n<N && m<fileCount) {
+ const String8& p = oldSnapshot.keyAt(n);
+ const String8& q = newSnapshot.keyAt(m);
+ FileRec& g = newSnapshot.editValueAt(m);
+ int cmp = p.compare(q);
+ if (g.deleted || cmp < 0) {
+ // file removed
+ LOGP("file removed: %s", p.string());
+ g.deleted = true; // They didn't mention the file, but we noticed that it's gone.
+ dataStream->WriteEntityHeader(p, -1);
+ n++;
+ }
+ else if (cmp > 0) {
+ // file added
+ LOGP("file added: %s", g.file.string());
+ write_update_file(dataStream, q, g.file.string());
+ m++;
+ }
+ else {
+ // both files exist, check them
+ const FileState& f = oldSnapshot.valueAt(n);
+
+ int fd = open(g.file.string(), O_RDONLY);
+ if (fd < 0) {
+ // We can't open the file. Don't report it as a delete either. Let the
+ // server keep the old version. Maybe they'll be able to deal with it
+ // on restore.
+ LOGP("Unable to open file %s - skipping", g.file.string());
+ } else {
+ g.s.crc32 = compute_crc32(fd);
+
+ LOGP("%s", q.string());
+ LOGP(" new: modTime=%d,%d mode=%04o size=%-3d crc32=0x%08x",
+ f.modTime_sec, f.modTime_nsec, f.mode, f.size, f.crc32);
+ LOGP(" old: modTime=%d,%d mode=%04o size=%-3d crc32=0x%08x",
+ g.s.modTime_sec, g.s.modTime_nsec, g.s.mode, g.s.size, g.s.crc32);
+ if (f.modTime_sec != g.s.modTime_sec || f.modTime_nsec != g.s.modTime_nsec
+ || f.mode != g.s.mode || f.size != g.s.size || f.crc32 != g.s.crc32) {
+ write_update_file(dataStream, fd, g.s.mode, p, g.file.string());
+ }
+
+ close(fd);
+ }
+ n++;
+ m++;
+ }
+ }
+
+ // these were deleted
+ while (n<N) {
+ dataStream->WriteEntityHeader(oldSnapshot.keyAt(n), -1);
+ n++;
+ }
+
+ // these were added
+ while (m<fileCount) {
+ const String8& q = newSnapshot.keyAt(m);
+ FileRec& g = newSnapshot.editValueAt(m);
+ write_update_file(dataStream, q, g.file.string());
+ m++;
+ }
+
+ err = write_snapshot_file(newSnapshotFD, newSnapshot);
+
+ return 0;
+}
+
+// Utility function, equivalent to stpcpy(): perform a strcpy, but instead of
+// returning the initial dest, return a pointer to the trailing NUL.
+static char* strcpy_ptr(char* dest, const char* str) {
+ if (dest && str) {
+ while ((*dest = *str) != 0) {
+ dest++;
+ str++;
+ }
+ }
+ return dest;
+}
+
+static void calc_tar_checksum(char* buf) {
+ // [ 148 : 8 ] checksum -- to be calculated with this field as space chars
+ memset(buf + 148, ' ', 8);
+
+ uint16_t sum = 0;
+ for (uint8_t* p = (uint8_t*) buf; p < ((uint8_t*)buf) + 512; p++) {
+ sum += *p;
+ }
+
+ // Now write the real checksum value:
+ // [ 148 : 8 ] checksum: 6 octal digits [leading zeroes], NUL, SPC
+ sprintf(buf + 148, "%06o", sum); // the trailing space is already in place
+}
+
+// Returns number of bytes written
+static int write_pax_header_entry(char* buf, const char* key, const char* value) {
+ // start with the size of "1 key=value\n"
+ int len = strlen(key) + strlen(value) + 4;
+ if (len > 9) len++;
+ if (len > 99) len++;
+ if (len > 999) len++;
+ // since PATH_MAX is 4096 we don't expect to have to generate any single
+ // header entry longer than 9999 characters
+
+ return sprintf(buf, "%d %s=%s\n", len, key, value);
+}
+
+// Wire format to the backup manager service is chunked: each chunk is prefixed by
+// a 4-byte count of its size. A chunk size of zero (four zero bytes) indicates EOD.
+void send_tarfile_chunk(BackupDataWriter* writer, const char* buffer, size_t size) {
+ uint32_t chunk_size_no = htonl(size);
+ writer->WriteEntityData(&chunk_size_no, 4);
+ if (size != 0) writer->WriteEntityData(buffer, size);
+}
+
+int write_tarfile(const String8& packageName, const String8& domain,
+ const String8& rootpath, const String8& filepath, BackupDataWriter* writer)
+{
+ // In the output stream everything is stored relative to the root
+ const char* relstart = filepath.string() + rootpath.length();
+ if (*relstart == '/') relstart++; // won't be true when path == rootpath
+ String8 relpath(relstart);
+
+ // If relpath is empty, it means this is the top of one of the standard named
+ // domain directories, so we should just skip it
+ if (relpath.length() == 0) {
+ return 0;
+ }
+
+ // Too long a name for the ustar format?
+ // "apps/" + packagename + '/' + domainpath < 155 chars
+ // relpath < 100 chars
+ bool needExtended = false;
+ if ((5 + packageName.length() + 1 + domain.length() >= 155) || (relpath.length() >= 100)) {
+ needExtended = true;
+ }
+
+ // Non-7bit-clean path also means needing pax extended format
+ if (!needExtended) {
+ for (size_t i = 0; i < filepath.length(); i++) {
+ if ((filepath[i] & 0x80) != 0) {
+ needExtended = true;
+ break;
+ }
+ }
+ }
+
+ int err = 0;
+ struct stat64 s;
+ if (lstat64(filepath.string(), &s) != 0) {
+ err = errno;
+ ALOGE("Error %d (%s) from lstat64(%s)", err, strerror(err), filepath.string());
+ return err;
+ }
+
+ String8 fullname; // for pax later on
+ String8 prefix;
+
+ const int isdir = S_ISDIR(s.st_mode);
+ if (isdir) s.st_size = 0; // directories get no actual data in the tar stream
+
+ // !!! TODO: use mmap when possible to avoid churning the buffer cache
+ // !!! TODO: this will break with symlinks; need to use readlink(2)
+ int fd = open(filepath.string(), O_RDONLY);
+ if (fd < 0) {
+ err = errno;
+ ALOGE("Error %d (%s) from open(%s)", err, strerror(err), filepath.string());
+ return err;
+ }
+
+ // read/write up to this much at a time.
+ const size_t BUFSIZE = 32 * 1024;
+ char* buf = (char *)calloc(1,BUFSIZE);
+ char* paxHeader = buf + 512; // use a different chunk of it as separate scratch
+ char* paxData = buf + 1024;
+
+ if (buf == NULL) {
+ ALOGE("Out of mem allocating transfer buffer");
+ err = ENOMEM;
+ goto done;
+ }
+
+ // Magic fields for the ustar file format
+ strcat(buf + 257, "ustar");
+ strcat(buf + 263, "00");
+
+ // [ 265 : 32 ] user name, ignored on restore
+ // [ 297 : 32 ] group name, ignored on restore
+
+ // [ 100 : 8 ] file mode
+ snprintf(buf + 100, 8, "%06o ", s.st_mode & ~S_IFMT);
+
+ // [ 108 : 8 ] uid -- ignored in Android format; uids are remapped at restore time
+ // [ 116 : 8 ] gid -- ignored in Android format
+ snprintf(buf + 108, 8, "0%lo", s.st_uid);
+ snprintf(buf + 116, 8, "0%lo", s.st_gid);
+
+ // [ 124 : 12 ] file size in bytes
+ if (s.st_size > 077777777777LL) {
+ // very large files need a pax extended size header
+ needExtended = true;
+ }
+ snprintf(buf + 124, 12, "%011llo", (isdir) ? 0LL : s.st_size);
+
+ // [ 136 : 12 ] last mod time as a UTC time_t
+ snprintf(buf + 136, 12, "%0lo", s.st_mtime);
+
+ // [ 156 : 1 ] link/file type
+ uint8_t type;
+ if (isdir) {
+ type = '5'; // tar magic: '5' == directory
+ } else if (S_ISREG(s.st_mode)) {
+ type = '0'; // tar magic: '0' == normal file
+ } else {
+ ALOGW("Error: unknown file mode 0%o [%s]", s.st_mode, filepath.string());
+ goto cleanup;
+ }
+ buf[156] = type;
+
+ // [ 157 : 100 ] name of linked file [not implemented]
+
+ {
+ // Prefix and main relative path. Path lengths have been preflighted.
+ if (packageName.length() > 0) {
+ prefix = "apps/";
+ prefix += packageName;
+ }
+ if (domain.length() > 0) {
+ prefix.appendPath(domain);
+ }
+
+ // pax extended means we don't put in a prefix field, and put a different
+ // string in the basic name field. We can also construct the full path name
+ // out of the substrings we've now built.
+ fullname = prefix;
+ fullname.appendPath(relpath);
+
+ // ustar:
+ // [ 0 : 100 ]; file name/path
+ // [ 345 : 155 ] filename path prefix
+ // We only use the prefix area if fullname won't fit in the path
+ if (fullname.length() > 100) {
+ strncpy(buf, relpath.string(), 100);
+ strncpy(buf + 345, prefix.string(), 155);
+ } else {
+ strncpy(buf, fullname.string(), 100);
+ }
+ }
+
+ // [ 329 : 8 ] and [ 337 : 8 ] devmajor/devminor, not used
+
+ ALOGI(" Name: %s", fullname.string());
+
+ // If we're using a pax extended header, build & write that here; lengths are
+ // already preflighted
+ if (needExtended) {
+ char sizeStr[32]; // big enough for a 64-bit unsigned value in decimal
+ char* p = paxData;
+
+ // construct the pax extended header data block
+ memset(paxData, 0, BUFSIZE - (paxData - buf));
+ int len;
+
+ // size header -- calc len in digits by actually rendering the number
+ // to a string - brute force but simple
+ snprintf(sizeStr, sizeof(sizeStr), "%lld", s.st_size);
+ p += write_pax_header_entry(p, "size", sizeStr);
+
+ // fullname was generated above with the ustar paths
+ p += write_pax_header_entry(p, "path", fullname.string());
+
+ // Now we know how big the pax data is
+ int paxLen = p - paxData;
+
+ // Now build the pax *header* templated on the ustar header
+ memcpy(paxHeader, buf, 512);
+
+ String8 leaf = fullname.getPathLeaf();
+ memset(paxHeader, 0, 100); // rewrite the name area
+ snprintf(paxHeader, 100, "PaxHeader/%s", leaf.string());
+ memset(paxHeader + 345, 0, 155); // rewrite the prefix area
+ strncpy(paxHeader + 345, prefix.string(), 155);
+
+ paxHeader[156] = 'x'; // mark it as a pax extended header
+
+ // [ 124 : 12 ] size of pax extended header data
+ memset(paxHeader + 124, 0, 12);
+ snprintf(paxHeader + 124, 12, "%011o", p - paxData);
+
+ // Checksum and write the pax block header
+ calc_tar_checksum(paxHeader);
+ send_tarfile_chunk(writer, paxHeader, 512);
+
+ // Now write the pax data itself
+ int paxblocks = (paxLen + 511) / 512;
+ send_tarfile_chunk(writer, paxData, 512 * paxblocks);
+ }
+
+ // Checksum and write the 512-byte ustar file header block to the output
+ calc_tar_checksum(buf);
+ send_tarfile_chunk(writer, buf, 512);
+
+ // Now write the file data itself, for real files. We honor tar's convention that
+ // only full 512-byte blocks are sent to write().
+ if (!isdir) {
+ off64_t toWrite = s.st_size;
+ while (toWrite > 0) {
+ size_t toRead = (toWrite < BUFSIZE) ? toWrite : BUFSIZE;
+ ssize_t nRead = read(fd, buf, toRead);
+ if (nRead < 0) {
+ err = errno;
+ ALOGE("Unable to read file [%s], err=%d (%s)", filepath.string(),
+ err, strerror(err));
+ break;
+ } else if (nRead == 0) {
+ ALOGE("EOF but expect %lld more bytes in [%s]", (long long) toWrite,
+ filepath.string());
+ err = EIO;
+ break;
+ }
+
+ // At EOF we might have a short block; NUL-pad that to a 512-byte multiple. This
+ // depends on the OS guarantee that for ordinary files, read() will never return
+ // less than the number of bytes requested.
+ ssize_t partial = (nRead+512) % 512;
+ if (partial > 0) {
+ ssize_t remainder = 512 - partial;
+ memset(buf + nRead, 0, remainder);
+ nRead += remainder;
+ }
+ send_tarfile_chunk(writer, buf, nRead);
+ toWrite -= nRead;
+ }
+ }
+
+cleanup:
+ free(buf);
+done:
+ close(fd);
+ return err;
+}
+// end tarfile
+
+
+
+#define RESTORE_BUF_SIZE (8*1024)
+
+RestoreHelperBase::RestoreHelperBase()
+{
+ m_buf = malloc(RESTORE_BUF_SIZE);
+ m_loggedUnknownMetadata = false;
+}
+
+RestoreHelperBase::~RestoreHelperBase()
+{
+ free(m_buf);
+}
+
+status_t
+RestoreHelperBase::WriteFile(const String8& filename, BackupDataReader* in)
+{
+ ssize_t err;
+ size_t dataSize;
+ String8 key;
+ int fd;
+ void* buf = m_buf;
+ ssize_t amt;
+ int mode;
+ int crc;
+ struct stat st;
+ FileRec r;
+
+ err = in->ReadEntityHeader(&key, &dataSize);
+ if (err != NO_ERROR) {
+ return err;
+ }
+
+ // Get the metadata block off the head of the file entity and use that to
+ // set up the output file
+ file_metadata_v1 metadata;
+ amt = in->ReadEntityData(&metadata, sizeof(metadata));
+ if (amt != sizeof(metadata)) {
+ ALOGW("Could not read metadata for %s -- %ld / %s", filename.string(),
+ (long)amt, strerror(errno));
+ return EIO;
+ }
+ metadata.version = fromlel(metadata.version);
+ metadata.mode = fromlel(metadata.mode);
+ if (metadata.version > CURRENT_METADATA_VERSION) {
+ if (!m_loggedUnknownMetadata) {
+ m_loggedUnknownMetadata = true;
+ ALOGW("Restoring file with unsupported metadata version %d (currently %d)",
+ metadata.version, CURRENT_METADATA_VERSION);
+ }
+ }
+ mode = metadata.mode;
+
+ // Write the file and compute the crc
+ crc = crc32(0L, Z_NULL, 0);
+ fd = open(filename.string(), O_CREAT|O_RDWR|O_TRUNC, mode);
+ if (fd == -1) {
+ ALOGW("Could not open file %s -- %s", filename.string(), strerror(errno));
+ return errno;
+ }
+
+ while ((amt = in->ReadEntityData(buf, RESTORE_BUF_SIZE)) > 0) {
+ err = write(fd, buf, amt);
+ if (err != amt) {
+ close(fd);
+ ALOGW("Error '%s' writing '%s'", strerror(errno), filename.string());
+ return errno;
+ }
+ crc = crc32(crc, (Bytef*)buf, amt);
+ }
+
+ close(fd);
+
+ // Record for the snapshot
+ err = stat(filename.string(), &st);
+ if (err != 0) {
+ ALOGW("Error stating file that we just created %s", filename.string());
+ return errno;
+ }
+
+ r.file = filename;
+ r.deleted = false;
+ r.s.modTime_sec = st.st_mtime;
+ r.s.modTime_nsec = 0; // workaround sim breakage
+ //r.s.modTime_nsec = st.st_mtime_nsec;
+ r.s.mode = st.st_mode;
+ r.s.size = st.st_size;
+ r.s.crc32 = crc;
+
+ m_files.add(key, r);
+
+ return NO_ERROR;
+}
+
+status_t
+RestoreHelperBase::WriteSnapshot(int fd)
+{
+ return write_snapshot_file(fd, m_files);;
+}
+
+#if TEST_BACKUP_HELPERS
+
+#define SCRATCH_DIR "/data/backup_helper_test/"
+
+static int
+write_text_file(const char* path, const char* data)
+{
+ int amt;
+ int fd;
+ int len;
+
+ fd = creat(path, 0666);
+ if (fd == -1) {
+ fprintf(stderr, "creat %s failed\n", path);
+ return errno;
+ }
+
+ len = strlen(data);
+ amt = write(fd, data, len);
+ if (amt != len) {
+ fprintf(stderr, "error (%s) writing to file %s\n", strerror(errno), path);
+ return errno;
+ }
+
+ close(fd);
+
+ return 0;
+}
+
+static int
+compare_file(const char* path, const unsigned char* data, int len)
+{
+ int fd;
+ int amt;
+
+ fd = open(path, O_RDONLY);
+ if (fd == -1) {
+ fprintf(stderr, "compare_file error (%s) opening %s\n", strerror(errno), path);
+ return errno;
+ }
+
+ unsigned char* contents = (unsigned char*)malloc(len);
+ if (contents == NULL) {
+ fprintf(stderr, "malloc(%d) failed\n", len);
+ return ENOMEM;
+ }
+
+ bool sizesMatch = true;
+ amt = lseek(fd, 0, SEEK_END);
+ if (amt != len) {
+ fprintf(stderr, "compare_file file length should be %d, was %d\n", len, amt);
+ sizesMatch = false;
+ }
+ lseek(fd, 0, SEEK_SET);
+
+ int readLen = amt < len ? amt : len;
+ amt = read(fd, contents, readLen);
+ if (amt != readLen) {
+ fprintf(stderr, "compare_file read expected %d bytes but got %d\n", len, amt);
+ }
+
+ bool contentsMatch = true;
+ for (int i=0; i<readLen; i++) {
+ if (data[i] != contents[i]) {
+ if (contentsMatch) {
+ fprintf(stderr, "compare_file contents are different: (index, expected, actual)\n");
+ contentsMatch = false;
+ }
+ fprintf(stderr, " [%-2d] %02x %02x\n", i, data[i], contents[i]);
+ }
+ }
+
+ free(contents);
+ return contentsMatch && sizesMatch ? 0 : 1;
+}
+
+int
+backup_helper_test_empty()
+{
+ int err;
+ int fd;
+ KeyedVector<String8,FileRec> snapshot;
+ const char* filename = SCRATCH_DIR "backup_helper_test_empty.snap";
+
+ system("rm -r " SCRATCH_DIR);
+ mkdir(SCRATCH_DIR, 0777);
+
+ // write
+ fd = creat(filename, 0666);
+ if (fd == -1) {
+ fprintf(stderr, "error creating %s\n", filename);
+ return 1;
+ }
+
+ err = write_snapshot_file(fd, snapshot);
+
+ close(fd);
+
+ if (err != 0) {
+ fprintf(stderr, "write_snapshot_file reported error %d (%s)\n", err, strerror(err));
+ return err;
+ }
+
+ static const unsigned char correct_data[] = {
+ 0x53, 0x6e, 0x61, 0x70, 0x00, 0x00, 0x00, 0x00,
+ 0x46, 0x69, 0x6c, 0x65, 0x10, 0x00, 0x00, 0x00
+ };
+
+ err = compare_file(filename, correct_data, sizeof(correct_data));
+ if (err != 0) {
+ return err;
+ }
+
+ // read
+ fd = open(filename, O_RDONLY);
+ if (fd == -1) {
+ fprintf(stderr, "error opening for read %s\n", filename);
+ return 1;
+ }
+
+ KeyedVector<String8,FileState> readSnapshot;
+ err = read_snapshot_file(fd, &readSnapshot);
+ if (err != 0) {
+ fprintf(stderr, "read_snapshot_file failed %d\n", err);
+ return err;
+ }
+
+ if (readSnapshot.size() != 0) {
+ fprintf(stderr, "readSnapshot should be length 0\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+int
+backup_helper_test_four()
+{
+ int err;
+ int fd;
+ KeyedVector<String8,FileRec> snapshot;
+ const char* filename = SCRATCH_DIR "backup_helper_test_four.snap";
+
+ system("rm -r " SCRATCH_DIR);
+ mkdir(SCRATCH_DIR, 0777);
+
+ // write
+ fd = creat(filename, 0666);
+ if (fd == -1) {
+ fprintf(stderr, "error opening %s\n", filename);
+ return 1;
+ }
+
+ String8 filenames[4];
+ FileState states[4];
+ FileRec r;
+ r.deleted = false;
+
+ states[0].modTime_sec = 0xfedcba98;
+ states[0].modTime_nsec = 0xdeadbeef;
+ states[0].mode = 0777; // decimal 511, hex 0x000001ff
+ states[0].size = 0xababbcbc;
+ states[0].crc32 = 0x12345678;
+ states[0].nameLen = -12;
+ r.s = states[0];
+ filenames[0] = String8("bytes_of_padding");
+ snapshot.add(filenames[0], r);
+
+ states[1].modTime_sec = 0x93400031;
+ states[1].modTime_nsec = 0xdeadbeef;
+ states[1].mode = 0666; // decimal 438, hex 0x000001b6
+ states[1].size = 0x88557766;
+ states[1].crc32 = 0x22334422;
+ states[1].nameLen = -1;
+ r.s = states[1];
+ filenames[1] = String8("bytes_of_padding3");
+ snapshot.add(filenames[1], r);
+
+ states[2].modTime_sec = 0x33221144;
+ states[2].modTime_nsec = 0xdeadbeef;
+ states[2].mode = 0744; // decimal 484, hex 0x000001e4
+ states[2].size = 0x11223344;
+ states[2].crc32 = 0x01122334;
+ states[2].nameLen = 0;
+ r.s = states[2];
+ filenames[2] = String8("bytes_of_padding_2");
+ snapshot.add(filenames[2], r);
+
+ states[3].modTime_sec = 0x33221144;
+ states[3].modTime_nsec = 0xdeadbeef;
+ states[3].mode = 0755; // decimal 493, hex 0x000001ed
+ states[3].size = 0x11223344;
+ states[3].crc32 = 0x01122334;
+ states[3].nameLen = 0;
+ r.s = states[3];
+ filenames[3] = String8("bytes_of_padding__1");
+ snapshot.add(filenames[3], r);
+
+ err = write_snapshot_file(fd, snapshot);
+
+ close(fd);
+
+ if (err != 0) {
+ fprintf(stderr, "write_snapshot_file reported error %d (%s)\n", err, strerror(err));
+ return err;
+ }
+
+ static const unsigned char correct_data[] = {
+ // header
+ 0x53, 0x6e, 0x61, 0x70, 0x04, 0x00, 0x00, 0x00,
+ 0x46, 0x69, 0x6c, 0x65, 0xbc, 0x00, 0x00, 0x00,
+
+ // bytes_of_padding
+ 0x98, 0xba, 0xdc, 0xfe, 0xef, 0xbe, 0xad, 0xde,
+ 0xff, 0x01, 0x00, 0x00, 0xbc, 0xbc, 0xab, 0xab,
+ 0x78, 0x56, 0x34, 0x12, 0x10, 0x00, 0x00, 0x00,
+ 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x6f, 0x66,
+ 0x5f, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67,
+
+ // bytes_of_padding3
+ 0x31, 0x00, 0x40, 0x93, 0xef, 0xbe, 0xad, 0xde,
+ 0xb6, 0x01, 0x00, 0x00, 0x66, 0x77, 0x55, 0x88,
+ 0x22, 0x44, 0x33, 0x22, 0x11, 0x00, 0x00, 0x00,
+ 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x6f, 0x66,
+ 0x5f, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67,
+ 0x33, 0xab, 0xab, 0xab,
+
+ // bytes of padding2
+ 0x44, 0x11, 0x22, 0x33, 0xef, 0xbe, 0xad, 0xde,
+ 0xe4, 0x01, 0x00, 0x00, 0x44, 0x33, 0x22, 0x11,
+ 0x34, 0x23, 0x12, 0x01, 0x12, 0x00, 0x00, 0x00,
+ 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x6f, 0x66,
+ 0x5f, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67,
+ 0x5f, 0x32, 0xab, 0xab,
+
+ // bytes of padding3
+ 0x44, 0x11, 0x22, 0x33, 0xef, 0xbe, 0xad, 0xde,
+ 0xed, 0x01, 0x00, 0x00, 0x44, 0x33, 0x22, 0x11,
+ 0x34, 0x23, 0x12, 0x01, 0x13, 0x00, 0x00, 0x00,
+ 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x6f, 0x66,
+ 0x5f, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67,
+ 0x5f, 0x5f, 0x31, 0xab
+ };
+
+ err = compare_file(filename, correct_data, sizeof(correct_data));
+ if (err != 0) {
+ return err;
+ }
+
+ // read
+ fd = open(filename, O_RDONLY);
+ if (fd == -1) {
+ fprintf(stderr, "error opening for read %s\n", filename);
+ return 1;
+ }
+
+
+ KeyedVector<String8,FileState> readSnapshot;
+ err = read_snapshot_file(fd, &readSnapshot);
+ if (err != 0) {
+ fprintf(stderr, "read_snapshot_file failed %d\n", err);
+ return err;
+ }
+
+ if (readSnapshot.size() != 4) {
+ fprintf(stderr, "readSnapshot should be length 4 is %d\n", readSnapshot.size());
+ return 1;
+ }
+
+ bool matched = true;
+ for (size_t i=0; i<readSnapshot.size(); i++) {
+ const String8& name = readSnapshot.keyAt(i);
+ const FileState state = readSnapshot.valueAt(i);
+
+ if (name != filenames[i] || states[i].modTime_sec != state.modTime_sec
+ || states[i].modTime_nsec != state.modTime_nsec || states[i].mode != state.mode
+ || states[i].size != state.size || states[i].crc32 != states[i].crc32) {
+ fprintf(stderr, "state %d expected={%d/%d, 0x%08x, %04o, 0x%08x, %3d} '%s'\n"
+ " actual={%d/%d, 0x%08x, %04o, 0x%08x, %3d} '%s'\n", i,
+ states[i].modTime_sec, states[i].modTime_nsec, states[i].mode, states[i].size,
+ states[i].crc32, name.length(), filenames[i].string(),
+ state.modTime_sec, state.modTime_nsec, state.mode, state.size, state.crc32,
+ state.nameLen, name.string());
+ matched = false;
+ }
+ }
+
+ return matched ? 0 : 1;
+}
+
+// hexdump -v -e '" " 8/1 " 0x%02x," "\n"' data_writer.data
+const unsigned char DATA_GOLDEN_FILE[] = {
+ 0x44, 0x61, 0x74, 0x61, 0x0b, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x6e, 0x6f, 0x5f, 0x70,
+ 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x00,
+ 0x6e, 0x6f, 0x5f, 0x70, 0x61, 0x64, 0x64, 0x69,
+ 0x6e, 0x67, 0x5f, 0x00, 0x44, 0x61, 0x74, 0x61,
+ 0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
+ 0x70, 0x61, 0x64, 0x64, 0x65, 0x64, 0x5f, 0x74,
+ 0x6f, 0x5f, 0x5f, 0x33, 0x00, 0xbc, 0xbc, 0xbc,
+ 0x70, 0x61, 0x64, 0x64, 0x65, 0x64, 0x5f, 0x74,
+ 0x6f, 0x5f, 0x5f, 0x33, 0x00, 0xbc, 0xbc, 0xbc,
+ 0x44, 0x61, 0x74, 0x61, 0x0d, 0x00, 0x00, 0x00,
+ 0x0e, 0x00, 0x00, 0x00, 0x70, 0x61, 0x64, 0x64,
+ 0x65, 0x64, 0x5f, 0x74, 0x6f, 0x5f, 0x32, 0x5f,
+ 0x5f, 0x00, 0xbc, 0xbc, 0x70, 0x61, 0x64, 0x64,
+ 0x65, 0x64, 0x5f, 0x74, 0x6f, 0x5f, 0x32, 0x5f,
+ 0x5f, 0x00, 0xbc, 0xbc, 0x44, 0x61, 0x74, 0x61,
+ 0x0a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
+ 0x70, 0x61, 0x64, 0x64, 0x65, 0x64, 0x5f, 0x74,
+ 0x6f, 0x31, 0x00, 0xbc, 0x70, 0x61, 0x64, 0x64,
+ 0x65, 0x64, 0x5f, 0x74, 0x6f, 0x31, 0x00
+
+};
+const int DATA_GOLDEN_FILE_SIZE = sizeof(DATA_GOLDEN_FILE);
+
+static int
+test_write_header_and_entity(BackupDataWriter& writer, const char* str)
+{
+ int err;
+ String8 text(str);
+
+ err = writer.WriteEntityHeader(text, text.length()+1);
+ if (err != 0) {
+ fprintf(stderr, "WriteEntityHeader failed with %s\n", strerror(err));
+ return err;
+ }
+
+ err = writer.WriteEntityData(text.string(), text.length()+1);
+ if (err != 0) {
+ fprintf(stderr, "write failed for data '%s'\n", text.string());
+ return errno;
+ }
+
+ return err;
+}
+
+int
+backup_helper_test_data_writer()
+{
+ int err;
+ int fd;
+ const char* filename = SCRATCH_DIR "data_writer.data";
+
+ system("rm -r " SCRATCH_DIR);
+ mkdir(SCRATCH_DIR, 0777);
+ mkdir(SCRATCH_DIR "data", 0777);
+
+ fd = creat(filename, 0666);
+ if (fd == -1) {
+ fprintf(stderr, "error creating: %s\n", strerror(errno));
+ return errno;
+ }
+
+ BackupDataWriter writer(fd);
+
+ err = 0;
+ err |= test_write_header_and_entity(writer, "no_padding_");
+ err |= test_write_header_and_entity(writer, "padded_to__3");
+ err |= test_write_header_and_entity(writer, "padded_to_2__");
+ err |= test_write_header_and_entity(writer, "padded_to1");
+
+ close(fd);
+
+ err = compare_file(filename, DATA_GOLDEN_FILE, DATA_GOLDEN_FILE_SIZE);
+ if (err != 0) {
+ return err;
+ }
+
+ return err;
+}
+
+int
+test_read_header_and_entity(BackupDataReader& reader, const char* str)
+{
+ int err;
+ int bufSize = strlen(str)+1;
+ char* buf = (char*)malloc(bufSize);
+ String8 string;
+ int cookie = 0x11111111;
+ size_t actualSize;
+ bool done;
+ int type;
+ ssize_t nRead;
+
+ // printf("\n\n---------- test_read_header_and_entity -- %s\n\n", str);
+
+ err = reader.ReadNextHeader(&done, &type);
+ if (done) {
+ fprintf(stderr, "should not be done yet\n");
+ goto finished;
+ }
+ if (err != 0) {
+ fprintf(stderr, "ReadNextHeader (for app header) failed with %s\n", strerror(err));
+ goto finished;
+ }
+ if (type != BACKUP_HEADER_ENTITY_V1) {
+ err = EINVAL;
+ fprintf(stderr, "type=0x%08x expected 0x%08x\n", type, BACKUP_HEADER_ENTITY_V1);
+ }
+
+ err = reader.ReadEntityHeader(&string, &actualSize);
+ if (err != 0) {
+ fprintf(stderr, "ReadEntityHeader failed with %s\n", strerror(err));
+ goto finished;
+ }
+ if (string != str) {
+ fprintf(stderr, "ReadEntityHeader expected key '%s' got '%s'\n", str, string.string());
+ err = EINVAL;
+ goto finished;
+ }
+ if ((int)actualSize != bufSize) {
+ fprintf(stderr, "ReadEntityHeader expected dataSize 0x%08x got 0x%08x\n", bufSize,
+ actualSize);
+ err = EINVAL;
+ goto finished;
+ }
+
+ nRead = reader.ReadEntityData(buf, bufSize);
+ if (nRead < 0) {
+ err = reader.Status();
+ fprintf(stderr, "ReadEntityData failed with %s\n", strerror(err));
+ goto finished;
+ }
+
+ if (0 != memcmp(buf, str, bufSize)) {
+ fprintf(stderr, "ReadEntityData expected '%s' but got something starting with "
+ "%02x %02x %02x %02x '%c%c%c%c'\n", str, buf[0], buf[1], buf[2], buf[3],
+ buf[0], buf[1], buf[2], buf[3]);
+ err = EINVAL;
+ goto finished;
+ }
+
+ // The next read will confirm whether it got the right amount of data.
+
+finished:
+ if (err != NO_ERROR) {
+ fprintf(stderr, "test_read_header_and_entity failed with %s\n", strerror(err));
+ }
+ free(buf);
+ return err;
+}
+
+int
+backup_helper_test_data_reader()
+{
+ int err;
+ int fd;
+ const char* filename = SCRATCH_DIR "data_reader.data";
+
+ system("rm -r " SCRATCH_DIR);
+ mkdir(SCRATCH_DIR, 0777);
+ mkdir(SCRATCH_DIR "data", 0777);
+
+ fd = creat(filename, 0666);
+ if (fd == -1) {
+ fprintf(stderr, "error creating: %s\n", strerror(errno));
+ return errno;
+ }
+
+ err = write(fd, DATA_GOLDEN_FILE, DATA_GOLDEN_FILE_SIZE);
+ if (err != DATA_GOLDEN_FILE_SIZE) {
+ fprintf(stderr, "Error \"%s\" writing golden file %s\n", strerror(errno), filename);
+ return errno;
+ }
+
+ close(fd);
+
+ fd = open(filename, O_RDONLY);
+ if (fd == -1) {
+ fprintf(stderr, "Error \"%s\" opening golden file %s for read\n", strerror(errno),
+ filename);
+ return errno;
+ }
+
+ {
+ BackupDataReader reader(fd);
+
+ err = 0;
+
+ if (err == NO_ERROR) {
+ err = test_read_header_and_entity(reader, "no_padding_");
+ }
+
+ if (err == NO_ERROR) {
+ err = test_read_header_and_entity(reader, "padded_to__3");
+ }
+
+ if (err == NO_ERROR) {
+ err = test_read_header_and_entity(reader, "padded_to_2__");
+ }
+
+ if (err == NO_ERROR) {
+ err = test_read_header_and_entity(reader, "padded_to1");
+ }
+ }
+
+ close(fd);
+
+ return err;
+}
+
+static int
+get_mod_time(const char* filename, struct timeval times[2])
+{
+ int err;
+ struct stat64 st;
+ err = stat64(filename, &st);
+ if (err != 0) {
+ fprintf(stderr, "stat '%s' failed: %s\n", filename, strerror(errno));
+ return errno;
+ }
+ times[0].tv_sec = st.st_atime;
+ times[1].tv_sec = st.st_mtime;
+
+ // If st_atime is a macro then struct stat64 uses struct timespec
+ // to store the access and modif time values and typically
+ // st_*time_nsec is not defined. In glibc, this is controlled by
+ // __USE_MISC.
+#ifdef __USE_MISC
+#if !defined(st_atime) || defined(st_atime_nsec)
+#error "Check if this __USE_MISC conditional is still needed."
+#endif
+ times[0].tv_usec = st.st_atim.tv_nsec / 1000;
+ times[1].tv_usec = st.st_mtim.tv_nsec / 1000;
+#else
+ times[0].tv_usec = st.st_atime_nsec / 1000;
+ times[1].tv_usec = st.st_mtime_nsec / 1000;
+#endif
+
+ return 0;
+}
+
+int
+backup_helper_test_files()
+{
+ int err;
+ int oldSnapshotFD;
+ int dataStreamFD;
+ int newSnapshotFD;
+
+ system("rm -r " SCRATCH_DIR);
+ mkdir(SCRATCH_DIR, 0777);
+ mkdir(SCRATCH_DIR "data", 0777);
+
+ write_text_file(SCRATCH_DIR "data/b", "b\nbb\n");
+ write_text_file(SCRATCH_DIR "data/c", "c\ncc\n");
+ write_text_file(SCRATCH_DIR "data/d", "d\ndd\n");
+ write_text_file(SCRATCH_DIR "data/e", "e\nee\n");
+ write_text_file(SCRATCH_DIR "data/f", "f\nff\n");
+ write_text_file(SCRATCH_DIR "data/h", "h\nhh\n");
+
+ char const* files_before[] = {
+ SCRATCH_DIR "data/b",
+ SCRATCH_DIR "data/c",
+ SCRATCH_DIR "data/d",
+ SCRATCH_DIR "data/e",
+ SCRATCH_DIR "data/f"
+ };
+
+ char const* keys_before[] = {
+ "data/b",
+ "data/c",
+ "data/d",
+ "data/e",
+ "data/f"
+ };
+
+ dataStreamFD = creat(SCRATCH_DIR "1.data", 0666);
+ if (dataStreamFD == -1) {
+ fprintf(stderr, "error creating: %s\n", strerror(errno));
+ return errno;
+ }
+
+ newSnapshotFD = creat(SCRATCH_DIR "before.snap", 0666);
+ if (newSnapshotFD == -1) {
+ fprintf(stderr, "error creating: %s\n", strerror(errno));
+ return errno;
+ }
+
+ {
+ BackupDataWriter dataStream(dataStreamFD);
+
+ err = back_up_files(-1, &dataStream, newSnapshotFD, files_before, keys_before, 5);
+ if (err != 0) {
+ return err;
+ }
+ }
+
+ close(dataStreamFD);
+ close(newSnapshotFD);
+
+ sleep(3);
+
+ struct timeval d_times[2];
+ struct timeval e_times[2];
+
+ err = get_mod_time(SCRATCH_DIR "data/d", d_times);
+ err |= get_mod_time(SCRATCH_DIR "data/e", e_times);
+ if (err != 0) {
+ return err;
+ }
+
+ write_text_file(SCRATCH_DIR "data/a", "a\naa\n");
+ unlink(SCRATCH_DIR "data/c");
+ write_text_file(SCRATCH_DIR "data/c", "c\ncc\n");
+ write_text_file(SCRATCH_DIR "data/d", "dd\ndd\n");
+ utimes(SCRATCH_DIR "data/d", d_times);
+ write_text_file(SCRATCH_DIR "data/e", "z\nzz\n");
+ utimes(SCRATCH_DIR "data/e", e_times);
+ write_text_file(SCRATCH_DIR "data/g", "g\ngg\n");
+ unlink(SCRATCH_DIR "data/f");
+
+ char const* files_after[] = {
+ SCRATCH_DIR "data/a", // added
+ SCRATCH_DIR "data/b", // same
+ SCRATCH_DIR "data/c", // different mod time
+ SCRATCH_DIR "data/d", // different size (same mod time)
+ SCRATCH_DIR "data/e", // different contents (same mod time, same size)
+ SCRATCH_DIR "data/g" // added
+ };
+
+ char const* keys_after[] = {
+ "data/a", // added
+ "data/b", // same
+ "data/c", // different mod time
+ "data/d", // different size (same mod time)
+ "data/e", // different contents (same mod time, same size)
+ "data/g" // added
+ };
+
+ oldSnapshotFD = open(SCRATCH_DIR "before.snap", O_RDONLY);
+ if (oldSnapshotFD == -1) {
+ fprintf(stderr, "error opening: %s\n", strerror(errno));
+ return errno;
+ }
+
+ dataStreamFD = creat(SCRATCH_DIR "2.data", 0666);
+ if (dataStreamFD == -1) {
+ fprintf(stderr, "error creating: %s\n", strerror(errno));
+ return errno;
+ }
+
+ newSnapshotFD = creat(SCRATCH_DIR "after.snap", 0666);
+ if (newSnapshotFD == -1) {
+ fprintf(stderr, "error creating: %s\n", strerror(errno));
+ return errno;
+ }
+
+ {
+ BackupDataWriter dataStream(dataStreamFD);
+
+ err = back_up_files(oldSnapshotFD, &dataStream, newSnapshotFD, files_after, keys_after, 6);
+ if (err != 0) {
+ return err;
+ }
+}
+
+ close(oldSnapshotFD);
+ close(dataStreamFD);
+ close(newSnapshotFD);
+
+ return 0;
+}
+
+int
+backup_helper_test_null_base()
+{
+ int err;
+ int oldSnapshotFD;
+ int dataStreamFD;
+ int newSnapshotFD;
+
+ system("rm -r " SCRATCH_DIR);
+ mkdir(SCRATCH_DIR, 0777);
+ mkdir(SCRATCH_DIR "data", 0777);
+
+ write_text_file(SCRATCH_DIR "data/a", "a\naa\n");
+
+ char const* files[] = {
+ SCRATCH_DIR "data/a",
+ };
+
+ char const* keys[] = {
+ "a",
+ };
+
+ dataStreamFD = creat(SCRATCH_DIR "null_base.data", 0666);
+ if (dataStreamFD == -1) {
+ fprintf(stderr, "error creating: %s\n", strerror(errno));
+ return errno;
+ }
+
+ newSnapshotFD = creat(SCRATCH_DIR "null_base.snap", 0666);
+ if (newSnapshotFD == -1) {
+ fprintf(stderr, "error creating: %s\n", strerror(errno));
+ return errno;
+ }
+
+ {
+ BackupDataWriter dataStream(dataStreamFD);
+
+ err = back_up_files(-1, &dataStream, newSnapshotFD, files, keys, 1);
+ if (err != 0) {
+ return err;
+ }
+ }
+
+ close(dataStreamFD);
+ close(newSnapshotFD);
+
+ return 0;
+}
+
+int
+backup_helper_test_missing_file()
+{
+ int err;
+ int oldSnapshotFD;
+ int dataStreamFD;
+ int newSnapshotFD;
+
+ system("rm -r " SCRATCH_DIR);
+ mkdir(SCRATCH_DIR, 0777);
+ mkdir(SCRATCH_DIR "data", 0777);
+
+ write_text_file(SCRATCH_DIR "data/b", "b\nbb\n");
+
+ char const* files[] = {
+ SCRATCH_DIR "data/a",
+ SCRATCH_DIR "data/b",
+ SCRATCH_DIR "data/c",
+ };
+
+ char const* keys[] = {
+ "a",
+ "b",
+ "c",
+ };
+
+ dataStreamFD = creat(SCRATCH_DIR "null_base.data", 0666);
+ if (dataStreamFD == -1) {
+ fprintf(stderr, "error creating: %s\n", strerror(errno));
+ return errno;
+ }
+
+ newSnapshotFD = creat(SCRATCH_DIR "null_base.snap", 0666);
+ if (newSnapshotFD == -1) {
+ fprintf(stderr, "error creating: %s\n", strerror(errno));
+ return errno;
+ }
+
+ {
+ BackupDataWriter dataStream(dataStreamFD);
+
+ err = back_up_files(-1, &dataStream, newSnapshotFD, files, keys, 1);
+ if (err != 0) {
+ return err;
+ }
+ }
+
+ close(dataStreamFD);
+ close(newSnapshotFD);
+
+ return 0;
+}
+
+
+#endif // TEST_BACKUP_HELPERS
+
+}
diff --git a/libs/androidfw/CursorWindow.cpp b/libs/androidfw/CursorWindow.cpp
new file mode 100644
index 0000000..0f54edb
--- /dev/null
+++ b/libs/androidfw/CursorWindow.cpp
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 2006-2007 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "CursorWindow"
+
+#include <androidfw/CursorWindow.h>
+#include <binder/Parcel.h>
+#include <utils/Log.h>
+
+#include <cutils/ashmem.h>
+#include <sys/mman.h>
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+
+namespace android {
+
+CursorWindow::CursorWindow(const String8& name, int ashmemFd,
+ void* data, size_t size, bool readOnly) :
+ mName(name), mAshmemFd(ashmemFd), mData(data), mSize(size), mReadOnly(readOnly) {
+ mHeader = static_cast<Header*>(mData);
+}
+
+CursorWindow::~CursorWindow() {
+ ::munmap(mData, mSize);
+ ::close(mAshmemFd);
+}
+
+status_t CursorWindow::create(const String8& name, size_t size, CursorWindow** outCursorWindow) {
+ String8 ashmemName("CursorWindow: ");
+ ashmemName.append(name);
+
+ status_t result;
+ int ashmemFd = ashmem_create_region(ashmemName.string(), size);
+ if (ashmemFd < 0) {
+ result = -errno;
+ } else {
+ result = ashmem_set_prot_region(ashmemFd, PROT_READ | PROT_WRITE);
+ if (result >= 0) {
+ void* data = ::mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, ashmemFd, 0);
+ if (data == MAP_FAILED) {
+ result = -errno;
+ } else {
+ result = ashmem_set_prot_region(ashmemFd, PROT_READ);
+ if (result >= 0) {
+ CursorWindow* window = new CursorWindow(name, ashmemFd,
+ data, size, false /*readOnly*/);
+ result = window->clear();
+ if (!result) {
+ LOG_WINDOW("Created new CursorWindow: freeOffset=%d, "
+ "numRows=%d, numColumns=%d, mSize=%d, mData=%p",
+ window->mHeader->freeOffset,
+ window->mHeader->numRows,
+ window->mHeader->numColumns,
+ window->mSize, window->mData);
+ *outCursorWindow = window;
+ return OK;
+ }
+ delete window;
+ }
+ }
+ ::munmap(data, size);
+ }
+ ::close(ashmemFd);
+ }
+ *outCursorWindow = NULL;
+ return result;
+}
+
+status_t CursorWindow::createFromParcel(Parcel* parcel, CursorWindow** outCursorWindow) {
+ String8 name = parcel->readString8();
+
+ status_t result;
+ int ashmemFd = parcel->readFileDescriptor();
+ if (ashmemFd == int(BAD_TYPE)) {
+ result = BAD_TYPE;
+ } else {
+ ssize_t size = ashmem_get_size_region(ashmemFd);
+ if (size < 0) {
+ result = UNKNOWN_ERROR;
+ } else {
+ int dupAshmemFd = ::dup(ashmemFd);
+ if (dupAshmemFd < 0) {
+ result = -errno;
+ } else {
+ void* data = ::mmap(NULL, size, PROT_READ, MAP_SHARED, dupAshmemFd, 0);
+ if (data == MAP_FAILED) {
+ result = -errno;
+ } else {
+ CursorWindow* window = new CursorWindow(name, dupAshmemFd,
+ data, size, true /*readOnly*/);
+ LOG_WINDOW("Created CursorWindow from parcel: freeOffset=%d, "
+ "numRows=%d, numColumns=%d, mSize=%d, mData=%p",
+ window->mHeader->freeOffset,
+ window->mHeader->numRows,
+ window->mHeader->numColumns,
+ window->mSize, window->mData);
+ *outCursorWindow = window;
+ return OK;
+ }
+ ::close(dupAshmemFd);
+ }
+ }
+ }
+ *outCursorWindow = NULL;
+ return result;
+}
+
+status_t CursorWindow::writeToParcel(Parcel* parcel) {
+ status_t status = parcel->writeString8(mName);
+ if (!status) {
+ status = parcel->writeDupFileDescriptor(mAshmemFd);
+ }
+ return status;
+}
+
+status_t CursorWindow::clear() {
+ if (mReadOnly) {
+ return INVALID_OPERATION;
+ }
+
+ mHeader->freeOffset = sizeof(Header) + sizeof(RowSlotChunk);
+ mHeader->firstChunkOffset = sizeof(Header);
+ mHeader->numRows = 0;
+ mHeader->numColumns = 0;
+
+ RowSlotChunk* firstChunk = static_cast<RowSlotChunk*>(offsetToPtr(mHeader->firstChunkOffset));
+ firstChunk->nextChunkOffset = 0;
+ return OK;
+}
+
+status_t CursorWindow::setNumColumns(uint32_t numColumns) {
+ if (mReadOnly) {
+ return INVALID_OPERATION;
+ }
+
+ uint32_t cur = mHeader->numColumns;
+ if ((cur > 0 || mHeader->numRows > 0) && cur != numColumns) {
+ ALOGE("Trying to go from %d columns to %d", cur, numColumns);
+ return INVALID_OPERATION;
+ }
+ mHeader->numColumns = numColumns;
+ return OK;
+}
+
+status_t CursorWindow::allocRow() {
+ if (mReadOnly) {
+ return INVALID_OPERATION;
+ }
+
+ // Fill in the row slot
+ RowSlot* rowSlot = allocRowSlot();
+ if (rowSlot == NULL) {
+ return NO_MEMORY;
+ }
+
+ // Allocate the slots for the field directory
+ size_t fieldDirSize = mHeader->numColumns * sizeof(FieldSlot);
+ uint32_t fieldDirOffset = alloc(fieldDirSize, true /*aligned*/);
+ if (!fieldDirOffset) {
+ mHeader->numRows--;
+ LOG_WINDOW("The row failed, so back out the new row accounting "
+ "from allocRowSlot %d", mHeader->numRows);
+ return NO_MEMORY;
+ }
+ FieldSlot* fieldDir = static_cast<FieldSlot*>(offsetToPtr(fieldDirOffset));
+ memset(fieldDir, 0, fieldDirSize);
+
+ LOG_WINDOW("Allocated row %u, rowSlot is at offset %u, fieldDir is %d bytes at offset %u\n",
+ mHeader->numRows - 1, offsetFromPtr(rowSlot), fieldDirSize, fieldDirOffset);
+ rowSlot->offset = fieldDirOffset;
+ return OK;
+}
+
+status_t CursorWindow::freeLastRow() {
+ if (mReadOnly) {
+ return INVALID_OPERATION;
+ }
+
+ if (mHeader->numRows > 0) {
+ mHeader->numRows--;
+ }
+ return OK;
+}
+
+uint32_t CursorWindow::alloc(size_t size, bool aligned) {
+ uint32_t padding;
+ if (aligned) {
+ // 4 byte alignment
+ padding = (~mHeader->freeOffset + 1) & 3;
+ } else {
+ padding = 0;
+ }
+
+ uint32_t offset = mHeader->freeOffset + padding;
+ uint32_t nextFreeOffset = offset + size;
+ if (nextFreeOffset > mSize) {
+ ALOGW("Window is full: requested allocation %d bytes, "
+ "free space %d bytes, window size %d bytes",
+ size, freeSpace(), mSize);
+ return 0;
+ }
+
+ mHeader->freeOffset = nextFreeOffset;
+ return offset;
+}
+
+CursorWindow::RowSlot* CursorWindow::getRowSlot(uint32_t row) {
+ uint32_t chunkPos = row;
+ RowSlotChunk* chunk = static_cast<RowSlotChunk*>(
+ offsetToPtr(mHeader->firstChunkOffset));
+ while (chunkPos >= ROW_SLOT_CHUNK_NUM_ROWS) {
+ chunk = static_cast<RowSlotChunk*>(offsetToPtr(chunk->nextChunkOffset));
+ chunkPos -= ROW_SLOT_CHUNK_NUM_ROWS;
+ }
+ return &chunk->slots[chunkPos];
+}
+
+CursorWindow::RowSlot* CursorWindow::allocRowSlot() {
+ uint32_t chunkPos = mHeader->numRows;
+ RowSlotChunk* chunk = static_cast<RowSlotChunk*>(
+ offsetToPtr(mHeader->firstChunkOffset));
+ while (chunkPos > ROW_SLOT_CHUNK_NUM_ROWS) {
+ chunk = static_cast<RowSlotChunk*>(offsetToPtr(chunk->nextChunkOffset));
+ chunkPos -= ROW_SLOT_CHUNK_NUM_ROWS;
+ }
+ if (chunkPos == ROW_SLOT_CHUNK_NUM_ROWS) {
+ if (!chunk->nextChunkOffset) {
+ chunk->nextChunkOffset = alloc(sizeof(RowSlotChunk), true /*aligned*/);
+ if (!chunk->nextChunkOffset) {
+ return NULL;
+ }
+ }
+ chunk = static_cast<RowSlotChunk*>(offsetToPtr(chunk->nextChunkOffset));
+ chunk->nextChunkOffset = 0;
+ chunkPos = 0;
+ }
+ mHeader->numRows += 1;
+ return &chunk->slots[chunkPos];
+}
+
+CursorWindow::FieldSlot* CursorWindow::getFieldSlot(uint32_t row, uint32_t column) {
+ if (row >= mHeader->numRows || column >= mHeader->numColumns) {
+ ALOGE("Failed to read row %d, column %d from a CursorWindow which "
+ "has %d rows, %d columns.",
+ row, column, mHeader->numRows, mHeader->numColumns);
+ return NULL;
+ }
+ RowSlot* rowSlot = getRowSlot(row);
+ if (!rowSlot) {
+ ALOGE("Failed to find rowSlot for row %d.", row);
+ return NULL;
+ }
+ FieldSlot* fieldDir = static_cast<FieldSlot*>(offsetToPtr(rowSlot->offset));
+ return &fieldDir[column];
+}
+
+status_t CursorWindow::putBlob(uint32_t row, uint32_t column, const void* value, size_t size) {
+ return putBlobOrString(row, column, value, size, FIELD_TYPE_BLOB);
+}
+
+status_t CursorWindow::putString(uint32_t row, uint32_t column, const char* value,
+ size_t sizeIncludingNull) {
+ return putBlobOrString(row, column, value, sizeIncludingNull, FIELD_TYPE_STRING);
+}
+
+status_t CursorWindow::putBlobOrString(uint32_t row, uint32_t column,
+ const void* value, size_t size, int32_t type) {
+ if (mReadOnly) {
+ return INVALID_OPERATION;
+ }
+
+ FieldSlot* fieldSlot = getFieldSlot(row, column);
+ if (!fieldSlot) {
+ return BAD_VALUE;
+ }
+
+ uint32_t offset = alloc(size);
+ if (!offset) {
+ return NO_MEMORY;
+ }
+
+ memcpy(offsetToPtr(offset), value, size);
+
+ fieldSlot->type = type;
+ fieldSlot->data.buffer.offset = offset;
+ fieldSlot->data.buffer.size = size;
+ return OK;
+}
+
+status_t CursorWindow::putLong(uint32_t row, uint32_t column, int64_t value) {
+ if (mReadOnly) {
+ return INVALID_OPERATION;
+ }
+
+ FieldSlot* fieldSlot = getFieldSlot(row, column);
+ if (!fieldSlot) {
+ return BAD_VALUE;
+ }
+
+ fieldSlot->type = FIELD_TYPE_INTEGER;
+ fieldSlot->data.l = value;
+ return OK;
+}
+
+status_t CursorWindow::putDouble(uint32_t row, uint32_t column, double value) {
+ if (mReadOnly) {
+ return INVALID_OPERATION;
+ }
+
+ FieldSlot* fieldSlot = getFieldSlot(row, column);
+ if (!fieldSlot) {
+ return BAD_VALUE;
+ }
+
+ fieldSlot->type = FIELD_TYPE_FLOAT;
+ fieldSlot->data.d = value;
+ return OK;
+}
+
+status_t CursorWindow::putNull(uint32_t row, uint32_t column) {
+ if (mReadOnly) {
+ return INVALID_OPERATION;
+ }
+
+ FieldSlot* fieldSlot = getFieldSlot(row, column);
+ if (!fieldSlot) {
+ return BAD_VALUE;
+ }
+
+ fieldSlot->type = FIELD_TYPE_NULL;
+ fieldSlot->data.buffer.offset = 0;
+ fieldSlot->data.buffer.size = 0;
+ return OK;
+}
+
+}; // namespace android
diff --git a/libs/androidfw/MODULE_LICENSE_APACHE2 b/libs/androidfw/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/libs/androidfw/MODULE_LICENSE_APACHE2
diff --git a/libs/androidfw/NOTICE b/libs/androidfw/NOTICE
new file mode 100644
index 0000000..c5b1efa
--- /dev/null
+++ b/libs/androidfw/NOTICE
@@ -0,0 +1,190 @@
+
+ Copyright (c) 2005-2008, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+
+ 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.
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
diff --git a/libs/androidfw/ObbFile.cpp b/libs/androidfw/ObbFile.cpp
new file mode 100644
index 0000000..21e06c8
--- /dev/null
+++ b/libs/androidfw/ObbFile.cpp
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define LOG_TAG "ObbFile"
+
+#include <androidfw/ObbFile.h>
+#include <utils/Compat.h>
+#include <utils/Log.h>
+
+//#define DEBUG 1
+
+#define kFooterTagSize 8 /* last two 32-bit integers */
+
+#define kFooterMinSize 33 /* 32-bit signature version (4 bytes)
+ * 32-bit package version (4 bytes)
+ * 32-bit flags (4 bytes)
+ * 64-bit salt (8 bytes)
+ * 32-bit package name size (4 bytes)
+ * >=1-character package name (1 byte)
+ * 32-bit footer size (4 bytes)
+ * 32-bit footer marker (4 bytes)
+ */
+
+#define kMaxBufSize 32768 /* Maximum file read buffer */
+
+#define kSignature 0x01059983U /* ObbFile signature */
+
+#define kSigVersion 1 /* We only know about signature version 1 */
+
+/* offsets in version 1 of the header */
+#define kPackageVersionOffset 4
+#define kFlagsOffset 8
+#define kSaltOffset 12
+#define kPackageNameLenOffset 20
+#define kPackageNameOffset 24
+
+/*
+ * TEMP_FAILURE_RETRY is defined by some, but not all, versions of
+ * <unistd.h>. (Alas, it is not as standard as we'd hoped!) So, if it's
+ * not already defined, then define it here.
+ */
+#ifndef TEMP_FAILURE_RETRY
+/* Used to retry syscalls that can return EINTR. */
+#define TEMP_FAILURE_RETRY(exp) ({ \
+ typeof (exp) _rc; \
+ do { \
+ _rc = (exp); \
+ } while (_rc == -1 && errno == EINTR); \
+ _rc; })
+#endif
+
+
+namespace android {
+
+ObbFile::ObbFile()
+ : mPackageName("")
+ , mVersion(-1)
+ , mFlags(0)
+{
+ memset(mSalt, 0, sizeof(mSalt));
+}
+
+ObbFile::~ObbFile() {
+}
+
+bool ObbFile::readFrom(const char* filename)
+{
+ int fd;
+ bool success = false;
+
+ fd = ::open(filename, O_RDONLY);
+ if (fd < 0) {
+ ALOGW("couldn't open file %s: %s", filename, strerror(errno));
+ goto out;
+ }
+ success = readFrom(fd);
+ close(fd);
+
+ if (!success) {
+ ALOGW("failed to read from %s (fd=%d)\n", filename, fd);
+ }
+
+out:
+ return success;
+}
+
+bool ObbFile::readFrom(int fd)
+{
+ if (fd < 0) {
+ ALOGW("attempt to read from invalid fd\n");
+ return false;
+ }
+
+ return parseObbFile(fd);
+}
+
+bool ObbFile::parseObbFile(int fd)
+{
+ off64_t fileLength = lseek64(fd, 0, SEEK_END);
+
+ if (fileLength < kFooterMinSize) {
+ if (fileLength < 0) {
+ ALOGW("error seeking in ObbFile: %s\n", strerror(errno));
+ } else {
+ ALOGW("file is only %lld (less than %d minimum)\n", fileLength, kFooterMinSize);
+ }
+ return false;
+ }
+
+ ssize_t actual;
+ size_t footerSize;
+
+ {
+ lseek64(fd, fileLength - kFooterTagSize, SEEK_SET);
+
+ char *footer = new char[kFooterTagSize];
+ actual = TEMP_FAILURE_RETRY(read(fd, footer, kFooterTagSize));
+ if (actual != kFooterTagSize) {
+ ALOGW("couldn't read footer signature: %s\n", strerror(errno));
+ return false;
+ }
+
+ unsigned int fileSig = get4LE((unsigned char*)footer + sizeof(int32_t));
+ if (fileSig != kSignature) {
+ ALOGW("footer didn't match magic string (expected 0x%08x; got 0x%08x)\n",
+ kSignature, fileSig);
+ return false;
+ }
+
+ footerSize = get4LE((unsigned char*)footer);
+ if (footerSize > (size_t)fileLength - kFooterTagSize
+ || footerSize > kMaxBufSize) {
+ ALOGW("claimed footer size is too large (0x%08zx; file size is 0x%08llx)\n",
+ footerSize, fileLength);
+ return false;
+ }
+
+ if (footerSize < (kFooterMinSize - kFooterTagSize)) {
+ ALOGW("claimed footer size is too small (0x%zx; minimum size is 0x%x)\n",
+ footerSize, kFooterMinSize - kFooterTagSize);
+ return false;
+ }
+ }
+
+ off64_t fileOffset = fileLength - footerSize - kFooterTagSize;
+ if (lseek64(fd, fileOffset, SEEK_SET) != fileOffset) {
+ ALOGW("seek %lld failed: %s\n", fileOffset, strerror(errno));
+ return false;
+ }
+
+ mFooterStart = fileOffset;
+
+ char* scanBuf = (char*)malloc(footerSize);
+ if (scanBuf == NULL) {
+ ALOGW("couldn't allocate scanBuf: %s\n", strerror(errno));
+ return false;
+ }
+
+ actual = TEMP_FAILURE_RETRY(read(fd, scanBuf, footerSize));
+ // readAmount is guaranteed to be less than kMaxBufSize
+ if (actual != (ssize_t)footerSize) {
+ ALOGI("couldn't read ObbFile footer: %s\n", strerror(errno));
+ free(scanBuf);
+ return false;
+ }
+
+#ifdef DEBUG
+ for (int i = 0; i < footerSize; ++i) {
+ ALOGI("char: 0x%02x\n", scanBuf[i]);
+ }
+#endif
+
+ uint32_t sigVersion = get4LE((unsigned char*)scanBuf);
+ if (sigVersion != kSigVersion) {
+ ALOGW("Unsupported ObbFile version %d\n", sigVersion);
+ free(scanBuf);
+ return false;
+ }
+
+ mVersion = (int32_t) get4LE((unsigned char*)scanBuf + kPackageVersionOffset);
+ mFlags = (int32_t) get4LE((unsigned char*)scanBuf + kFlagsOffset);
+
+ memcpy(&mSalt, (unsigned char*)scanBuf + kSaltOffset, sizeof(mSalt));
+
+ size_t packageNameLen = get4LE((unsigned char*)scanBuf + kPackageNameLenOffset);
+ if (packageNameLen == 0
+ || packageNameLen > (footerSize - kPackageNameOffset)) {
+ ALOGW("bad ObbFile package name length (0x%04zx; 0x%04zx possible)\n",
+ packageNameLen, footerSize - kPackageNameOffset);
+ free(scanBuf);
+ return false;
+ }
+
+ char* packageName = reinterpret_cast<char*>(scanBuf + kPackageNameOffset);
+ mPackageName = String8(const_cast<char*>(packageName), packageNameLen);
+
+ free(scanBuf);
+
+#ifdef DEBUG
+ ALOGI("Obb scan succeeded: packageName=%s, version=%d\n", mPackageName.string(), mVersion);
+#endif
+
+ return true;
+}
+
+bool ObbFile::writeTo(const char* filename)
+{
+ int fd;
+ bool success = false;
+
+ fd = ::open(filename, O_WRONLY);
+ if (fd < 0) {
+ goto out;
+ }
+ success = writeTo(fd);
+ close(fd);
+
+out:
+ if (!success) {
+ ALOGW("failed to write to %s: %s\n", filename, strerror(errno));
+ }
+ return success;
+}
+
+bool ObbFile::writeTo(int fd)
+{
+ if (fd < 0) {
+ return false;
+ }
+
+ lseek64(fd, 0, SEEK_END);
+
+ if (mPackageName.size() == 0 || mVersion == -1) {
+ ALOGW("tried to write uninitialized ObbFile data\n");
+ return false;
+ }
+
+ unsigned char intBuf[sizeof(uint32_t)+1];
+ memset(&intBuf, 0, sizeof(intBuf));
+
+ put4LE(intBuf, kSigVersion);
+ if (write(fd, &intBuf, sizeof(uint32_t)) != (ssize_t)sizeof(uint32_t)) {
+ ALOGW("couldn't write signature version: %s\n", strerror(errno));
+ return false;
+ }
+
+ put4LE(intBuf, mVersion);
+ if (write(fd, &intBuf, sizeof(uint32_t)) != (ssize_t)sizeof(uint32_t)) {
+ ALOGW("couldn't write package version\n");
+ return false;
+ }
+
+ put4LE(intBuf, mFlags);
+ if (write(fd, &intBuf, sizeof(uint32_t)) != (ssize_t)sizeof(uint32_t)) {
+ ALOGW("couldn't write package version\n");
+ return false;
+ }
+
+ if (write(fd, mSalt, sizeof(mSalt)) != (ssize_t)sizeof(mSalt)) {
+ ALOGW("couldn't write salt: %s\n", strerror(errno));
+ return false;
+ }
+
+ size_t packageNameLen = mPackageName.size();
+ put4LE(intBuf, packageNameLen);
+ if (write(fd, &intBuf, sizeof(uint32_t)) != (ssize_t)sizeof(uint32_t)) {
+ ALOGW("couldn't write package name length: %s\n", strerror(errno));
+ return false;
+ }
+
+ if (write(fd, mPackageName.string(), packageNameLen) != (ssize_t)packageNameLen) {
+ ALOGW("couldn't write package name: %s\n", strerror(errno));
+ return false;
+ }
+
+ put4LE(intBuf, kPackageNameOffset + packageNameLen);
+ if (write(fd, &intBuf, sizeof(uint32_t)) != (ssize_t)sizeof(uint32_t)) {
+ ALOGW("couldn't write footer size: %s\n", strerror(errno));
+ return false;
+ }
+
+ put4LE(intBuf, kSignature);
+ if (write(fd, &intBuf, sizeof(uint32_t)) != (ssize_t)sizeof(uint32_t)) {
+ ALOGW("couldn't write footer magic signature: %s\n", strerror(errno));
+ return false;
+ }
+
+ return true;
+}
+
+bool ObbFile::removeFrom(const char* filename)
+{
+ int fd;
+ bool success = false;
+
+ fd = ::open(filename, O_RDWR);
+ if (fd < 0) {
+ goto out;
+ }
+ success = removeFrom(fd);
+ close(fd);
+
+out:
+ if (!success) {
+ ALOGW("failed to remove signature from %s: %s\n", filename, strerror(errno));
+ }
+ return success;
+}
+
+bool ObbFile::removeFrom(int fd)
+{
+ if (fd < 0) {
+ return false;
+ }
+
+ if (!readFrom(fd)) {
+ return false;
+ }
+
+ ftruncate(fd, mFooterStart);
+
+ return true;
+}
+
+}
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
new file mode 100644
index 0000000..1cc3563
--- /dev/null
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -0,0 +1,5796 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "ResourceType"
+//#define LOG_NDEBUG 0
+
+#include <androidfw/ResourceTypes.h>
+#include <utils/Atomic.h>
+#include <utils/ByteOrder.h>
+#include <utils/Debug.h>
+#include <utils/Log.h>
+#include <utils/String16.h>
+#include <utils/String8.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <memory.h>
+#include <ctype.h>
+#include <stdint.h>
+
+#ifndef INT32_MAX
+#define INT32_MAX ((int32_t)(2147483647))
+#endif
+
+#define STRING_POOL_NOISY(x) //x
+#define XML_NOISY(x) //x
+#define TABLE_NOISY(x) //x
+#define TABLE_GETENTRY(x) //x
+#define TABLE_SUPER_NOISY(x) //x
+#define LOAD_TABLE_NOISY(x) //x
+#define TABLE_THEME(x) //x
+
+namespace android {
+
+#ifdef HAVE_WINSOCK
+#undef nhtol
+#undef htonl
+
+#ifdef HAVE_LITTLE_ENDIAN
+#define ntohl(x) ( ((x) << 24) | (((x) >> 24) & 255) | (((x) << 8) & 0xff0000) | (((x) >> 8) & 0xff00) )
+#define htonl(x) ntohl(x)
+#define ntohs(x) ( (((x) << 8) & 0xff00) | (((x) >> 8) & 255) )
+#define htons(x) ntohs(x)
+#else
+#define ntohl(x) (x)
+#define htonl(x) (x)
+#define ntohs(x) (x)
+#define htons(x) (x)
+#endif
+#endif
+
+#define IDMAP_MAGIC 0x706d6469
+// size measured in sizeof(uint32_t)
+#define IDMAP_HEADER_SIZE (ResTable::IDMAP_HEADER_SIZE_BYTES / sizeof(uint32_t))
+
+static void printToLogFunc(void* cookie, const char* txt)
+{
+ ALOGV("%s", txt);
+}
+
+// Standard C isspace() is only required to look at the low byte of its input, so
+// produces incorrect results for UTF-16 characters. For safety's sake, assume that
+// any high-byte UTF-16 code point is not whitespace.
+inline int isspace16(char16_t c) {
+ return (c < 0x0080 && isspace(c));
+}
+
+// range checked; guaranteed to NUL-terminate within the stated number of available slots
+// NOTE: if this truncates the dst string due to running out of space, no attempt is
+// made to avoid splitting surrogate pairs.
+static void strcpy16_dtoh(uint16_t* dst, const uint16_t* src, size_t avail)
+{
+ uint16_t* last = dst + avail - 1;
+ while (*src && (dst < last)) {
+ char16_t s = dtohs(*src);
+ *dst++ = s;
+ src++;
+ }
+ *dst = 0;
+}
+
+static status_t validate_chunk(const ResChunk_header* chunk,
+ size_t minSize,
+ const uint8_t* dataEnd,
+ const char* name)
+{
+ const uint16_t headerSize = dtohs(chunk->headerSize);
+ const uint32_t size = dtohl(chunk->size);
+
+ if (headerSize >= minSize) {
+ if (headerSize <= size) {
+ if (((headerSize|size)&0x3) == 0) {
+ if ((ssize_t)size <= (dataEnd-((const uint8_t*)chunk))) {
+ return NO_ERROR;
+ }
+ ALOGW("%s data size %p extends beyond resource end %p.",
+ name, (void*)size,
+ (void*)(dataEnd-((const uint8_t*)chunk)));
+ return BAD_TYPE;
+ }
+ ALOGW("%s size 0x%x or headerSize 0x%x is not on an integer boundary.",
+ name, (int)size, (int)headerSize);
+ return BAD_TYPE;
+ }
+ ALOGW("%s size %p is smaller than header size %p.",
+ name, (void*)size, (void*)(int)headerSize);
+ return BAD_TYPE;
+ }
+ ALOGW("%s header size %p is too small.",
+ name, (void*)(int)headerSize);
+ return BAD_TYPE;
+}
+
+inline void Res_value::copyFrom_dtoh(const Res_value& src)
+{
+ size = dtohs(src.size);
+ res0 = src.res0;
+ dataType = src.dataType;
+ data = dtohl(src.data);
+}
+
+void Res_png_9patch::deviceToFile()
+{
+ for (int i = 0; i < numXDivs; i++) {
+ xDivs[i] = htonl(xDivs[i]);
+ }
+ for (int i = 0; i < numYDivs; i++) {
+ yDivs[i] = htonl(yDivs[i]);
+ }
+ paddingLeft = htonl(paddingLeft);
+ paddingRight = htonl(paddingRight);
+ paddingTop = htonl(paddingTop);
+ paddingBottom = htonl(paddingBottom);
+ for (int i=0; i<numColors; i++) {
+ colors[i] = htonl(colors[i]);
+ }
+}
+
+void Res_png_9patch::fileToDevice()
+{
+ for (int i = 0; i < numXDivs; i++) {
+ xDivs[i] = ntohl(xDivs[i]);
+ }
+ for (int i = 0; i < numYDivs; i++) {
+ yDivs[i] = ntohl(yDivs[i]);
+ }
+ paddingLeft = ntohl(paddingLeft);
+ paddingRight = ntohl(paddingRight);
+ paddingTop = ntohl(paddingTop);
+ paddingBottom = ntohl(paddingBottom);
+ for (int i=0; i<numColors; i++) {
+ colors[i] = ntohl(colors[i]);
+ }
+}
+
+size_t Res_png_9patch::serializedSize()
+{
+ // The size of this struct is 32 bytes on the 32-bit target system
+ // 4 * int8_t
+ // 4 * int32_t
+ // 3 * pointer
+ return 32
+ + numXDivs * sizeof(int32_t)
+ + numYDivs * sizeof(int32_t)
+ + numColors * sizeof(uint32_t);
+}
+
+void* Res_png_9patch::serialize()
+{
+ // Use calloc since we're going to leave a few holes in the data
+ // and want this to run cleanly under valgrind
+ void* newData = calloc(1, serializedSize());
+ serialize(newData);
+ return newData;
+}
+
+void Res_png_9patch::serialize(void * outData)
+{
+ char* data = (char*) outData;
+ memmove(data, &wasDeserialized, 4); // copy wasDeserialized, numXDivs, numYDivs, numColors
+ memmove(data + 12, &paddingLeft, 16); // copy paddingXXXX
+ data += 32;
+
+ memmove(data, this->xDivs, numXDivs * sizeof(int32_t));
+ data += numXDivs * sizeof(int32_t);
+ memmove(data, this->yDivs, numYDivs * sizeof(int32_t));
+ data += numYDivs * sizeof(int32_t);
+ memmove(data, this->colors, numColors * sizeof(uint32_t));
+}
+
+static void deserializeInternal(const void* inData, Res_png_9patch* outData) {
+ char* patch = (char*) inData;
+ if (inData != outData) {
+ memmove(&outData->wasDeserialized, patch, 4); // copy wasDeserialized, numXDivs, numYDivs, numColors
+ memmove(&outData->paddingLeft, patch + 12, 4); // copy wasDeserialized, numXDivs, numYDivs, numColors
+ }
+ outData->wasDeserialized = true;
+ char* data = (char*)outData;
+ data += sizeof(Res_png_9patch);
+ outData->xDivs = (int32_t*) data;
+ data += outData->numXDivs * sizeof(int32_t);
+ outData->yDivs = (int32_t*) data;
+ data += outData->numYDivs * sizeof(int32_t);
+ outData->colors = (uint32_t*) data;
+}
+
+static bool assertIdmapHeader(const uint32_t* map, size_t sizeBytes)
+{
+ if (sizeBytes < ResTable::IDMAP_HEADER_SIZE_BYTES) {
+ ALOGW("idmap assertion failed: size=%d bytes\n", (int)sizeBytes);
+ return false;
+ }
+ if (*map != htodl(IDMAP_MAGIC)) { // htodl: map data expected to be in correct endianess
+ ALOGW("idmap assertion failed: invalid magic found (is 0x%08x, expected 0x%08x)\n",
+ *map, htodl(IDMAP_MAGIC));
+ return false;
+ }
+ return true;
+}
+
+static status_t idmapLookup(const uint32_t* map, size_t sizeBytes, uint32_t key, uint32_t* outValue)
+{
+ // see README for details on the format of map
+ if (!assertIdmapHeader(map, sizeBytes)) {
+ return UNKNOWN_ERROR;
+ }
+ map = map + IDMAP_HEADER_SIZE; // skip ahead to data segment
+ // size of data block, in uint32_t
+ const size_t size = (sizeBytes - ResTable::IDMAP_HEADER_SIZE_BYTES) / sizeof(uint32_t);
+ const uint32_t type = Res_GETTYPE(key) + 1; // add one, idmap stores "public" type id
+ const uint32_t entry = Res_GETENTRY(key);
+ const uint32_t typeCount = *map;
+
+ if (type > typeCount) {
+ ALOGW("Resource ID map: type=%d exceeds number of types=%d\n", type, typeCount);
+ return UNKNOWN_ERROR;
+ }
+ if (typeCount > size) {
+ ALOGW("Resource ID map: number of types=%d exceeds size of map=%d\n", typeCount, (int)size);
+ return UNKNOWN_ERROR;
+ }
+ const uint32_t typeOffset = map[type];
+ if (typeOffset == 0) {
+ *outValue = 0;
+ return NO_ERROR;
+ }
+ if (typeOffset + 1 > size) {
+ ALOGW("Resource ID map: type offset=%d exceeds reasonable value, size of map=%d\n",
+ typeOffset, (int)size);
+ return UNKNOWN_ERROR;
+ }
+ const uint32_t entryCount = map[typeOffset];
+ const uint32_t entryOffset = map[typeOffset + 1];
+ if (entryCount == 0 || entry < entryOffset || entry - entryOffset > entryCount - 1) {
+ *outValue = 0;
+ return NO_ERROR;
+ }
+ const uint32_t index = typeOffset + 2 + entry - entryOffset;
+ if (index > size) {
+ ALOGW("Resource ID map: entry index=%d exceeds size of map=%d\n", index, (int)size);
+ *outValue = 0;
+ return NO_ERROR;
+ }
+ *outValue = map[index];
+
+ return NO_ERROR;
+}
+
+static status_t getIdmapPackageId(const uint32_t* map, size_t mapSize, uint32_t *outId)
+{
+ if (!assertIdmapHeader(map, mapSize)) {
+ return UNKNOWN_ERROR;
+ }
+ const uint32_t* p = map + IDMAP_HEADER_SIZE + 1;
+ while (*p == 0) {
+ ++p;
+ }
+ *outId = (map[*p + IDMAP_HEADER_SIZE + 2] >> 24) & 0x000000ff;
+ return NO_ERROR;
+}
+
+Res_png_9patch* Res_png_9patch::deserialize(const void* inData)
+{
+ if (sizeof(void*) != sizeof(int32_t)) {
+ ALOGE("Cannot deserialize on non 32-bit system\n");
+ return NULL;
+ }
+ deserializeInternal(inData, (Res_png_9patch*) inData);
+ return (Res_png_9patch*) inData;
+}
+
+// --------------------------------------------------------------------
+// --------------------------------------------------------------------
+// --------------------------------------------------------------------
+
+ResStringPool::ResStringPool()
+ : mError(NO_INIT), mOwnedData(NULL), mHeader(NULL), mCache(NULL)
+{
+}
+
+ResStringPool::ResStringPool(const void* data, size_t size, bool copyData)
+ : mError(NO_INIT), mOwnedData(NULL), mHeader(NULL), mCache(NULL)
+{
+ setTo(data, size, copyData);
+}
+
+ResStringPool::~ResStringPool()
+{
+ uninit();
+}
+
+status_t ResStringPool::setTo(const void* data, size_t size, bool copyData)
+{
+ if (!data || !size) {
+ return (mError=BAD_TYPE);
+ }
+
+ uninit();
+
+ const bool notDeviceEndian = htods(0xf0) != 0xf0;
+
+ if (copyData || notDeviceEndian) {
+ mOwnedData = malloc(size);
+ if (mOwnedData == NULL) {
+ return (mError=NO_MEMORY);
+ }
+ memcpy(mOwnedData, data, size);
+ data = mOwnedData;
+ }
+
+ mHeader = (const ResStringPool_header*)data;
+
+ if (notDeviceEndian) {
+ ResStringPool_header* h = const_cast<ResStringPool_header*>(mHeader);
+ h->header.headerSize = dtohs(mHeader->header.headerSize);
+ h->header.type = dtohs(mHeader->header.type);
+ h->header.size = dtohl(mHeader->header.size);
+ h->stringCount = dtohl(mHeader->stringCount);
+ h->styleCount = dtohl(mHeader->styleCount);
+ h->flags = dtohl(mHeader->flags);
+ h->stringsStart = dtohl(mHeader->stringsStart);
+ h->stylesStart = dtohl(mHeader->stylesStart);
+ }
+
+ if (mHeader->header.headerSize > mHeader->header.size
+ || mHeader->header.size > size) {
+ ALOGW("Bad string block: header size %d or total size %d is larger than data size %d\n",
+ (int)mHeader->header.headerSize, (int)mHeader->header.size, (int)size);
+ return (mError=BAD_TYPE);
+ }
+ mSize = mHeader->header.size;
+ mEntries = (const uint32_t*)
+ (((const uint8_t*)data)+mHeader->header.headerSize);
+
+ if (mHeader->stringCount > 0) {
+ if ((mHeader->stringCount*sizeof(uint32_t) < mHeader->stringCount) // uint32 overflow?
+ || (mHeader->header.headerSize+(mHeader->stringCount*sizeof(uint32_t)))
+ > size) {
+ ALOGW("Bad string block: entry of %d items extends past data size %d\n",
+ (int)(mHeader->header.headerSize+(mHeader->stringCount*sizeof(uint32_t))),
+ (int)size);
+ return (mError=BAD_TYPE);
+ }
+
+ size_t charSize;
+ if (mHeader->flags&ResStringPool_header::UTF8_FLAG) {
+ charSize = sizeof(uint8_t);
+ } else {
+ charSize = sizeof(char16_t);
+ }
+
+ mStrings = (const void*)
+ (((const uint8_t*)data)+mHeader->stringsStart);
+ if (mHeader->stringsStart >= (mHeader->header.size-sizeof(uint16_t))) {
+ ALOGW("Bad string block: string pool starts at %d, after total size %d\n",
+ (int)mHeader->stringsStart, (int)mHeader->header.size);
+ return (mError=BAD_TYPE);
+ }
+ if (mHeader->styleCount == 0) {
+ mStringPoolSize =
+ (mHeader->header.size-mHeader->stringsStart)/charSize;
+ } else {
+ // check invariant: styles starts before end of data
+ if (mHeader->stylesStart >= (mHeader->header.size-sizeof(uint16_t))) {
+ ALOGW("Bad style block: style block starts at %d past data size of %d\n",
+ (int)mHeader->stylesStart, (int)mHeader->header.size);
+ return (mError=BAD_TYPE);
+ }
+ // check invariant: styles follow the strings
+ if (mHeader->stylesStart <= mHeader->stringsStart) {
+ ALOGW("Bad style block: style block starts at %d, before strings at %d\n",
+ (int)mHeader->stylesStart, (int)mHeader->stringsStart);
+ return (mError=BAD_TYPE);
+ }
+ mStringPoolSize =
+ (mHeader->stylesStart-mHeader->stringsStart)/charSize;
+ }
+
+ // check invariant: stringCount > 0 requires a string pool to exist
+ if (mStringPoolSize == 0) {
+ ALOGW("Bad string block: stringCount is %d but pool size is 0\n", (int)mHeader->stringCount);
+ return (mError=BAD_TYPE);
+ }
+
+ if (notDeviceEndian) {
+ size_t i;
+ uint32_t* e = const_cast<uint32_t*>(mEntries);
+ for (i=0; i<mHeader->stringCount; i++) {
+ e[i] = dtohl(mEntries[i]);
+ }
+ if (!(mHeader->flags&ResStringPool_header::UTF8_FLAG)) {
+ const char16_t* strings = (const char16_t*)mStrings;
+ char16_t* s = const_cast<char16_t*>(strings);
+ for (i=0; i<mStringPoolSize; i++) {
+ s[i] = dtohs(strings[i]);
+ }
+ }
+ }
+
+ if ((mHeader->flags&ResStringPool_header::UTF8_FLAG &&
+ ((uint8_t*)mStrings)[mStringPoolSize-1] != 0) ||
+ (!mHeader->flags&ResStringPool_header::UTF8_FLAG &&
+ ((char16_t*)mStrings)[mStringPoolSize-1] != 0)) {
+ ALOGW("Bad string block: last string is not 0-terminated\n");
+ return (mError=BAD_TYPE);
+ }
+ } else {
+ mStrings = NULL;
+ mStringPoolSize = 0;
+ }
+
+ if (mHeader->styleCount > 0) {
+ mEntryStyles = mEntries + mHeader->stringCount;
+ // invariant: integer overflow in calculating mEntryStyles
+ if (mEntryStyles < mEntries) {
+ ALOGW("Bad string block: integer overflow finding styles\n");
+ return (mError=BAD_TYPE);
+ }
+
+ if (((const uint8_t*)mEntryStyles-(const uint8_t*)mHeader) > (int)size) {
+ ALOGW("Bad string block: entry of %d styles extends past data size %d\n",
+ (int)((const uint8_t*)mEntryStyles-(const uint8_t*)mHeader),
+ (int)size);
+ return (mError=BAD_TYPE);
+ }
+ mStyles = (const uint32_t*)
+ (((const uint8_t*)data)+mHeader->stylesStart);
+ if (mHeader->stylesStart >= mHeader->header.size) {
+ ALOGW("Bad string block: style pool starts %d, after total size %d\n",
+ (int)mHeader->stylesStart, (int)mHeader->header.size);
+ return (mError=BAD_TYPE);
+ }
+ mStylePoolSize =
+ (mHeader->header.size-mHeader->stylesStart)/sizeof(uint32_t);
+
+ if (notDeviceEndian) {
+ size_t i;
+ uint32_t* e = const_cast<uint32_t*>(mEntryStyles);
+ for (i=0; i<mHeader->styleCount; i++) {
+ e[i] = dtohl(mEntryStyles[i]);
+ }
+ uint32_t* s = const_cast<uint32_t*>(mStyles);
+ for (i=0; i<mStylePoolSize; i++) {
+ s[i] = dtohl(mStyles[i]);
+ }
+ }
+
+ const ResStringPool_span endSpan = {
+ { htodl(ResStringPool_span::END) },
+ htodl(ResStringPool_span::END), htodl(ResStringPool_span::END)
+ };
+ if (memcmp(&mStyles[mStylePoolSize-(sizeof(endSpan)/sizeof(uint32_t))],
+ &endSpan, sizeof(endSpan)) != 0) {
+ ALOGW("Bad string block: last style is not 0xFFFFFFFF-terminated\n");
+ return (mError=BAD_TYPE);
+ }
+ } else {
+ mEntryStyles = NULL;
+ mStyles = NULL;
+ mStylePoolSize = 0;
+ }
+
+ return (mError=NO_ERROR);
+}
+
+status_t ResStringPool::getError() const
+{
+ return mError;
+}
+
+void ResStringPool::uninit()
+{
+ mError = NO_INIT;
+ if (mHeader != NULL && mCache != NULL) {
+ for (size_t x = 0; x < mHeader->stringCount; x++) {
+ if (mCache[x] != NULL) {
+ free(mCache[x]);
+ mCache[x] = NULL;
+ }
+ }
+ free(mCache);
+ mCache = NULL;
+ }
+ if (mOwnedData) {
+ free(mOwnedData);
+ mOwnedData = NULL;
+ }
+}
+
+/**
+ * Strings in UTF-16 format have length indicated by a length encoded in the
+ * stored data. It is either 1 or 2 characters of length data. This allows a
+ * maximum length of 0x7FFFFFF (2147483647 bytes), but if you're storing that
+ * much data in a string, you're abusing them.
+ *
+ * If the high bit is set, then there are two characters or 4 bytes of length
+ * data encoded. In that case, drop the high bit of the first character and
+ * add it together with the next character.
+ */
+static inline size_t
+decodeLength(const char16_t** str)
+{
+ size_t len = **str;
+ if ((len & 0x8000) != 0) {
+ (*str)++;
+ len = ((len & 0x7FFF) << 16) | **str;
+ }
+ (*str)++;
+ return len;
+}
+
+/**
+ * Strings in UTF-8 format have length indicated by a length encoded in the
+ * stored data. It is either 1 or 2 characters of length data. This allows a
+ * maximum length of 0x7FFF (32767 bytes), but you should consider storing
+ * text in another way if you're using that much data in a single string.
+ *
+ * If the high bit is set, then there are two characters or 2 bytes of length
+ * data encoded. In that case, drop the high bit of the first character and
+ * add it together with the next character.
+ */
+static inline size_t
+decodeLength(const uint8_t** str)
+{
+ size_t len = **str;
+ if ((len & 0x80) != 0) {
+ (*str)++;
+ len = ((len & 0x7F) << 8) | **str;
+ }
+ (*str)++;
+ return len;
+}
+
+const uint16_t* ResStringPool::stringAt(size_t idx, size_t* u16len) const
+{
+ if (mError == NO_ERROR && idx < mHeader->stringCount) {
+ const bool isUTF8 = (mHeader->flags&ResStringPool_header::UTF8_FLAG) != 0;
+ const uint32_t off = mEntries[idx]/(isUTF8?sizeof(char):sizeof(char16_t));
+ if (off < (mStringPoolSize-1)) {
+ if (!isUTF8) {
+ const char16_t* strings = (char16_t*)mStrings;
+ const char16_t* str = strings+off;
+
+ *u16len = decodeLength(&str);
+ if ((uint32_t)(str+*u16len-strings) < mStringPoolSize) {
+ return str;
+ } else {
+ ALOGW("Bad string block: string #%d extends to %d, past end at %d\n",
+ (int)idx, (int)(str+*u16len-strings), (int)mStringPoolSize);
+ }
+ } else {
+ const uint8_t* strings = (uint8_t*)mStrings;
+ const uint8_t* u8str = strings+off;
+
+ *u16len = decodeLength(&u8str);
+ size_t u8len = decodeLength(&u8str);
+
+ // encLen must be less than 0x7FFF due to encoding.
+ if ((uint32_t)(u8str+u8len-strings) < mStringPoolSize) {
+ AutoMutex lock(mDecodeLock);
+
+ if (mCache == NULL) {
+#ifndef HAVE_ANDROID_OS
+ STRING_POOL_NOISY(ALOGI("CREATING STRING CACHE OF %d bytes",
+ mHeader->stringCount*sizeof(char16_t**)));
+#else
+ // We do not want to be in this case when actually running Android.
+ ALOGW("CREATING STRING CACHE OF %d bytes",
+ mHeader->stringCount*sizeof(char16_t**));
+#endif
+ mCache = (char16_t**)calloc(mHeader->stringCount, sizeof(char16_t**));
+ if (mCache == NULL) {
+ ALOGW("No memory trying to allocate decode cache table of %d bytes\n",
+ (int)(mHeader->stringCount*sizeof(char16_t**)));
+ return NULL;
+ }
+ }
+
+ if (mCache[idx] != NULL) {
+ return mCache[idx];
+ }
+
+ ssize_t actualLen = utf8_to_utf16_length(u8str, u8len);
+ if (actualLen < 0 || (size_t)actualLen != *u16len) {
+ ALOGW("Bad string block: string #%lld decoded length is not correct "
+ "%lld vs %llu\n",
+ (long long)idx, (long long)actualLen, (long long)*u16len);
+ return NULL;
+ }
+
+ char16_t *u16str = (char16_t *)calloc(*u16len+1, sizeof(char16_t));
+ if (!u16str) {
+ ALOGW("No memory when trying to allocate decode cache for string #%d\n",
+ (int)idx);
+ return NULL;
+ }
+
+ STRING_POOL_NOISY(ALOGI("Caching UTF8 string: %s", u8str));
+ utf8_to_utf16(u8str, u8len, u16str);
+ mCache[idx] = u16str;
+ return u16str;
+ } else {
+ ALOGW("Bad string block: string #%lld extends to %lld, past end at %lld\n",
+ (long long)idx, (long long)(u8str+u8len-strings),
+ (long long)mStringPoolSize);
+ }
+ }
+ } else {
+ ALOGW("Bad string block: string #%d entry is at %d, past end at %d\n",
+ (int)idx, (int)(off*sizeof(uint16_t)),
+ (int)(mStringPoolSize*sizeof(uint16_t)));
+ }
+ }
+ return NULL;
+}
+
+const char* ResStringPool::string8At(size_t idx, size_t* outLen) const
+{
+ if (mError == NO_ERROR && idx < mHeader->stringCount) {
+ if ((mHeader->flags&ResStringPool_header::UTF8_FLAG) == 0) {
+ return NULL;
+ }
+ const uint32_t off = mEntries[idx]/sizeof(char);
+ if (off < (mStringPoolSize-1)) {
+ const uint8_t* strings = (uint8_t*)mStrings;
+ const uint8_t* str = strings+off;
+ *outLen = decodeLength(&str);
+ size_t encLen = decodeLength(&str);
+ if ((uint32_t)(str+encLen-strings) < mStringPoolSize) {
+ return (const char*)str;
+ } else {
+ ALOGW("Bad string block: string #%d extends to %d, past end at %d\n",
+ (int)idx, (int)(str+encLen-strings), (int)mStringPoolSize);
+ }
+ } else {
+ ALOGW("Bad string block: string #%d entry is at %d, past end at %d\n",
+ (int)idx, (int)(off*sizeof(uint16_t)),
+ (int)(mStringPoolSize*sizeof(uint16_t)));
+ }
+ }
+ return NULL;
+}
+
+const String8 ResStringPool::string8ObjectAt(size_t idx) const
+{
+ size_t len;
+ const char *str = (const char*)string8At(idx, &len);
+ if (str != NULL) {
+ return String8(str);
+ }
+ return String8(stringAt(idx, &len));
+}
+
+const ResStringPool_span* ResStringPool::styleAt(const ResStringPool_ref& ref) const
+{
+ return styleAt(ref.index);
+}
+
+const ResStringPool_span* ResStringPool::styleAt(size_t idx) const
+{
+ if (mError == NO_ERROR && idx < mHeader->styleCount) {
+ const uint32_t off = (mEntryStyles[idx]/sizeof(uint32_t));
+ if (off < mStylePoolSize) {
+ return (const ResStringPool_span*)(mStyles+off);
+ } else {
+ ALOGW("Bad string block: style #%d entry is at %d, past end at %d\n",
+ (int)idx, (int)(off*sizeof(uint32_t)),
+ (int)(mStylePoolSize*sizeof(uint32_t)));
+ }
+ }
+ return NULL;
+}
+
+ssize_t ResStringPool::indexOfString(const char16_t* str, size_t strLen) const
+{
+ if (mError != NO_ERROR) {
+ return mError;
+ }
+
+ size_t len;
+
+ if ((mHeader->flags&ResStringPool_header::UTF8_FLAG) != 0) {
+ STRING_POOL_NOISY(ALOGI("indexOfString UTF-8: %s", String8(str, strLen).string()));
+
+ // The string pool contains UTF 8 strings; we don't want to cause
+ // temporary UTF-16 strings to be created as we search.
+ if (mHeader->flags&ResStringPool_header::SORTED_FLAG) {
+ // Do a binary search for the string... this is a little tricky,
+ // because the strings are sorted with strzcmp16(). So to match
+ // the ordering, we need to convert strings in the pool to UTF-16.
+ // But we don't want to hit the cache, so instead we will have a
+ // local temporary allocation for the conversions.
+ char16_t* convBuffer = (char16_t*)malloc(strLen+4);
+ ssize_t l = 0;
+ ssize_t h = mHeader->stringCount-1;
+
+ ssize_t mid;
+ while (l <= h) {
+ mid = l + (h - l)/2;
+ const uint8_t* s = (const uint8_t*)string8At(mid, &len);
+ int c;
+ if (s != NULL) {
+ char16_t* end = utf8_to_utf16_n(s, len, convBuffer, strLen+3);
+ *end = 0;
+ c = strzcmp16(convBuffer, end-convBuffer, str, strLen);
+ } else {
+ c = -1;
+ }
+ STRING_POOL_NOISY(ALOGI("Looking at %s, cmp=%d, l/mid/h=%d/%d/%d\n",
+ (const char*)s, c, (int)l, (int)mid, (int)h));
+ if (c == 0) {
+ STRING_POOL_NOISY(ALOGI("MATCH!"));
+ free(convBuffer);
+ return mid;
+ } else if (c < 0) {
+ l = mid + 1;
+ } else {
+ h = mid - 1;
+ }
+ }
+ free(convBuffer);
+ } else {
+ // It is unusual to get the ID from an unsorted string block...
+ // most often this happens because we want to get IDs for style
+ // span tags; since those always appear at the end of the string
+ // block, start searching at the back.
+ String8 str8(str, strLen);
+ const size_t str8Len = str8.size();
+ for (int i=mHeader->stringCount-1; i>=0; i--) {
+ const char* s = string8At(i, &len);
+ STRING_POOL_NOISY(ALOGI("Looking at %s, i=%d\n",
+ String8(s).string(),
+ i));
+ if (s && str8Len == len && memcmp(s, str8.string(), str8Len) == 0) {
+ STRING_POOL_NOISY(ALOGI("MATCH!"));
+ return i;
+ }
+ }
+ }
+
+ } else {
+ STRING_POOL_NOISY(ALOGI("indexOfString UTF-16: %s", String8(str, strLen).string()));
+
+ if (mHeader->flags&ResStringPool_header::SORTED_FLAG) {
+ // Do a binary search for the string...
+ ssize_t l = 0;
+ ssize_t h = mHeader->stringCount-1;
+
+ ssize_t mid;
+ while (l <= h) {
+ mid = l + (h - l)/2;
+ const char16_t* s = stringAt(mid, &len);
+ int c = s ? strzcmp16(s, len, str, strLen) : -1;
+ STRING_POOL_NOISY(ALOGI("Looking at %s, cmp=%d, l/mid/h=%d/%d/%d\n",
+ String8(s).string(),
+ c, (int)l, (int)mid, (int)h));
+ if (c == 0) {
+ STRING_POOL_NOISY(ALOGI("MATCH!"));
+ return mid;
+ } else if (c < 0) {
+ l = mid + 1;
+ } else {
+ h = mid - 1;
+ }
+ }
+ } else {
+ // It is unusual to get the ID from an unsorted string block...
+ // most often this happens because we want to get IDs for style
+ // span tags; since those always appear at the end of the string
+ // block, start searching at the back.
+ for (int i=mHeader->stringCount-1; i>=0; i--) {
+ const char16_t* s = stringAt(i, &len);
+ STRING_POOL_NOISY(ALOGI("Looking at %s, i=%d\n",
+ String8(s).string(),
+ i));
+ if (s && strLen == len && strzcmp16(s, len, str, strLen) == 0) {
+ STRING_POOL_NOISY(ALOGI("MATCH!"));
+ return i;
+ }
+ }
+ }
+ }
+
+ return NAME_NOT_FOUND;
+}
+
+size_t ResStringPool::size() const
+{
+ return (mError == NO_ERROR) ? mHeader->stringCount : 0;
+}
+
+size_t ResStringPool::styleCount() const
+{
+ return (mError == NO_ERROR) ? mHeader->styleCount : 0;
+}
+
+size_t ResStringPool::bytes() const
+{
+ return (mError == NO_ERROR) ? mHeader->header.size : 0;
+}
+
+bool ResStringPool::isSorted() const
+{
+ return (mHeader->flags&ResStringPool_header::SORTED_FLAG)!=0;
+}
+
+bool ResStringPool::isUTF8() const
+{
+ return (mHeader->flags&ResStringPool_header::UTF8_FLAG)!=0;
+}
+
+// --------------------------------------------------------------------
+// --------------------------------------------------------------------
+// --------------------------------------------------------------------
+
+ResXMLParser::ResXMLParser(const ResXMLTree& tree)
+ : mTree(tree), mEventCode(BAD_DOCUMENT)
+{
+}
+
+void ResXMLParser::restart()
+{
+ mCurNode = NULL;
+ mEventCode = mTree.mError == NO_ERROR ? START_DOCUMENT : BAD_DOCUMENT;
+}
+const ResStringPool& ResXMLParser::getStrings() const
+{
+ return mTree.mStrings;
+}
+
+ResXMLParser::event_code_t ResXMLParser::getEventType() const
+{
+ return mEventCode;
+}
+
+ResXMLParser::event_code_t ResXMLParser::next()
+{
+ if (mEventCode == START_DOCUMENT) {
+ mCurNode = mTree.mRootNode;
+ mCurExt = mTree.mRootExt;
+ return (mEventCode=mTree.mRootCode);
+ } else if (mEventCode >= FIRST_CHUNK_CODE) {
+ return nextNode();
+ }
+ return mEventCode;
+}
+
+int32_t ResXMLParser::getCommentID() const
+{
+ return mCurNode != NULL ? dtohl(mCurNode->comment.index) : -1;
+}
+
+const uint16_t* ResXMLParser::getComment(size_t* outLen) const
+{
+ int32_t id = getCommentID();
+ return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : NULL;
+}
+
+uint32_t ResXMLParser::getLineNumber() const
+{
+ return mCurNode != NULL ? dtohl(mCurNode->lineNumber) : -1;
+}
+
+int32_t ResXMLParser::getTextID() const
+{
+ if (mEventCode == TEXT) {
+ return dtohl(((const ResXMLTree_cdataExt*)mCurExt)->data.index);
+ }
+ return -1;
+}
+
+const uint16_t* ResXMLParser::getText(size_t* outLen) const
+{
+ int32_t id = getTextID();
+ return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : NULL;
+}
+
+ssize_t ResXMLParser::getTextValue(Res_value* outValue) const
+{
+ if (mEventCode == TEXT) {
+ outValue->copyFrom_dtoh(((const ResXMLTree_cdataExt*)mCurExt)->typedData);
+ return sizeof(Res_value);
+ }
+ return BAD_TYPE;
+}
+
+int32_t ResXMLParser::getNamespacePrefixID() const
+{
+ if (mEventCode == START_NAMESPACE || mEventCode == END_NAMESPACE) {
+ return dtohl(((const ResXMLTree_namespaceExt*)mCurExt)->prefix.index);
+ }
+ return -1;
+}
+
+const uint16_t* ResXMLParser::getNamespacePrefix(size_t* outLen) const
+{
+ int32_t id = getNamespacePrefixID();
+ //printf("prefix=%d event=%p\n", id, mEventCode);
+ return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : NULL;
+}
+
+int32_t ResXMLParser::getNamespaceUriID() const
+{
+ if (mEventCode == START_NAMESPACE || mEventCode == END_NAMESPACE) {
+ return dtohl(((const ResXMLTree_namespaceExt*)mCurExt)->uri.index);
+ }
+ return -1;
+}
+
+const uint16_t* ResXMLParser::getNamespaceUri(size_t* outLen) const
+{
+ int32_t id = getNamespaceUriID();
+ //printf("uri=%d event=%p\n", id, mEventCode);
+ return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : NULL;
+}
+
+int32_t ResXMLParser::getElementNamespaceID() const
+{
+ if (mEventCode == START_TAG) {
+ return dtohl(((const ResXMLTree_attrExt*)mCurExt)->ns.index);
+ }
+ if (mEventCode == END_TAG) {
+ return dtohl(((const ResXMLTree_endElementExt*)mCurExt)->ns.index);
+ }
+ return -1;
+}
+
+const uint16_t* ResXMLParser::getElementNamespace(size_t* outLen) const
+{
+ int32_t id = getElementNamespaceID();
+ return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : NULL;
+}
+
+int32_t ResXMLParser::getElementNameID() const
+{
+ if (mEventCode == START_TAG) {
+ return dtohl(((const ResXMLTree_attrExt*)mCurExt)->name.index);
+ }
+ if (mEventCode == END_TAG) {
+ return dtohl(((const ResXMLTree_endElementExt*)mCurExt)->name.index);
+ }
+ return -1;
+}
+
+const uint16_t* ResXMLParser::getElementName(size_t* outLen) const
+{
+ int32_t id = getElementNameID();
+ return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : NULL;
+}
+
+size_t ResXMLParser::getAttributeCount() const
+{
+ if (mEventCode == START_TAG) {
+ return dtohs(((const ResXMLTree_attrExt*)mCurExt)->attributeCount);
+ }
+ return 0;
+}
+
+int32_t ResXMLParser::getAttributeNamespaceID(size_t idx) const
+{
+ if (mEventCode == START_TAG) {
+ const ResXMLTree_attrExt* tag = (const ResXMLTree_attrExt*)mCurExt;
+ if (idx < dtohs(tag->attributeCount)) {
+ const ResXMLTree_attribute* attr = (const ResXMLTree_attribute*)
+ (((const uint8_t*)tag)
+ + dtohs(tag->attributeStart)
+ + (dtohs(tag->attributeSize)*idx));
+ return dtohl(attr->ns.index);
+ }
+ }
+ return -2;
+}
+
+const uint16_t* ResXMLParser::getAttributeNamespace(size_t idx, size_t* outLen) const
+{
+ int32_t id = getAttributeNamespaceID(idx);
+ //printf("attribute namespace=%d idx=%d event=%p\n", id, idx, mEventCode);
+ //XML_NOISY(printf("getAttributeNamespace 0x%x=0x%x\n", idx, id));
+ return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : NULL;
+}
+
+const char* ResXMLParser::getAttributeNamespace8(size_t idx, size_t* outLen) const
+{
+ int32_t id = getAttributeNamespaceID(idx);
+ //printf("attribute namespace=%d idx=%d event=%p\n", id, idx, mEventCode);
+ //XML_NOISY(printf("getAttributeNamespace 0x%x=0x%x\n", idx, id));
+ return id >= 0 ? mTree.mStrings.string8At(id, outLen) : NULL;
+}
+
+int32_t ResXMLParser::getAttributeNameID(size_t idx) const
+{
+ if (mEventCode == START_TAG) {
+ const ResXMLTree_attrExt* tag = (const ResXMLTree_attrExt*)mCurExt;
+ if (idx < dtohs(tag->attributeCount)) {
+ const ResXMLTree_attribute* attr = (const ResXMLTree_attribute*)
+ (((const uint8_t*)tag)
+ + dtohs(tag->attributeStart)
+ + (dtohs(tag->attributeSize)*idx));
+ return dtohl(attr->name.index);
+ }
+ }
+ return -1;
+}
+
+const uint16_t* ResXMLParser::getAttributeName(size_t idx, size_t* outLen) const
+{
+ int32_t id = getAttributeNameID(idx);
+ //printf("attribute name=%d idx=%d event=%p\n", id, idx, mEventCode);
+ //XML_NOISY(printf("getAttributeName 0x%x=0x%x\n", idx, id));
+ return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : NULL;
+}
+
+const char* ResXMLParser::getAttributeName8(size_t idx, size_t* outLen) const
+{
+ int32_t id = getAttributeNameID(idx);
+ //printf("attribute name=%d idx=%d event=%p\n", id, idx, mEventCode);
+ //XML_NOISY(printf("getAttributeName 0x%x=0x%x\n", idx, id));
+ return id >= 0 ? mTree.mStrings.string8At(id, outLen) : NULL;
+}
+
+uint32_t ResXMLParser::getAttributeNameResID(size_t idx) const
+{
+ int32_t id = getAttributeNameID(idx);
+ if (id >= 0 && (size_t)id < mTree.mNumResIds) {
+ return dtohl(mTree.mResIds[id]);
+ }
+ return 0;
+}
+
+int32_t ResXMLParser::getAttributeValueStringID(size_t idx) const
+{
+ if (mEventCode == START_TAG) {
+ const ResXMLTree_attrExt* tag = (const ResXMLTree_attrExt*)mCurExt;
+ if (idx < dtohs(tag->attributeCount)) {
+ const ResXMLTree_attribute* attr = (const ResXMLTree_attribute*)
+ (((const uint8_t*)tag)
+ + dtohs(tag->attributeStart)
+ + (dtohs(tag->attributeSize)*idx));
+ return dtohl(attr->rawValue.index);
+ }
+ }
+ return -1;
+}
+
+const uint16_t* ResXMLParser::getAttributeStringValue(size_t idx, size_t* outLen) const
+{
+ int32_t id = getAttributeValueStringID(idx);
+ //XML_NOISY(printf("getAttributeValue 0x%x=0x%x\n", idx, id));
+ return id >= 0 ? mTree.mStrings.stringAt(id, outLen) : NULL;
+}
+
+int32_t ResXMLParser::getAttributeDataType(size_t idx) const
+{
+ if (mEventCode == START_TAG) {
+ const ResXMLTree_attrExt* tag = (const ResXMLTree_attrExt*)mCurExt;
+ if (idx < dtohs(tag->attributeCount)) {
+ const ResXMLTree_attribute* attr = (const ResXMLTree_attribute*)
+ (((const uint8_t*)tag)
+ + dtohs(tag->attributeStart)
+ + (dtohs(tag->attributeSize)*idx));
+ return attr->typedValue.dataType;
+ }
+ }
+ return Res_value::TYPE_NULL;
+}
+
+int32_t ResXMLParser::getAttributeData(size_t idx) const
+{
+ if (mEventCode == START_TAG) {
+ const ResXMLTree_attrExt* tag = (const ResXMLTree_attrExt*)mCurExt;
+ if (idx < dtohs(tag->attributeCount)) {
+ const ResXMLTree_attribute* attr = (const ResXMLTree_attribute*)
+ (((const uint8_t*)tag)
+ + dtohs(tag->attributeStart)
+ + (dtohs(tag->attributeSize)*idx));
+ return dtohl(attr->typedValue.data);
+ }
+ }
+ return 0;
+}
+
+ssize_t ResXMLParser::getAttributeValue(size_t idx, Res_value* outValue) const
+{
+ if (mEventCode == START_TAG) {
+ const ResXMLTree_attrExt* tag = (const ResXMLTree_attrExt*)mCurExt;
+ if (idx < dtohs(tag->attributeCount)) {
+ const ResXMLTree_attribute* attr = (const ResXMLTree_attribute*)
+ (((const uint8_t*)tag)
+ + dtohs(tag->attributeStart)
+ + (dtohs(tag->attributeSize)*idx));
+ outValue->copyFrom_dtoh(attr->typedValue);
+ return sizeof(Res_value);
+ }
+ }
+ return BAD_TYPE;
+}
+
+ssize_t ResXMLParser::indexOfAttribute(const char* ns, const char* attr) const
+{
+ String16 nsStr(ns != NULL ? ns : "");
+ String16 attrStr(attr);
+ return indexOfAttribute(ns ? nsStr.string() : NULL, ns ? nsStr.size() : 0,
+ attrStr.string(), attrStr.size());
+}
+
+ssize_t ResXMLParser::indexOfAttribute(const char16_t* ns, size_t nsLen,
+ const char16_t* attr, size_t attrLen) const
+{
+ if (mEventCode == START_TAG) {
+ if (attr == NULL) {
+ return NAME_NOT_FOUND;
+ }
+ const size_t N = getAttributeCount();
+ if (mTree.mStrings.isUTF8()) {
+ String8 ns8, attr8;
+ if (ns != NULL) {
+ ns8 = String8(ns, nsLen);
+ }
+ attr8 = String8(attr, attrLen);
+ STRING_POOL_NOISY(ALOGI("indexOfAttribute UTF8 %s (%d) / %s (%d)", ns8.string(), nsLen,
+ attr8.string(), attrLen));
+ for (size_t i=0; i<N; i++) {
+ size_t curNsLen = 0, curAttrLen = 0;
+ const char* curNs = getAttributeNamespace8(i, &curNsLen);
+ const char* curAttr = getAttributeName8(i, &curAttrLen);
+ STRING_POOL_NOISY(ALOGI(" curNs=%s (%d), curAttr=%s (%d)", curNs, curNsLen,
+ curAttr, curAttrLen));
+ if (curAttr != NULL && curNsLen == nsLen && curAttrLen == attrLen
+ && memcmp(attr8.string(), curAttr, attrLen) == 0) {
+ if (ns == NULL) {
+ if (curNs == NULL) {
+ STRING_POOL_NOISY(ALOGI(" FOUND!"));
+ return i;
+ }
+ } else if (curNs != NULL) {
+ //printf(" --> ns=%s, curNs=%s\n",
+ // String8(ns).string(), String8(curNs).string());
+ if (memcmp(ns8.string(), curNs, nsLen) == 0) {
+ STRING_POOL_NOISY(ALOGI(" FOUND!"));
+ return i;
+ }
+ }
+ }
+ }
+ } else {
+ STRING_POOL_NOISY(ALOGI("indexOfAttribute UTF16 %s (%d) / %s (%d)",
+ String8(ns, nsLen).string(), nsLen,
+ String8(attr, attrLen).string(), attrLen));
+ for (size_t i=0; i<N; i++) {
+ size_t curNsLen = 0, curAttrLen = 0;
+ const char16_t* curNs = getAttributeNamespace(i, &curNsLen);
+ const char16_t* curAttr = getAttributeName(i, &curAttrLen);
+ STRING_POOL_NOISY(ALOGI(" curNs=%s (%d), curAttr=%s (%d)",
+ String8(curNs, curNsLen).string(), curNsLen,
+ String8(curAttr, curAttrLen).string(), curAttrLen));
+ if (curAttr != NULL && curNsLen == nsLen && curAttrLen == attrLen
+ && (memcmp(attr, curAttr, attrLen*sizeof(char16_t)) == 0)) {
+ if (ns == NULL) {
+ if (curNs == NULL) {
+ STRING_POOL_NOISY(ALOGI(" FOUND!"));
+ return i;
+ }
+ } else if (curNs != NULL) {
+ //printf(" --> ns=%s, curNs=%s\n",
+ // String8(ns).string(), String8(curNs).string());
+ if (memcmp(ns, curNs, nsLen*sizeof(char16_t)) == 0) {
+ STRING_POOL_NOISY(ALOGI(" FOUND!"));
+ return i;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return NAME_NOT_FOUND;
+}
+
+ssize_t ResXMLParser::indexOfID() const
+{
+ if (mEventCode == START_TAG) {
+ const ssize_t idx = dtohs(((const ResXMLTree_attrExt*)mCurExt)->idIndex);
+ if (idx > 0) return (idx-1);
+ }
+ return NAME_NOT_FOUND;
+}
+
+ssize_t ResXMLParser::indexOfClass() const
+{
+ if (mEventCode == START_TAG) {
+ const ssize_t idx = dtohs(((const ResXMLTree_attrExt*)mCurExt)->classIndex);
+ if (idx > 0) return (idx-1);
+ }
+ return NAME_NOT_FOUND;
+}
+
+ssize_t ResXMLParser::indexOfStyle() const
+{
+ if (mEventCode == START_TAG) {
+ const ssize_t idx = dtohs(((const ResXMLTree_attrExt*)mCurExt)->styleIndex);
+ if (idx > 0) return (idx-1);
+ }
+ return NAME_NOT_FOUND;
+}
+
+ResXMLParser::event_code_t ResXMLParser::nextNode()
+{
+ if (mEventCode < 0) {
+ return mEventCode;
+ }
+
+ do {
+ const ResXMLTree_node* next = (const ResXMLTree_node*)
+ (((const uint8_t*)mCurNode) + dtohl(mCurNode->header.size));
+ //ALOGW("Next node: prev=%p, next=%p\n", mCurNode, next);
+
+ if (((const uint8_t*)next) >= mTree.mDataEnd) {
+ mCurNode = NULL;
+ return (mEventCode=END_DOCUMENT);
+ }
+
+ if (mTree.validateNode(next) != NO_ERROR) {
+ mCurNode = NULL;
+ return (mEventCode=BAD_DOCUMENT);
+ }
+
+ mCurNode = next;
+ const uint16_t headerSize = dtohs(next->header.headerSize);
+ const uint32_t totalSize = dtohl(next->header.size);
+ mCurExt = ((const uint8_t*)next) + headerSize;
+ size_t minExtSize = 0;
+ event_code_t eventCode = (event_code_t)dtohs(next->header.type);
+ switch ((mEventCode=eventCode)) {
+ case RES_XML_START_NAMESPACE_TYPE:
+ case RES_XML_END_NAMESPACE_TYPE:
+ minExtSize = sizeof(ResXMLTree_namespaceExt);
+ break;
+ case RES_XML_START_ELEMENT_TYPE:
+ minExtSize = sizeof(ResXMLTree_attrExt);
+ break;
+ case RES_XML_END_ELEMENT_TYPE:
+ minExtSize = sizeof(ResXMLTree_endElementExt);
+ break;
+ case RES_XML_CDATA_TYPE:
+ minExtSize = sizeof(ResXMLTree_cdataExt);
+ break;
+ default:
+ ALOGW("Unknown XML block: header type %d in node at %d\n",
+ (int)dtohs(next->header.type),
+ (int)(((const uint8_t*)next)-((const uint8_t*)mTree.mHeader)));
+ continue;
+ }
+
+ if ((totalSize-headerSize) < minExtSize) {
+ ALOGW("Bad XML block: header type 0x%x in node at 0x%x has size %d, need %d\n",
+ (int)dtohs(next->header.type),
+ (int)(((const uint8_t*)next)-((const uint8_t*)mTree.mHeader)),
+ (int)(totalSize-headerSize), (int)minExtSize);
+ return (mEventCode=BAD_DOCUMENT);
+ }
+
+ //printf("CurNode=%p, CurExt=%p, headerSize=%d, minExtSize=%d\n",
+ // mCurNode, mCurExt, headerSize, minExtSize);
+
+ return eventCode;
+ } while (true);
+}
+
+void ResXMLParser::getPosition(ResXMLParser::ResXMLPosition* pos) const
+{
+ pos->eventCode = mEventCode;
+ pos->curNode = mCurNode;
+ pos->curExt = mCurExt;
+}
+
+void ResXMLParser::setPosition(const ResXMLParser::ResXMLPosition& pos)
+{
+ mEventCode = pos.eventCode;
+ mCurNode = pos.curNode;
+ mCurExt = pos.curExt;
+}
+
+
+// --------------------------------------------------------------------
+
+static volatile int32_t gCount = 0;
+
+ResXMLTree::ResXMLTree()
+ : ResXMLParser(*this)
+ , mError(NO_INIT), mOwnedData(NULL)
+{
+ //ALOGI("Creating ResXMLTree %p #%d\n", this, android_atomic_inc(&gCount)+1);
+ restart();
+}
+
+ResXMLTree::ResXMLTree(const void* data, size_t size, bool copyData)
+ : ResXMLParser(*this)
+ , mError(NO_INIT), mOwnedData(NULL)
+{
+ //ALOGI("Creating ResXMLTree %p #%d\n", this, android_atomic_inc(&gCount)+1);
+ setTo(data, size, copyData);
+}
+
+ResXMLTree::~ResXMLTree()
+{
+ //ALOGI("Destroying ResXMLTree in %p #%d\n", this, android_atomic_dec(&gCount)-1);
+ uninit();
+}
+
+status_t ResXMLTree::setTo(const void* data, size_t size, bool copyData)
+{
+ uninit();
+ mEventCode = START_DOCUMENT;
+
+ if (!data || !size) {
+ return (mError=BAD_TYPE);
+ }
+
+ if (copyData) {
+ mOwnedData = malloc(size);
+ if (mOwnedData == NULL) {
+ return (mError=NO_MEMORY);
+ }
+ memcpy(mOwnedData, data, size);
+ data = mOwnedData;
+ }
+
+ mHeader = (const ResXMLTree_header*)data;
+ mSize = dtohl(mHeader->header.size);
+ if (dtohs(mHeader->header.headerSize) > mSize || mSize > size) {
+ ALOGW("Bad XML block: header size %d or total size %d is larger than data size %d\n",
+ (int)dtohs(mHeader->header.headerSize),
+ (int)dtohl(mHeader->header.size), (int)size);
+ mError = BAD_TYPE;
+ restart();
+ return mError;
+ }
+ mDataEnd = ((const uint8_t*)mHeader) + mSize;
+
+ mStrings.uninit();
+ mRootNode = NULL;
+ mResIds = NULL;
+ mNumResIds = 0;
+
+ // First look for a couple interesting chunks: the string block
+ // and first XML node.
+ const ResChunk_header* chunk =
+ (const ResChunk_header*)(((const uint8_t*)mHeader) + dtohs(mHeader->header.headerSize));
+ const ResChunk_header* lastChunk = chunk;
+ while (((const uint8_t*)chunk) < (mDataEnd-sizeof(ResChunk_header)) &&
+ ((const uint8_t*)chunk) < (mDataEnd-dtohl(chunk->size))) {
+ status_t err = validate_chunk(chunk, sizeof(ResChunk_header), mDataEnd, "XML");
+ if (err != NO_ERROR) {
+ mError = err;
+ goto done;
+ }
+ const uint16_t type = dtohs(chunk->type);
+ const size_t size = dtohl(chunk->size);
+ XML_NOISY(printf("Scanning @ %p: type=0x%x, size=0x%x\n",
+ (void*)(((uint32_t)chunk)-((uint32_t)mHeader)), type, size));
+ if (type == RES_STRING_POOL_TYPE) {
+ mStrings.setTo(chunk, size);
+ } else if (type == RES_XML_RESOURCE_MAP_TYPE) {
+ mResIds = (const uint32_t*)
+ (((const uint8_t*)chunk)+dtohs(chunk->headerSize));
+ mNumResIds = (dtohl(chunk->size)-dtohs(chunk->headerSize))/sizeof(uint32_t);
+ } else if (type >= RES_XML_FIRST_CHUNK_TYPE
+ && type <= RES_XML_LAST_CHUNK_TYPE) {
+ if (validateNode((const ResXMLTree_node*)chunk) != NO_ERROR) {
+ mError = BAD_TYPE;
+ goto done;
+ }
+ mCurNode = (const ResXMLTree_node*)lastChunk;
+ if (nextNode() == BAD_DOCUMENT) {
+ mError = BAD_TYPE;
+ goto done;
+ }
+ mRootNode = mCurNode;
+ mRootExt = mCurExt;
+ mRootCode = mEventCode;
+ break;
+ } else {
+ XML_NOISY(printf("Skipping unknown chunk!\n"));
+ }
+ lastChunk = chunk;
+ chunk = (const ResChunk_header*)
+ (((const uint8_t*)chunk) + size);
+ }
+
+ if (mRootNode == NULL) {
+ ALOGW("Bad XML block: no root element node found\n");
+ mError = BAD_TYPE;
+ goto done;
+ }
+
+ mError = mStrings.getError();
+
+done:
+ restart();
+ return mError;
+}
+
+status_t ResXMLTree::getError() const
+{
+ return mError;
+}
+
+void ResXMLTree::uninit()
+{
+ mError = NO_INIT;
+ mStrings.uninit();
+ if (mOwnedData) {
+ free(mOwnedData);
+ mOwnedData = NULL;
+ }
+ restart();
+}
+
+status_t ResXMLTree::validateNode(const ResXMLTree_node* node) const
+{
+ const uint16_t eventCode = dtohs(node->header.type);
+
+ status_t err = validate_chunk(
+ &node->header, sizeof(ResXMLTree_node),
+ mDataEnd, "ResXMLTree_node");
+
+ if (err >= NO_ERROR) {
+ // Only perform additional validation on START nodes
+ if (eventCode != RES_XML_START_ELEMENT_TYPE) {
+ return NO_ERROR;
+ }
+
+ const uint16_t headerSize = dtohs(node->header.headerSize);
+ const uint32_t size = dtohl(node->header.size);
+ const ResXMLTree_attrExt* attrExt = (const ResXMLTree_attrExt*)
+ (((const uint8_t*)node) + headerSize);
+ // check for sensical values pulled out of the stream so far...
+ if ((size >= headerSize + sizeof(ResXMLTree_attrExt))
+ && ((void*)attrExt > (void*)node)) {
+ const size_t attrSize = ((size_t)dtohs(attrExt->attributeSize))
+ * dtohs(attrExt->attributeCount);
+ if ((dtohs(attrExt->attributeStart)+attrSize) <= (size-headerSize)) {
+ return NO_ERROR;
+ }
+ ALOGW("Bad XML block: node attributes use 0x%x bytes, only have 0x%x bytes\n",
+ (unsigned int)(dtohs(attrExt->attributeStart)+attrSize),
+ (unsigned int)(size-headerSize));
+ }
+ else {
+ ALOGW("Bad XML start block: node header size 0x%x, size 0x%x\n",
+ (unsigned int)headerSize, (unsigned int)size);
+ }
+ return BAD_TYPE;
+ }
+
+ return err;
+
+#if 0
+ const bool isStart = dtohs(node->header.type) == RES_XML_START_ELEMENT_TYPE;
+
+ const uint16_t headerSize = dtohs(node->header.headerSize);
+ const uint32_t size = dtohl(node->header.size);
+
+ if (headerSize >= (isStart ? sizeof(ResXMLTree_attrNode) : sizeof(ResXMLTree_node))) {
+ if (size >= headerSize) {
+ if (((const uint8_t*)node) <= (mDataEnd-size)) {
+ if (!isStart) {
+ return NO_ERROR;
+ }
+ if ((((size_t)dtohs(node->attributeSize))*dtohs(node->attributeCount))
+ <= (size-headerSize)) {
+ return NO_ERROR;
+ }
+ ALOGW("Bad XML block: node attributes use 0x%x bytes, only have 0x%x bytes\n",
+ ((int)dtohs(node->attributeSize))*dtohs(node->attributeCount),
+ (int)(size-headerSize));
+ return BAD_TYPE;
+ }
+ ALOGW("Bad XML block: node at 0x%x extends beyond data end 0x%x\n",
+ (int)(((const uint8_t*)node)-((const uint8_t*)mHeader)), (int)mSize);
+ return BAD_TYPE;
+ }
+ ALOGW("Bad XML block: node at 0x%x header size 0x%x smaller than total size 0x%x\n",
+ (int)(((const uint8_t*)node)-((const uint8_t*)mHeader)),
+ (int)headerSize, (int)size);
+ return BAD_TYPE;
+ }
+ ALOGW("Bad XML block: node at 0x%x header size 0x%x too small\n",
+ (int)(((const uint8_t*)node)-((const uint8_t*)mHeader)),
+ (int)headerSize);
+ return BAD_TYPE;
+#endif
+}
+
+// --------------------------------------------------------------------
+// --------------------------------------------------------------------
+// --------------------------------------------------------------------
+
+void ResTable_config::copyFromDeviceNoSwap(const ResTable_config& o) {
+ const size_t size = dtohl(o.size);
+ if (size >= sizeof(ResTable_config)) {
+ *this = o;
+ } else {
+ memcpy(this, &o, size);
+ memset(((uint8_t*)this)+size, 0, sizeof(ResTable_config)-size);
+ }
+}
+
+void ResTable_config::copyFromDtoH(const ResTable_config& o) {
+ copyFromDeviceNoSwap(o);
+ size = sizeof(ResTable_config);
+ mcc = dtohs(mcc);
+ mnc = dtohs(mnc);
+ density = dtohs(density);
+ screenWidth = dtohs(screenWidth);
+ screenHeight = dtohs(screenHeight);
+ sdkVersion = dtohs(sdkVersion);
+ minorVersion = dtohs(minorVersion);
+ smallestScreenWidthDp = dtohs(smallestScreenWidthDp);
+ screenWidthDp = dtohs(screenWidthDp);
+ screenHeightDp = dtohs(screenHeightDp);
+}
+
+void ResTable_config::swapHtoD() {
+ size = htodl(size);
+ mcc = htods(mcc);
+ mnc = htods(mnc);
+ density = htods(density);
+ screenWidth = htods(screenWidth);
+ screenHeight = htods(screenHeight);
+ sdkVersion = htods(sdkVersion);
+ minorVersion = htods(minorVersion);
+ smallestScreenWidthDp = htods(smallestScreenWidthDp);
+ screenWidthDp = htods(screenWidthDp);
+ screenHeightDp = htods(screenHeightDp);
+}
+
+int ResTable_config::compare(const ResTable_config& o) const {
+ int32_t diff = (int32_t)(imsi - o.imsi);
+ if (diff != 0) return diff;
+ diff = (int32_t)(locale - o.locale);
+ if (diff != 0) return diff;
+ diff = (int32_t)(screenType - o.screenType);
+ if (diff != 0) return diff;
+ diff = (int32_t)(input - o.input);
+ if (diff != 0) return diff;
+ diff = (int32_t)(screenSize - o.screenSize);
+ if (diff != 0) return diff;
+ diff = (int32_t)(version - o.version);
+ if (diff != 0) return diff;
+ diff = (int32_t)(screenLayout - o.screenLayout);
+ if (diff != 0) return diff;
+ diff = (int32_t)(uiMode - o.uiMode);
+ if (diff != 0) return diff;
+ diff = (int32_t)(smallestScreenWidthDp - o.smallestScreenWidthDp);
+ if (diff != 0) return diff;
+ diff = (int32_t)(screenSizeDp - o.screenSizeDp);
+ return (int)diff;
+}
+
+int ResTable_config::compareLogical(const ResTable_config& o) const {
+ if (mcc != o.mcc) {
+ return mcc < o.mcc ? -1 : 1;
+ }
+ if (mnc != o.mnc) {
+ return mnc < o.mnc ? -1 : 1;
+ }
+ if (language[0] != o.language[0]) {
+ return language[0] < o.language[0] ? -1 : 1;
+ }
+ if (language[1] != o.language[1]) {
+ return language[1] < o.language[1] ? -1 : 1;
+ }
+ if (country[0] != o.country[0]) {
+ return country[0] < o.country[0] ? -1 : 1;
+ }
+ if (country[1] != o.country[1]) {
+ return country[1] < o.country[1] ? -1 : 1;
+ }
+ if ((screenLayout & MASK_LAYOUTDIR) != (o.screenLayout & MASK_LAYOUTDIR)) {
+ return (screenLayout & MASK_LAYOUTDIR) < (o.screenLayout & MASK_LAYOUTDIR) ? -1 : 1;
+ }
+ if (smallestScreenWidthDp != o.smallestScreenWidthDp) {
+ return smallestScreenWidthDp < o.smallestScreenWidthDp ? -1 : 1;
+ }
+ if (screenWidthDp != o.screenWidthDp) {
+ return screenWidthDp < o.screenWidthDp ? -1 : 1;
+ }
+ if (screenHeightDp != o.screenHeightDp) {
+ return screenHeightDp < o.screenHeightDp ? -1 : 1;
+ }
+ if (screenWidth != o.screenWidth) {
+ return screenWidth < o.screenWidth ? -1 : 1;
+ }
+ if (screenHeight != o.screenHeight) {
+ return screenHeight < o.screenHeight ? -1 : 1;
+ }
+ if (density != o.density) {
+ return density < o.density ? -1 : 1;
+ }
+ if (orientation != o.orientation) {
+ return orientation < o.orientation ? -1 : 1;
+ }
+ if (touchscreen != o.touchscreen) {
+ return touchscreen < o.touchscreen ? -1 : 1;
+ }
+ if (input != o.input) {
+ return input < o.input ? -1 : 1;
+ }
+ if (screenLayout != o.screenLayout) {
+ return screenLayout < o.screenLayout ? -1 : 1;
+ }
+ if (uiMode != o.uiMode) {
+ return uiMode < o.uiMode ? -1 : 1;
+ }
+ if (version != o.version) {
+ return version < o.version ? -1 : 1;
+ }
+ return 0;
+}
+
+int ResTable_config::diff(const ResTable_config& o) const {
+ int diffs = 0;
+ if (mcc != o.mcc) diffs |= CONFIG_MCC;
+ if (mnc != o.mnc) diffs |= CONFIG_MNC;
+ if (locale != o.locale) diffs |= CONFIG_LOCALE;
+ if (orientation != o.orientation) diffs |= CONFIG_ORIENTATION;
+ if (density != o.density) diffs |= CONFIG_DENSITY;
+ if (touchscreen != o.touchscreen) diffs |= CONFIG_TOUCHSCREEN;
+ if (((inputFlags^o.inputFlags)&(MASK_KEYSHIDDEN|MASK_NAVHIDDEN)) != 0)
+ diffs |= CONFIG_KEYBOARD_HIDDEN;
+ if (keyboard != o.keyboard) diffs |= CONFIG_KEYBOARD;
+ if (navigation != o.navigation) diffs |= CONFIG_NAVIGATION;
+ if (screenSize != o.screenSize) diffs |= CONFIG_SCREEN_SIZE;
+ if (version != o.version) diffs |= CONFIG_VERSION;
+ if ((screenLayout & MASK_LAYOUTDIR) != (o.screenLayout & MASK_LAYOUTDIR)) diffs |= CONFIG_LAYOUTDIR;
+ if ((screenLayout & ~MASK_LAYOUTDIR) != (o.screenLayout & ~MASK_LAYOUTDIR)) diffs |= CONFIG_SCREEN_LAYOUT;
+ if (uiMode != o.uiMode) diffs |= CONFIG_UI_MODE;
+ if (smallestScreenWidthDp != o.smallestScreenWidthDp) diffs |= CONFIG_SMALLEST_SCREEN_SIZE;
+ if (screenSizeDp != o.screenSizeDp) diffs |= CONFIG_SCREEN_SIZE;
+ return diffs;
+}
+
+bool ResTable_config::isMoreSpecificThan(const ResTable_config& o) const {
+ // The order of the following tests defines the importance of one
+ // configuration parameter over another. Those tests first are more
+ // important, trumping any values in those following them.
+ if (imsi || o.imsi) {
+ if (mcc != o.mcc) {
+ if (!mcc) return false;
+ if (!o.mcc) return true;
+ }
+
+ if (mnc != o.mnc) {
+ if (!mnc) return false;
+ if (!o.mnc) return true;
+ }
+ }
+
+ if (locale || o.locale) {
+ if (language[0] != o.language[0]) {
+ if (!language[0]) return false;
+ if (!o.language[0]) return true;
+ }
+
+ if (country[0] != o.country[0]) {
+ if (!country[0]) return false;
+ if (!o.country[0]) return true;
+ }
+ }
+
+ if (screenLayout || o.screenLayout) {
+ if (((screenLayout^o.screenLayout) & MASK_LAYOUTDIR) != 0) {
+ if (!(screenLayout & MASK_LAYOUTDIR)) return false;
+ if (!(o.screenLayout & MASK_LAYOUTDIR)) return true;
+ }
+ }
+
+ if (smallestScreenWidthDp || o.smallestScreenWidthDp) {
+ if (smallestScreenWidthDp != o.smallestScreenWidthDp) {
+ if (!smallestScreenWidthDp) return false;
+ if (!o.smallestScreenWidthDp) return true;
+ }
+ }
+
+ if (screenSizeDp || o.screenSizeDp) {
+ if (screenWidthDp != o.screenWidthDp) {
+ if (!screenWidthDp) return false;
+ if (!o.screenWidthDp) return true;
+ }
+
+ if (screenHeightDp != o.screenHeightDp) {
+ if (!screenHeightDp) return false;
+ if (!o.screenHeightDp) return true;
+ }
+ }
+
+ if (screenLayout || o.screenLayout) {
+ if (((screenLayout^o.screenLayout) & MASK_SCREENSIZE) != 0) {
+ if (!(screenLayout & MASK_SCREENSIZE)) return false;
+ if (!(o.screenLayout & MASK_SCREENSIZE)) return true;
+ }
+ if (((screenLayout^o.screenLayout) & MASK_SCREENLONG) != 0) {
+ if (!(screenLayout & MASK_SCREENLONG)) return false;
+ if (!(o.screenLayout & MASK_SCREENLONG)) return true;
+ }
+ }
+
+ if (orientation != o.orientation) {
+ if (!orientation) return false;
+ if (!o.orientation) return true;
+ }
+
+ if (uiMode || o.uiMode) {
+ if (((uiMode^o.uiMode) & MASK_UI_MODE_TYPE) != 0) {
+ if (!(uiMode & MASK_UI_MODE_TYPE)) return false;
+ if (!(o.uiMode & MASK_UI_MODE_TYPE)) return true;
+ }
+ if (((uiMode^o.uiMode) & MASK_UI_MODE_NIGHT) != 0) {
+ if (!(uiMode & MASK_UI_MODE_NIGHT)) return false;
+ if (!(o.uiMode & MASK_UI_MODE_NIGHT)) return true;
+ }
+ }
+
+ // density is never 'more specific'
+ // as the default just equals 160
+
+ if (touchscreen != o.touchscreen) {
+ if (!touchscreen) return false;
+ if (!o.touchscreen) return true;
+ }
+
+ if (input || o.input) {
+ if (((inputFlags^o.inputFlags) & MASK_KEYSHIDDEN) != 0) {
+ if (!(inputFlags & MASK_KEYSHIDDEN)) return false;
+ if (!(o.inputFlags & MASK_KEYSHIDDEN)) return true;
+ }
+
+ if (((inputFlags^o.inputFlags) & MASK_NAVHIDDEN) != 0) {
+ if (!(inputFlags & MASK_NAVHIDDEN)) return false;
+ if (!(o.inputFlags & MASK_NAVHIDDEN)) return true;
+ }
+
+ if (keyboard != o.keyboard) {
+ if (!keyboard) return false;
+ if (!o.keyboard) return true;
+ }
+
+ if (navigation != o.navigation) {
+ if (!navigation) return false;
+ if (!o.navigation) return true;
+ }
+ }
+
+ if (screenSize || o.screenSize) {
+ if (screenWidth != o.screenWidth) {
+ if (!screenWidth) return false;
+ if (!o.screenWidth) return true;
+ }
+
+ if (screenHeight != o.screenHeight) {
+ if (!screenHeight) return false;
+ if (!o.screenHeight) return true;
+ }
+ }
+
+ if (version || o.version) {
+ if (sdkVersion != o.sdkVersion) {
+ if (!sdkVersion) return false;
+ if (!o.sdkVersion) return true;
+ }
+
+ if (minorVersion != o.minorVersion) {
+ if (!minorVersion) return false;
+ if (!o.minorVersion) return true;
+ }
+ }
+ return false;
+}
+
+bool ResTable_config::isBetterThan(const ResTable_config& o,
+ const ResTable_config* requested) const {
+ if (requested) {
+ if (imsi || o.imsi) {
+ if ((mcc != o.mcc) && requested->mcc) {
+ return (mcc);
+ }
+
+ if ((mnc != o.mnc) && requested->mnc) {
+ return (mnc);
+ }
+ }
+
+ if (locale || o.locale) {
+ if ((language[0] != o.language[0]) && requested->language[0]) {
+ return (language[0]);
+ }
+
+ if ((country[0] != o.country[0]) && requested->country[0]) {
+ return (country[0]);
+ }
+ }
+
+ if (screenLayout || o.screenLayout) {
+ if (((screenLayout^o.screenLayout) & MASK_LAYOUTDIR) != 0
+ && (requested->screenLayout & MASK_LAYOUTDIR)) {
+ int myLayoutDir = screenLayout & MASK_LAYOUTDIR;
+ int oLayoutDir = o.screenLayout & MASK_LAYOUTDIR;
+ return (myLayoutDir > oLayoutDir);
+ }
+ }
+
+ if (smallestScreenWidthDp || o.smallestScreenWidthDp) {
+ // The configuration closest to the actual size is best.
+ // We assume that larger configs have already been filtered
+ // out at this point. That means we just want the largest one.
+ if (smallestScreenWidthDp != o.smallestScreenWidthDp) {
+ return smallestScreenWidthDp > o.smallestScreenWidthDp;
+ }
+ }
+
+ if (screenSizeDp || o.screenSizeDp) {
+ // "Better" is based on the sum of the difference between both
+ // width and height from the requested dimensions. We are
+ // assuming the invalid configs (with smaller dimens) have
+ // already been filtered. Note that if a particular dimension
+ // is unspecified, we will end up with a large value (the
+ // difference between 0 and the requested dimension), which is
+ // good since we will prefer a config that has specified a
+ // dimension value.
+ int myDelta = 0, otherDelta = 0;
+ if (requested->screenWidthDp) {
+ myDelta += requested->screenWidthDp - screenWidthDp;
+ otherDelta += requested->screenWidthDp - o.screenWidthDp;
+ }
+ if (requested->screenHeightDp) {
+ myDelta += requested->screenHeightDp - screenHeightDp;
+ otherDelta += requested->screenHeightDp - o.screenHeightDp;
+ }
+ //ALOGI("Comparing this %dx%d to other %dx%d in %dx%d: myDelta=%d otherDelta=%d",
+ // screenWidthDp, screenHeightDp, o.screenWidthDp, o.screenHeightDp,
+ // requested->screenWidthDp, requested->screenHeightDp, myDelta, otherDelta);
+ if (myDelta != otherDelta) {
+ return myDelta < otherDelta;
+ }
+ }
+
+ if (screenLayout || o.screenLayout) {
+ if (((screenLayout^o.screenLayout) & MASK_SCREENSIZE) != 0
+ && (requested->screenLayout & MASK_SCREENSIZE)) {
+ // A little backwards compatibility here: undefined is
+ // considered equivalent to normal. But only if the
+ // requested size is at least normal; otherwise, small
+ // is better than the default.
+ int mySL = (screenLayout & MASK_SCREENSIZE);
+ int oSL = (o.screenLayout & MASK_SCREENSIZE);
+ int fixedMySL = mySL;
+ int fixedOSL = oSL;
+ if ((requested->screenLayout & MASK_SCREENSIZE) >= SCREENSIZE_NORMAL) {
+ if (fixedMySL == 0) fixedMySL = SCREENSIZE_NORMAL;
+ if (fixedOSL == 0) fixedOSL = SCREENSIZE_NORMAL;
+ }
+ // For screen size, the best match is the one that is
+ // closest to the requested screen size, but not over
+ // (the not over part is dealt with in match() below).
+ if (fixedMySL == fixedOSL) {
+ // If the two are the same, but 'this' is actually
+ // undefined, then the other is really a better match.
+ if (mySL == 0) return false;
+ return true;
+ }
+ if (fixedMySL != fixedOSL) {
+ return fixedMySL > fixedOSL;
+ }
+ }
+ if (((screenLayout^o.screenLayout) & MASK_SCREENLONG) != 0
+ && (requested->screenLayout & MASK_SCREENLONG)) {
+ return (screenLayout & MASK_SCREENLONG);
+ }
+ }
+
+ if ((orientation != o.orientation) && requested->orientation) {
+ return (orientation);
+ }
+
+ if (uiMode || o.uiMode) {
+ if (((uiMode^o.uiMode) & MASK_UI_MODE_TYPE) != 0
+ && (requested->uiMode & MASK_UI_MODE_TYPE)) {
+ return (uiMode & MASK_UI_MODE_TYPE);
+ }
+ if (((uiMode^o.uiMode) & MASK_UI_MODE_NIGHT) != 0
+ && (requested->uiMode & MASK_UI_MODE_NIGHT)) {
+ return (uiMode & MASK_UI_MODE_NIGHT);
+ }
+ }
+
+ if (screenType || o.screenType) {
+ if (density != o.density) {
+ // density is tough. Any density is potentially useful
+ // because the system will scale it. Scaling down
+ // is generally better than scaling up.
+ // Default density counts as 160dpi (the system default)
+ // TODO - remove 160 constants
+ int h = (density?density:160);
+ int l = (o.density?o.density:160);
+ bool bImBigger = true;
+ if (l > h) {
+ int t = h;
+ h = l;
+ l = t;
+ bImBigger = false;
+ }
+
+ int reqValue = (requested->density?requested->density:160);
+ if (reqValue >= h) {
+ // requested value higher than both l and h, give h
+ return bImBigger;
+ }
+ if (l >= reqValue) {
+ // requested value lower than both l and h, give l
+ return !bImBigger;
+ }
+ // saying that scaling down is 2x better than up
+ if (((2 * l) - reqValue) * h > reqValue * reqValue) {
+ return !bImBigger;
+ } else {
+ return bImBigger;
+ }
+ }
+
+ if ((touchscreen != o.touchscreen) && requested->touchscreen) {
+ return (touchscreen);
+ }
+ }
+
+ if (input || o.input) {
+ const int keysHidden = inputFlags & MASK_KEYSHIDDEN;
+ const int oKeysHidden = o.inputFlags & MASK_KEYSHIDDEN;
+ if (keysHidden != oKeysHidden) {
+ const int reqKeysHidden =
+ requested->inputFlags & MASK_KEYSHIDDEN;
+ if (reqKeysHidden) {
+
+ if (!keysHidden) return false;
+ if (!oKeysHidden) return true;
+ // For compatibility, we count KEYSHIDDEN_NO as being
+ // the same as KEYSHIDDEN_SOFT. Here we disambiguate
+ // these by making an exact match more specific.
+ if (reqKeysHidden == keysHidden) return true;
+ if (reqKeysHidden == oKeysHidden) return false;
+ }
+ }
+
+ const int navHidden = inputFlags & MASK_NAVHIDDEN;
+ const int oNavHidden = o.inputFlags & MASK_NAVHIDDEN;
+ if (navHidden != oNavHidden) {
+ const int reqNavHidden =
+ requested->inputFlags & MASK_NAVHIDDEN;
+ if (reqNavHidden) {
+
+ if (!navHidden) return false;
+ if (!oNavHidden) return true;
+ }
+ }
+
+ if ((keyboard != o.keyboard) && requested->keyboard) {
+ return (keyboard);
+ }
+
+ if ((navigation != o.navigation) && requested->navigation) {
+ return (navigation);
+ }
+ }
+
+ if (screenSize || o.screenSize) {
+ // "Better" is based on the sum of the difference between both
+ // width and height from the requested dimensions. We are
+ // assuming the invalid configs (with smaller sizes) have
+ // already been filtered. Note that if a particular dimension
+ // is unspecified, we will end up with a large value (the
+ // difference between 0 and the requested dimension), which is
+ // good since we will prefer a config that has specified a
+ // size value.
+ int myDelta = 0, otherDelta = 0;
+ if (requested->screenWidth) {
+ myDelta += requested->screenWidth - screenWidth;
+ otherDelta += requested->screenWidth - o.screenWidth;
+ }
+ if (requested->screenHeight) {
+ myDelta += requested->screenHeight - screenHeight;
+ otherDelta += requested->screenHeight - o.screenHeight;
+ }
+ if (myDelta != otherDelta) {
+ return myDelta < otherDelta;
+ }
+ }
+
+ if (version || o.version) {
+ if ((sdkVersion != o.sdkVersion) && requested->sdkVersion) {
+ return (sdkVersion > o.sdkVersion);
+ }
+
+ if ((minorVersion != o.minorVersion) &&
+ requested->minorVersion) {
+ return (minorVersion);
+ }
+ }
+
+ return false;
+ }
+ return isMoreSpecificThan(o);
+}
+
+bool ResTable_config::match(const ResTable_config& settings) const {
+ if (imsi != 0) {
+ if (mcc != 0 && mcc != settings.mcc) {
+ return false;
+ }
+ if (mnc != 0 && mnc != settings.mnc) {
+ return false;
+ }
+ }
+ if (locale != 0) {
+ if (language[0] != 0
+ && (language[0] != settings.language[0]
+ || language[1] != settings.language[1])) {
+ return false;
+ }
+ if (country[0] != 0
+ && (country[0] != settings.country[0]
+ || country[1] != settings.country[1])) {
+ return false;
+ }
+ }
+ if (screenConfig != 0) {
+ const int layoutDir = screenLayout&MASK_LAYOUTDIR;
+ const int setLayoutDir = settings.screenLayout&MASK_LAYOUTDIR;
+ if (layoutDir != 0 && layoutDir != setLayoutDir) {
+ return false;
+ }
+
+ const int screenSize = screenLayout&MASK_SCREENSIZE;
+ const int setScreenSize = settings.screenLayout&MASK_SCREENSIZE;
+ // Any screen sizes for larger screens than the setting do not
+ // match.
+ if (screenSize != 0 && screenSize > setScreenSize) {
+ return false;
+ }
+
+ const int screenLong = screenLayout&MASK_SCREENLONG;
+ const int setScreenLong = settings.screenLayout&MASK_SCREENLONG;
+ if (screenLong != 0 && screenLong != setScreenLong) {
+ return false;
+ }
+
+ const int uiModeType = uiMode&MASK_UI_MODE_TYPE;
+ const int setUiModeType = settings.uiMode&MASK_UI_MODE_TYPE;
+ if (uiModeType != 0 && uiModeType != setUiModeType) {
+ return false;
+ }
+
+ const int uiModeNight = uiMode&MASK_UI_MODE_NIGHT;
+ const int setUiModeNight = settings.uiMode&MASK_UI_MODE_NIGHT;
+ if (uiModeNight != 0 && uiModeNight != setUiModeNight) {
+ return false;
+ }
+
+ if (smallestScreenWidthDp != 0
+ && smallestScreenWidthDp > settings.smallestScreenWidthDp) {
+ return false;
+ }
+ }
+ if (screenSizeDp != 0) {
+ if (screenWidthDp != 0 && screenWidthDp > settings.screenWidthDp) {
+ //ALOGI("Filtering out width %d in requested %d", screenWidthDp, settings.screenWidthDp);
+ return false;
+ }
+ if (screenHeightDp != 0 && screenHeightDp > settings.screenHeightDp) {
+ //ALOGI("Filtering out height %d in requested %d", screenHeightDp, settings.screenHeightDp);
+ return false;
+ }
+ }
+ if (screenType != 0) {
+ if (orientation != 0 && orientation != settings.orientation) {
+ return false;
+ }
+ // density always matches - we can scale it. See isBetterThan
+ if (touchscreen != 0 && touchscreen != settings.touchscreen) {
+ return false;
+ }
+ }
+ if (input != 0) {
+ const int keysHidden = inputFlags&MASK_KEYSHIDDEN;
+ const int setKeysHidden = settings.inputFlags&MASK_KEYSHIDDEN;
+ if (keysHidden != 0 && keysHidden != setKeysHidden) {
+ // For compatibility, we count a request for KEYSHIDDEN_NO as also
+ // matching the more recent KEYSHIDDEN_SOFT. Basically
+ // KEYSHIDDEN_NO means there is some kind of keyboard available.
+ //ALOGI("Matching keysHidden: have=%d, config=%d\n", keysHidden, setKeysHidden);
+ if (keysHidden != KEYSHIDDEN_NO || setKeysHidden != KEYSHIDDEN_SOFT) {
+ //ALOGI("No match!");
+ return false;
+ }
+ }
+ const int navHidden = inputFlags&MASK_NAVHIDDEN;
+ const int setNavHidden = settings.inputFlags&MASK_NAVHIDDEN;
+ if (navHidden != 0 && navHidden != setNavHidden) {
+ return false;
+ }
+ if (keyboard != 0 && keyboard != settings.keyboard) {
+ return false;
+ }
+ if (navigation != 0 && navigation != settings.navigation) {
+ return false;
+ }
+ }
+ if (screenSize != 0) {
+ if (screenWidth != 0 && screenWidth > settings.screenWidth) {
+ return false;
+ }
+ if (screenHeight != 0 && screenHeight > settings.screenHeight) {
+ return false;
+ }
+ }
+ if (version != 0) {
+ if (sdkVersion != 0 && sdkVersion > settings.sdkVersion) {
+ return false;
+ }
+ if (minorVersion != 0 && minorVersion != settings.minorVersion) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void ResTable_config::getLocale(char str[6]) const {
+ memset(str, 0, 6);
+ if (language[0]) {
+ str[0] = language[0];
+ str[1] = language[1];
+ if (country[0]) {
+ str[2] = '_';
+ str[3] = country[0];
+ str[4] = country[1];
+ }
+ }
+}
+
+String8 ResTable_config::toString() const {
+ String8 res;
+
+ if (mcc != 0) {
+ if (res.size() > 0) res.append("-");
+ res.appendFormat("%dmcc", dtohs(mcc));
+ }
+ if (mnc != 0) {
+ if (res.size() > 0) res.append("-");
+ res.appendFormat("%dmnc", dtohs(mnc));
+ }
+ if (language[0] != 0) {
+ if (res.size() > 0) res.append("-");
+ res.append(language, 2);
+ }
+ if (country[0] != 0) {
+ if (res.size() > 0) res.append("-");
+ res.append(country, 2);
+ }
+ if ((screenLayout&MASK_LAYOUTDIR) != 0) {
+ if (res.size() > 0) res.append("-");
+ switch (screenLayout&ResTable_config::MASK_LAYOUTDIR) {
+ case ResTable_config::LAYOUTDIR_LTR:
+ res.append("ldltr");
+ break;
+ case ResTable_config::LAYOUTDIR_RTL:
+ res.append("ldrtl");
+ break;
+ default:
+ res.appendFormat("layoutDir=%d",
+ dtohs(screenLayout&ResTable_config::MASK_LAYOUTDIR));
+ break;
+ }
+ }
+ if (smallestScreenWidthDp != 0) {
+ if (res.size() > 0) res.append("-");
+ res.appendFormat("sw%ddp", dtohs(smallestScreenWidthDp));
+ }
+ if (screenWidthDp != 0) {
+ if (res.size() > 0) res.append("-");
+ res.appendFormat("w%ddp", dtohs(screenWidthDp));
+ }
+ if (screenHeightDp != 0) {
+ if (res.size() > 0) res.append("-");
+ res.appendFormat("h%ddp", dtohs(screenHeightDp));
+ }
+ if ((screenLayout&MASK_SCREENSIZE) != SCREENSIZE_ANY) {
+ if (res.size() > 0) res.append("-");
+ switch (screenLayout&ResTable_config::MASK_SCREENSIZE) {
+ case ResTable_config::SCREENSIZE_SMALL:
+ res.append("small");
+ break;
+ case ResTable_config::SCREENSIZE_NORMAL:
+ res.append("normal");
+ break;
+ case ResTable_config::SCREENSIZE_LARGE:
+ res.append("large");
+ break;
+ case ResTable_config::SCREENSIZE_XLARGE:
+ res.append("xlarge");
+ break;
+ default:
+ res.appendFormat("screenLayoutSize=%d",
+ dtohs(screenLayout&ResTable_config::MASK_SCREENSIZE));
+ break;
+ }
+ }
+ if ((screenLayout&MASK_SCREENLONG) != 0) {
+ if (res.size() > 0) res.append("-");
+ switch (screenLayout&ResTable_config::MASK_SCREENLONG) {
+ case ResTable_config::SCREENLONG_NO:
+ res.append("notlong");
+ break;
+ case ResTable_config::SCREENLONG_YES:
+ res.append("long");
+ break;
+ default:
+ res.appendFormat("screenLayoutLong=%d",
+ dtohs(screenLayout&ResTable_config::MASK_SCREENLONG));
+ break;
+ }
+ }
+ if (orientation != ORIENTATION_ANY) {
+ if (res.size() > 0) res.append("-");
+ switch (orientation) {
+ case ResTable_config::ORIENTATION_PORT:
+ res.append("port");
+ break;
+ case ResTable_config::ORIENTATION_LAND:
+ res.append("land");
+ break;
+ case ResTable_config::ORIENTATION_SQUARE:
+ res.append("square");
+ break;
+ default:
+ res.appendFormat("orientation=%d", dtohs(orientation));
+ break;
+ }
+ }
+ if ((uiMode&MASK_UI_MODE_TYPE) != UI_MODE_TYPE_ANY) {
+ if (res.size() > 0) res.append("-");
+ switch (uiMode&ResTable_config::MASK_UI_MODE_TYPE) {
+ case ResTable_config::UI_MODE_TYPE_DESK:
+ res.append("desk");
+ break;
+ case ResTable_config::UI_MODE_TYPE_CAR:
+ res.append("car");
+ break;
+ case ResTable_config::UI_MODE_TYPE_TELEVISION:
+ res.append("television");
+ break;
+ case ResTable_config::UI_MODE_TYPE_APPLIANCE:
+ res.append("appliance");
+ break;
+ default:
+ res.appendFormat("uiModeType=%d",
+ dtohs(screenLayout&ResTable_config::MASK_UI_MODE_TYPE));
+ break;
+ }
+ }
+ if ((uiMode&MASK_UI_MODE_NIGHT) != 0) {
+ if (res.size() > 0) res.append("-");
+ switch (uiMode&ResTable_config::MASK_UI_MODE_NIGHT) {
+ case ResTable_config::UI_MODE_NIGHT_NO:
+ res.append("notnight");
+ break;
+ case ResTable_config::UI_MODE_NIGHT_YES:
+ res.append("night");
+ break;
+ default:
+ res.appendFormat("uiModeNight=%d",
+ dtohs(uiMode&MASK_UI_MODE_NIGHT));
+ break;
+ }
+ }
+ if (density != DENSITY_DEFAULT) {
+ if (res.size() > 0) res.append("-");
+ switch (density) {
+ case ResTable_config::DENSITY_LOW:
+ res.append("ldpi");
+ break;
+ case ResTable_config::DENSITY_MEDIUM:
+ res.append("mdpi");
+ break;
+ case ResTable_config::DENSITY_TV:
+ res.append("tvdpi");
+ break;
+ case ResTable_config::DENSITY_HIGH:
+ res.append("hdpi");
+ break;
+ case ResTable_config::DENSITY_XHIGH:
+ res.append("xhdpi");
+ break;
+ case ResTable_config::DENSITY_XXHIGH:
+ res.append("xxhdpi");
+ break;
+ case ResTable_config::DENSITY_NONE:
+ res.append("nodpi");
+ break;
+ default:
+ res.appendFormat("%ddpi", dtohs(density));
+ break;
+ }
+ }
+ if (touchscreen != TOUCHSCREEN_ANY) {
+ if (res.size() > 0) res.append("-");
+ switch (touchscreen) {
+ case ResTable_config::TOUCHSCREEN_NOTOUCH:
+ res.append("notouch");
+ break;
+ case ResTable_config::TOUCHSCREEN_FINGER:
+ res.append("finger");
+ break;
+ case ResTable_config::TOUCHSCREEN_STYLUS:
+ res.append("stylus");
+ break;
+ default:
+ res.appendFormat("touchscreen=%d", dtohs(touchscreen));
+ break;
+ }
+ }
+ if (keyboard != KEYBOARD_ANY) {
+ if (res.size() > 0) res.append("-");
+ switch (keyboard) {
+ case ResTable_config::KEYBOARD_NOKEYS:
+ res.append("nokeys");
+ break;
+ case ResTable_config::KEYBOARD_QWERTY:
+ res.append("qwerty");
+ break;
+ case ResTable_config::KEYBOARD_12KEY:
+ res.append("12key");
+ break;
+ default:
+ res.appendFormat("keyboard=%d", dtohs(keyboard));
+ break;
+ }
+ }
+ if ((inputFlags&MASK_KEYSHIDDEN) != 0) {
+ if (res.size() > 0) res.append("-");
+ switch (inputFlags&MASK_KEYSHIDDEN) {
+ case ResTable_config::KEYSHIDDEN_NO:
+ res.append("keysexposed");
+ break;
+ case ResTable_config::KEYSHIDDEN_YES:
+ res.append("keyshidden");
+ break;
+ case ResTable_config::KEYSHIDDEN_SOFT:
+ res.append("keyssoft");
+ break;
+ }
+ }
+ if (navigation != NAVIGATION_ANY) {
+ if (res.size() > 0) res.append("-");
+ switch (navigation) {
+ case ResTable_config::NAVIGATION_NONAV:
+ res.append("nonav");
+ break;
+ case ResTable_config::NAVIGATION_DPAD:
+ res.append("dpad");
+ break;
+ case ResTable_config::NAVIGATION_TRACKBALL:
+ res.append("trackball");
+ break;
+ case ResTable_config::NAVIGATION_WHEEL:
+ res.append("wheel");
+ break;
+ default:
+ res.appendFormat("navigation=%d", dtohs(navigation));
+ break;
+ }
+ }
+ if ((inputFlags&MASK_NAVHIDDEN) != 0) {
+ if (res.size() > 0) res.append("-");
+ switch (inputFlags&MASK_NAVHIDDEN) {
+ case ResTable_config::NAVHIDDEN_NO:
+ res.append("navsexposed");
+ break;
+ case ResTable_config::NAVHIDDEN_YES:
+ res.append("navhidden");
+ break;
+ default:
+ res.appendFormat("inputFlagsNavHidden=%d",
+ dtohs(inputFlags&MASK_NAVHIDDEN));
+ break;
+ }
+ }
+ if (screenSize != 0) {
+ if (res.size() > 0) res.append("-");
+ res.appendFormat("%dx%d", dtohs(screenWidth), dtohs(screenHeight));
+ }
+ if (version != 0) {
+ if (res.size() > 0) res.append("-");
+ res.appendFormat("v%d", dtohs(sdkVersion));
+ if (minorVersion != 0) {
+ res.appendFormat(".%d", dtohs(minorVersion));
+ }
+ }
+
+ return res;
+}
+
+// --------------------------------------------------------------------
+// --------------------------------------------------------------------
+// --------------------------------------------------------------------
+
+struct ResTable::Header
+{
+ Header(ResTable* _owner) : owner(_owner), ownedData(NULL), header(NULL),
+ resourceIDMap(NULL), resourceIDMapSize(0) { }
+
+ ~Header()
+ {
+ free(resourceIDMap);
+ }
+
+ ResTable* const owner;
+ void* ownedData;
+ const ResTable_header* header;
+ size_t size;
+ const uint8_t* dataEnd;
+ size_t index;
+ void* cookie;
+
+ ResStringPool values;
+ uint32_t* resourceIDMap;
+ size_t resourceIDMapSize;
+};
+
+struct ResTable::Type
+{
+ Type(const Header* _header, const Package* _package, size_t count)
+ : header(_header), package(_package), entryCount(count),
+ typeSpec(NULL), typeSpecFlags(NULL) { }
+ const Header* const header;
+ const Package* const package;
+ const size_t entryCount;
+ const ResTable_typeSpec* typeSpec;
+ const uint32_t* typeSpecFlags;
+ Vector<const ResTable_type*> configs;
+};
+
+struct ResTable::Package
+{
+ Package(ResTable* _owner, const Header* _header, const ResTable_package* _package)
+ : owner(_owner), header(_header), package(_package) { }
+ ~Package()
+ {
+ size_t i = types.size();
+ while (i > 0) {
+ i--;
+ delete types[i];
+ }
+ }
+
+ ResTable* const owner;
+ const Header* const header;
+ const ResTable_package* const package;
+ Vector<Type*> types;
+
+ ResStringPool typeStrings;
+ ResStringPool keyStrings;
+
+ const Type* getType(size_t idx) const {
+ return idx < types.size() ? types[idx] : NULL;
+ }
+};
+
+// A group of objects describing a particular resource package.
+// The first in 'package' is always the root object (from the resource
+// table that defined the package); the ones after are skins on top of it.
+struct ResTable::PackageGroup
+{
+ PackageGroup(ResTable* _owner, const String16& _name, uint32_t _id)
+ : owner(_owner), name(_name), id(_id), typeCount(0), bags(NULL) { }
+ ~PackageGroup() {
+ clearBagCache();
+ const size_t N = packages.size();
+ for (size_t i=0; i<N; i++) {
+ Package* pkg = packages[i];
+ if (pkg->owner == owner) {
+ delete pkg;
+ }
+ }
+ }
+
+ void clearBagCache() {
+ if (bags) {
+ TABLE_NOISY(printf("bags=%p\n", bags));
+ Package* pkg = packages[0];
+ TABLE_NOISY(printf("typeCount=%x\n", typeCount));
+ for (size_t i=0; i<typeCount; i++) {
+ TABLE_NOISY(printf("type=%d\n", i));
+ const Type* type = pkg->getType(i);
+ if (type != NULL) {
+ bag_set** typeBags = bags[i];
+ TABLE_NOISY(printf("typeBags=%p\n", typeBags));
+ if (typeBags) {
+ TABLE_NOISY(printf("type->entryCount=%x\n", type->entryCount));
+ const size_t N = type->entryCount;
+ for (size_t j=0; j<N; j++) {
+ if (typeBags[j] && typeBags[j] != (bag_set*)0xFFFFFFFF)
+ free(typeBags[j]);
+ }
+ free(typeBags);
+ }
+ }
+ }
+ free(bags);
+ bags = NULL;
+ }
+ }
+
+ ResTable* const owner;
+ String16 const name;
+ uint32_t const id;
+ Vector<Package*> packages;
+
+ // This is for finding typeStrings and other common package stuff.
+ Package* basePackage;
+
+ // For quick access.
+ size_t typeCount;
+
+ // Computed attribute bags, first indexed by the type and second
+ // by the entry in that type.
+ bag_set*** bags;
+};
+
+struct ResTable::bag_set
+{
+ size_t numAttrs; // number in array
+ size_t availAttrs; // total space in array
+ uint32_t typeSpecFlags;
+ // Followed by 'numAttr' bag_entry structures.
+};
+
+ResTable::Theme::Theme(const ResTable& table)
+ : mTable(table)
+{
+ memset(mPackages, 0, sizeof(mPackages));
+}
+
+ResTable::Theme::~Theme()
+{
+ for (size_t i=0; i<Res_MAXPACKAGE; i++) {
+ package_info* pi = mPackages[i];
+ if (pi != NULL) {
+ free_package(pi);
+ }
+ }
+}
+
+void ResTable::Theme::free_package(package_info* pi)
+{
+ for (size_t j=0; j<pi->numTypes; j++) {
+ theme_entry* te = pi->types[j].entries;
+ if (te != NULL) {
+ free(te);
+ }
+ }
+ free(pi);
+}
+
+ResTable::Theme::package_info* ResTable::Theme::copy_package(package_info* pi)
+{
+ package_info* newpi = (package_info*)malloc(
+ sizeof(package_info) + (pi->numTypes*sizeof(type_info)));
+ newpi->numTypes = pi->numTypes;
+ for (size_t j=0; j<newpi->numTypes; j++) {
+ size_t cnt = pi->types[j].numEntries;
+ newpi->types[j].numEntries = cnt;
+ theme_entry* te = pi->types[j].entries;
+ if (te != NULL) {
+ theme_entry* newte = (theme_entry*)malloc(cnt*sizeof(theme_entry));
+ newpi->types[j].entries = newte;
+ memcpy(newte, te, cnt*sizeof(theme_entry));
+ } else {
+ newpi->types[j].entries = NULL;
+ }
+ }
+ return newpi;
+}
+
+status_t ResTable::Theme::applyStyle(uint32_t resID, bool force)
+{
+ const bag_entry* bag;
+ uint32_t bagTypeSpecFlags = 0;
+ mTable.lock();
+ const ssize_t N = mTable.getBagLocked(resID, &bag, &bagTypeSpecFlags);
+ TABLE_NOISY(ALOGV("Applying style 0x%08x to theme %p, count=%d", resID, this, N));
+ if (N < 0) {
+ mTable.unlock();
+ return N;
+ }
+
+ uint32_t curPackage = 0xffffffff;
+ ssize_t curPackageIndex = 0;
+ package_info* curPI = NULL;
+ uint32_t curType = 0xffffffff;
+ size_t numEntries = 0;
+ theme_entry* curEntries = NULL;
+
+ const bag_entry* end = bag + N;
+ while (bag < end) {
+ const uint32_t attrRes = bag->map.name.ident;
+ const uint32_t p = Res_GETPACKAGE(attrRes);
+ const uint32_t t = Res_GETTYPE(attrRes);
+ const uint32_t e = Res_GETENTRY(attrRes);
+
+ if (curPackage != p) {
+ const ssize_t pidx = mTable.getResourcePackageIndex(attrRes);
+ if (pidx < 0) {
+ ALOGE("Style contains key with bad package: 0x%08x\n", attrRes);
+ bag++;
+ continue;
+ }
+ curPackage = p;
+ curPackageIndex = pidx;
+ curPI = mPackages[pidx];
+ if (curPI == NULL) {
+ PackageGroup* const grp = mTable.mPackageGroups[pidx];
+ int cnt = grp->typeCount;
+ curPI = (package_info*)malloc(
+ sizeof(package_info) + (cnt*sizeof(type_info)));
+ curPI->numTypes = cnt;
+ memset(curPI->types, 0, cnt*sizeof(type_info));
+ mPackages[pidx] = curPI;
+ }
+ curType = 0xffffffff;
+ }
+ if (curType != t) {
+ if (t >= curPI->numTypes) {
+ ALOGE("Style contains key with bad type: 0x%08x\n", attrRes);
+ bag++;
+ continue;
+ }
+ curType = t;
+ curEntries = curPI->types[t].entries;
+ if (curEntries == NULL) {
+ PackageGroup* const grp = mTable.mPackageGroups[curPackageIndex];
+ const Type* type = grp->packages[0]->getType(t);
+ int cnt = type != NULL ? type->entryCount : 0;
+ curEntries = (theme_entry*)malloc(cnt*sizeof(theme_entry));
+ memset(curEntries, Res_value::TYPE_NULL, cnt*sizeof(theme_entry));
+ curPI->types[t].numEntries = cnt;
+ curPI->types[t].entries = curEntries;
+ }
+ numEntries = curPI->types[t].numEntries;
+ }
+ if (e >= numEntries) {
+ ALOGE("Style contains key with bad entry: 0x%08x\n", attrRes);
+ bag++;
+ continue;
+ }
+ theme_entry* curEntry = curEntries + e;
+ TABLE_NOISY(ALOGV("Attr 0x%08x: type=0x%x, data=0x%08x; curType=0x%x",
+ attrRes, bag->map.value.dataType, bag->map.value.data,
+ curEntry->value.dataType));
+ if (force || curEntry->value.dataType == Res_value::TYPE_NULL) {
+ curEntry->stringBlock = bag->stringBlock;
+ curEntry->typeSpecFlags |= bagTypeSpecFlags;
+ curEntry->value = bag->map.value;
+ }
+
+ bag++;
+ }
+
+ mTable.unlock();
+
+ //ALOGI("Applying style 0x%08x (force=%d) theme %p...\n", resID, force, this);
+ //dumpToLog();
+
+ return NO_ERROR;
+}
+
+status_t ResTable::Theme::setTo(const Theme& other)
+{
+ //ALOGI("Setting theme %p from theme %p...\n", this, &other);
+ //dumpToLog();
+ //other.dumpToLog();
+
+ if (&mTable == &other.mTable) {
+ for (size_t i=0; i<Res_MAXPACKAGE; i++) {
+ if (mPackages[i] != NULL) {
+ free_package(mPackages[i]);
+ }
+ if (other.mPackages[i] != NULL) {
+ mPackages[i] = copy_package(other.mPackages[i]);
+ } else {
+ mPackages[i] = NULL;
+ }
+ }
+ } else {
+ // @todo: need to really implement this, not just copy
+ // the system package (which is still wrong because it isn't
+ // fixing up resource references).
+ for (size_t i=0; i<Res_MAXPACKAGE; i++) {
+ if (mPackages[i] != NULL) {
+ free_package(mPackages[i]);
+ }
+ if (i == 0 && other.mPackages[i] != NULL) {
+ mPackages[i] = copy_package(other.mPackages[i]);
+ } else {
+ mPackages[i] = NULL;
+ }
+ }
+ }
+
+ //ALOGI("Final theme:");
+ //dumpToLog();
+
+ return NO_ERROR;
+}
+
+ssize_t ResTable::Theme::getAttribute(uint32_t resID, Res_value* outValue,
+ uint32_t* outTypeSpecFlags) const
+{
+ int cnt = 20;
+
+ if (outTypeSpecFlags != NULL) *outTypeSpecFlags = 0;
+
+ do {
+ const ssize_t p = mTable.getResourcePackageIndex(resID);
+ const uint32_t t = Res_GETTYPE(resID);
+ const uint32_t e = Res_GETENTRY(resID);
+
+ TABLE_THEME(ALOGI("Looking up attr 0x%08x in theme %p", resID, this));
+
+ if (p >= 0) {
+ const package_info* const pi = mPackages[p];
+ TABLE_THEME(ALOGI("Found package: %p", pi));
+ if (pi != NULL) {
+ TABLE_THEME(ALOGI("Desired type index is %ld in avail %d", t, pi->numTypes));
+ if (t < pi->numTypes) {
+ const type_info& ti = pi->types[t];
+ TABLE_THEME(ALOGI("Desired entry index is %ld in avail %d", e, ti.numEntries));
+ if (e < ti.numEntries) {
+ const theme_entry& te = ti.entries[e];
+ if (outTypeSpecFlags != NULL) {
+ *outTypeSpecFlags |= te.typeSpecFlags;
+ }
+ TABLE_THEME(ALOGI("Theme value: type=0x%x, data=0x%08x",
+ te.value.dataType, te.value.data));
+ const uint8_t type = te.value.dataType;
+ if (type == Res_value::TYPE_ATTRIBUTE) {
+ if (cnt > 0) {
+ cnt--;
+ resID = te.value.data;
+ continue;
+ }
+ ALOGW("Too many attribute references, stopped at: 0x%08x\n", resID);
+ return BAD_INDEX;
+ } else if (type != Res_value::TYPE_NULL) {
+ *outValue = te.value;
+ return te.stringBlock;
+ }
+ return BAD_INDEX;
+ }
+ }
+ }
+ }
+ break;
+
+ } while (true);
+
+ return BAD_INDEX;
+}
+
+ssize_t ResTable::Theme::resolveAttributeReference(Res_value* inOutValue,
+ ssize_t blockIndex, uint32_t* outLastRef,
+ uint32_t* inoutTypeSpecFlags, ResTable_config* inoutConfig) const
+{
+ //printf("Resolving type=0x%x\n", inOutValue->dataType);
+ if (inOutValue->dataType == Res_value::TYPE_ATTRIBUTE) {
+ uint32_t newTypeSpecFlags;
+ blockIndex = getAttribute(inOutValue->data, inOutValue, &newTypeSpecFlags);
+ TABLE_THEME(ALOGI("Resolving attr reference: blockIndex=%d, type=0x%x, data=%p\n",
+ (int)blockIndex, (int)inOutValue->dataType, (void*)inOutValue->data));
+ if (inoutTypeSpecFlags != NULL) *inoutTypeSpecFlags |= newTypeSpecFlags;
+ //printf("Retrieved attribute new type=0x%x\n", inOutValue->dataType);
+ if (blockIndex < 0) {
+ return blockIndex;
+ }
+ }
+ return mTable.resolveReference(inOutValue, blockIndex, outLastRef,
+ inoutTypeSpecFlags, inoutConfig);
+}
+
+void ResTable::Theme::dumpToLog() const
+{
+ ALOGI("Theme %p:\n", this);
+ for (size_t i=0; i<Res_MAXPACKAGE; i++) {
+ package_info* pi = mPackages[i];
+ if (pi == NULL) continue;
+
+ ALOGI(" Package #0x%02x:\n", (int)(i+1));
+ for (size_t j=0; j<pi->numTypes; j++) {
+ type_info& ti = pi->types[j];
+ if (ti.numEntries == 0) continue;
+
+ ALOGI(" Type #0x%02x:\n", (int)(j+1));
+ for (size_t k=0; k<ti.numEntries; k++) {
+ theme_entry& te = ti.entries[k];
+ if (te.value.dataType == Res_value::TYPE_NULL) continue;
+ ALOGI(" 0x%08x: t=0x%x, d=0x%08x (block=%d)\n",
+ (int)Res_MAKEID(i, j, k),
+ te.value.dataType, (int)te.value.data, (int)te.stringBlock);
+ }
+ }
+ }
+}
+
+ResTable::ResTable()
+ : mError(NO_INIT)
+{
+ memset(&mParams, 0, sizeof(mParams));
+ memset(mPackageMap, 0, sizeof(mPackageMap));
+ //ALOGI("Creating ResTable %p\n", this);
+}
+
+ResTable::ResTable(const void* data, size_t size, void* cookie, bool copyData)
+ : mError(NO_INIT)
+{
+ memset(&mParams, 0, sizeof(mParams));
+ memset(mPackageMap, 0, sizeof(mPackageMap));
+ add(data, size, cookie, copyData);
+ LOG_FATAL_IF(mError != NO_ERROR, "Error parsing resource table");
+ //ALOGI("Creating ResTable %p\n", this);
+}
+
+ResTable::~ResTable()
+{
+ //ALOGI("Destroying ResTable in %p\n", this);
+ uninit();
+}
+
+inline ssize_t ResTable::getResourcePackageIndex(uint32_t resID) const
+{
+ return ((ssize_t)mPackageMap[Res_GETPACKAGE(resID)+1])-1;
+}
+
+status_t ResTable::add(const void* data, size_t size, void* cookie, bool copyData,
+ const void* idmap)
+{
+ return add(data, size, cookie, NULL, copyData, reinterpret_cast<const Asset*>(idmap));
+}
+
+status_t ResTable::add(Asset* asset, void* cookie, bool copyData, const void* idmap)
+{
+ const void* data = asset->getBuffer(true);
+ if (data == NULL) {
+ ALOGW("Unable to get buffer of resource asset file");
+ return UNKNOWN_ERROR;
+ }
+ size_t size = (size_t)asset->getLength();
+ return add(data, size, cookie, asset, copyData, reinterpret_cast<const Asset*>(idmap));
+}
+
+status_t ResTable::add(ResTable* src)
+{
+ mError = src->mError;
+
+ for (size_t i=0; i<src->mHeaders.size(); i++) {
+ mHeaders.add(src->mHeaders[i]);
+ }
+
+ for (size_t i=0; i<src->mPackageGroups.size(); i++) {
+ PackageGroup* srcPg = src->mPackageGroups[i];
+ PackageGroup* pg = new PackageGroup(this, srcPg->name, srcPg->id);
+ for (size_t j=0; j<srcPg->packages.size(); j++) {
+ pg->packages.add(srcPg->packages[j]);
+ }
+ pg->basePackage = srcPg->basePackage;
+ pg->typeCount = srcPg->typeCount;
+ mPackageGroups.add(pg);
+ }
+
+ memcpy(mPackageMap, src->mPackageMap, sizeof(mPackageMap));
+
+ return mError;
+}
+
+status_t ResTable::add(const void* data, size_t size, void* cookie,
+ Asset* asset, bool copyData, const Asset* idmap)
+{
+ if (!data) return NO_ERROR;
+ Header* header = new Header(this);
+ header->index = mHeaders.size();
+ header->cookie = cookie;
+ if (idmap != NULL) {
+ const size_t idmap_size = idmap->getLength();
+ const void* idmap_data = const_cast<Asset*>(idmap)->getBuffer(true);
+ header->resourceIDMap = (uint32_t*)malloc(idmap_size);
+ if (header->resourceIDMap == NULL) {
+ delete header;
+ return (mError = NO_MEMORY);
+ }
+ memcpy((void*)header->resourceIDMap, idmap_data, idmap_size);
+ header->resourceIDMapSize = idmap_size;
+ }
+ mHeaders.add(header);
+
+ const bool notDeviceEndian = htods(0xf0) != 0xf0;
+
+ LOAD_TABLE_NOISY(
+ ALOGV("Adding resources to ResTable: data=%p, size=0x%x, cookie=%p, asset=%p, copy=%d "
+ "idmap=%p\n", data, size, cookie, asset, copyData, idmap));
+
+ if (copyData || notDeviceEndian) {
+ header->ownedData = malloc(size);
+ if (header->ownedData == NULL) {
+ return (mError=NO_MEMORY);
+ }
+ memcpy(header->ownedData, data, size);
+ data = header->ownedData;
+ }
+
+ header->header = (const ResTable_header*)data;
+ header->size = dtohl(header->header->header.size);
+ //ALOGI("Got size 0x%x, again size 0x%x, raw size 0x%x\n", header->size,
+ // dtohl(header->header->header.size), header->header->header.size);
+ LOAD_TABLE_NOISY(ALOGV("Loading ResTable @%p:\n", header->header));
+ LOAD_TABLE_NOISY(printHexData(2, header->header, header->size < 256 ? header->size : 256,
+ 16, 16, 0, false, printToLogFunc));
+ if (dtohs(header->header->header.headerSize) > header->size
+ || header->size > size) {
+ ALOGW("Bad resource table: header size 0x%x or total size 0x%x is larger than data size 0x%x\n",
+ (int)dtohs(header->header->header.headerSize),
+ (int)header->size, (int)size);
+ return (mError=BAD_TYPE);
+ }
+ if (((dtohs(header->header->header.headerSize)|header->size)&0x3) != 0) {
+ ALOGW("Bad resource table: header size 0x%x or total size 0x%x is not on an integer boundary\n",
+ (int)dtohs(header->header->header.headerSize),
+ (int)header->size);
+ return (mError=BAD_TYPE);
+ }
+ header->dataEnd = ((const uint8_t*)header->header) + header->size;
+
+ // Iterate through all chunks.
+ size_t curPackage = 0;
+
+ const ResChunk_header* chunk =
+ (const ResChunk_header*)(((const uint8_t*)header->header)
+ + dtohs(header->header->header.headerSize));
+ while (((const uint8_t*)chunk) <= (header->dataEnd-sizeof(ResChunk_header)) &&
+ ((const uint8_t*)chunk) <= (header->dataEnd-dtohl(chunk->size))) {
+ status_t err = validate_chunk(chunk, sizeof(ResChunk_header), header->dataEnd, "ResTable");
+ if (err != NO_ERROR) {
+ return (mError=err);
+ }
+ TABLE_NOISY(ALOGV("Chunk: type=0x%x, headerSize=0x%x, size=0x%x, pos=%p\n",
+ dtohs(chunk->type), dtohs(chunk->headerSize), dtohl(chunk->size),
+ (void*)(((const uint8_t*)chunk) - ((const uint8_t*)header->header))));
+ const size_t csize = dtohl(chunk->size);
+ const uint16_t ctype = dtohs(chunk->type);
+ if (ctype == RES_STRING_POOL_TYPE) {
+ if (header->values.getError() != NO_ERROR) {
+ // Only use the first string chunk; ignore any others that
+ // may appear.
+ status_t err = header->values.setTo(chunk, csize);
+ if (err != NO_ERROR) {
+ return (mError=err);
+ }
+ } else {
+ ALOGW("Multiple string chunks found in resource table.");
+ }
+ } else if (ctype == RES_TABLE_PACKAGE_TYPE) {
+ if (curPackage >= dtohl(header->header->packageCount)) {
+ ALOGW("More package chunks were found than the %d declared in the header.",
+ dtohl(header->header->packageCount));
+ return (mError=BAD_TYPE);
+ }
+ uint32_t idmap_id = 0;
+ if (idmap != NULL) {
+ uint32_t tmp;
+ if (getIdmapPackageId(header->resourceIDMap,
+ header->resourceIDMapSize,
+ &tmp) == NO_ERROR) {
+ idmap_id = tmp;
+ }
+ }
+ if (parsePackage((ResTable_package*)chunk, header, idmap_id) != NO_ERROR) {
+ return mError;
+ }
+ curPackage++;
+ } else {
+ ALOGW("Unknown chunk type %p in table at %p.\n",
+ (void*)(int)(ctype),
+ (void*)(((const uint8_t*)chunk) - ((const uint8_t*)header->header)));
+ }
+ chunk = (const ResChunk_header*)
+ (((const uint8_t*)chunk) + csize);
+ }
+
+ if (curPackage < dtohl(header->header->packageCount)) {
+ ALOGW("Fewer package chunks (%d) were found than the %d declared in the header.",
+ (int)curPackage, dtohl(header->header->packageCount));
+ return (mError=BAD_TYPE);
+ }
+ mError = header->values.getError();
+ if (mError != NO_ERROR) {
+ ALOGW("No string values found in resource table!");
+ }
+
+ TABLE_NOISY(ALOGV("Returning from add with mError=%d\n", mError));
+ return mError;
+}
+
+status_t ResTable::getError() const
+{
+ return mError;
+}
+
+void ResTable::uninit()
+{
+ mError = NO_INIT;
+ size_t N = mPackageGroups.size();
+ for (size_t i=0; i<N; i++) {
+ PackageGroup* g = mPackageGroups[i];
+ delete g;
+ }
+ N = mHeaders.size();
+ for (size_t i=0; i<N; i++) {
+ Header* header = mHeaders[i];
+ if (header->owner == this) {
+ if (header->ownedData) {
+ free(header->ownedData);
+ }
+ delete header;
+ }
+ }
+
+ mPackageGroups.clear();
+ mHeaders.clear();
+}
+
+bool ResTable::getResourceName(uint32_t resID, bool allowUtf8, resource_name* outName) const
+{
+ if (mError != NO_ERROR) {
+ return false;
+ }
+
+ const ssize_t p = getResourcePackageIndex(resID);
+ const int t = Res_GETTYPE(resID);
+ const int e = Res_GETENTRY(resID);
+
+ if (p < 0) {
+ if (Res_GETPACKAGE(resID)+1 == 0) {
+ ALOGW("No package identifier when getting name for resource number 0x%08x", resID);
+ } else {
+ ALOGW("No known package when getting name for resource number 0x%08x", resID);
+ }
+ return false;
+ }
+ if (t < 0) {
+ ALOGW("No type identifier when getting name for resource number 0x%08x", resID);
+ return false;
+ }
+
+ const PackageGroup* const grp = mPackageGroups[p];
+ if (grp == NULL) {
+ ALOGW("Bad identifier when getting name for resource number 0x%08x", resID);
+ return false;
+ }
+ if (grp->packages.size() > 0) {
+ const Package* const package = grp->packages[0];
+
+ const ResTable_type* type;
+ const ResTable_entry* entry;
+ ssize_t offset = getEntry(package, t, e, NULL, &type, &entry, NULL);
+ if (offset <= 0) {
+ return false;
+ }
+
+ outName->package = grp->name.string();
+ outName->packageLen = grp->name.size();
+ if (allowUtf8) {
+ outName->type8 = grp->basePackage->typeStrings.string8At(t, &outName->typeLen);
+ outName->name8 = grp->basePackage->keyStrings.string8At(
+ dtohl(entry->key.index), &outName->nameLen);
+ } else {
+ outName->type8 = NULL;
+ outName->name8 = NULL;
+ }
+ if (outName->type8 == NULL) {
+ outName->type = grp->basePackage->typeStrings.stringAt(t, &outName->typeLen);
+ // If we have a bad index for some reason, we should abort.
+ if (outName->type == NULL) {
+ return false;
+ }
+ }
+ if (outName->name8 == NULL) {
+ outName->name = grp->basePackage->keyStrings.stringAt(
+ dtohl(entry->key.index), &outName->nameLen);
+ // If we have a bad index for some reason, we should abort.
+ if (outName->name == NULL) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+ssize_t ResTable::getResource(uint32_t resID, Res_value* outValue, bool mayBeBag, uint16_t density,
+ uint32_t* outSpecFlags, ResTable_config* outConfig) const
+{
+ if (mError != NO_ERROR) {
+ return mError;
+ }
+
+ const ssize_t p = getResourcePackageIndex(resID);
+ const int t = Res_GETTYPE(resID);
+ const int e = Res_GETENTRY(resID);
+
+ if (p < 0) {
+ if (Res_GETPACKAGE(resID)+1 == 0) {
+ ALOGW("No package identifier when getting value for resource number 0x%08x", resID);
+ } else {
+ ALOGW("No known package when getting value for resource number 0x%08x", resID);
+ }
+ return BAD_INDEX;
+ }
+ if (t < 0) {
+ ALOGW("No type identifier when getting value for resource number 0x%08x", resID);
+ return BAD_INDEX;
+ }
+
+ const Res_value* bestValue = NULL;
+ const Package* bestPackage = NULL;
+ ResTable_config bestItem;
+ memset(&bestItem, 0, sizeof(bestItem)); // make the compiler shut up
+
+ if (outSpecFlags != NULL) *outSpecFlags = 0;
+
+ // Look through all resource packages, starting with the most
+ // recently added.
+ const PackageGroup* const grp = mPackageGroups[p];
+ if (grp == NULL) {
+ ALOGW("Bad identifier when getting value for resource number 0x%08x", resID);
+ return BAD_INDEX;
+ }
+
+ // Allow overriding density
+ const ResTable_config* desiredConfig = &mParams;
+ ResTable_config* overrideConfig = NULL;
+ if (density > 0) {
+ overrideConfig = (ResTable_config*) malloc(sizeof(ResTable_config));
+ if (overrideConfig == NULL) {
+ ALOGE("Couldn't malloc ResTable_config for overrides: %s", strerror(errno));
+ return BAD_INDEX;
+ }
+ memcpy(overrideConfig, &mParams, sizeof(ResTable_config));
+ overrideConfig->density = density;
+ desiredConfig = overrideConfig;
+ }
+
+ ssize_t rc = BAD_VALUE;
+ size_t ip = grp->packages.size();
+ while (ip > 0) {
+ ip--;
+ int T = t;
+ int E = e;
+
+ const Package* const package = grp->packages[ip];
+ if (package->header->resourceIDMap) {
+ uint32_t overlayResID = 0x0;
+ status_t retval = idmapLookup(package->header->resourceIDMap,
+ package->header->resourceIDMapSize,
+ resID, &overlayResID);
+ if (retval == NO_ERROR && overlayResID != 0x0) {
+ // for this loop iteration, this is the type and entry we really want
+ ALOGV("resource map 0x%08x -> 0x%08x\n", resID, overlayResID);
+ T = Res_GETTYPE(overlayResID);
+ E = Res_GETENTRY(overlayResID);
+ } else {
+ // resource not present in overlay package, continue with the next package
+ continue;
+ }
+ }
+
+ const ResTable_type* type;
+ const ResTable_entry* entry;
+ const Type* typeClass;
+ ssize_t offset = getEntry(package, T, E, desiredConfig, &type, &entry, &typeClass);
+ if (offset <= 0) {
+ // No {entry, appropriate config} pair found in package. If this
+ // package is an overlay package (ip != 0), this simply means the
+ // overlay package did not specify a default.
+ // Non-overlay packages are still required to provide a default.
+ if (offset < 0 && ip == 0) {
+ ALOGW("Failure getting entry for 0x%08x (t=%d e=%d) in package %zd (error %d)\n",
+ resID, T, E, ip, (int)offset);
+ rc = offset;
+ goto out;
+ }
+ continue;
+ }
+
+ if ((dtohs(entry->flags)&entry->FLAG_COMPLEX) != 0) {
+ if (!mayBeBag) {
+ ALOGW("Requesting resource %p failed because it is complex\n",
+ (void*)resID);
+ }
+ continue;
+ }
+
+ TABLE_NOISY(aout << "Resource type data: "
+ << HexDump(type, dtohl(type->header.size)) << endl);
+
+ if ((size_t)offset > (dtohl(type->header.size)-sizeof(Res_value))) {
+ ALOGW("ResTable_item at %d is beyond type chunk data %d",
+ (int)offset, dtohl(type->header.size));
+ rc = BAD_TYPE;
+ goto out;
+ }
+
+ const Res_value* item =
+ (const Res_value*)(((const uint8_t*)type) + offset);
+ ResTable_config thisConfig;
+ thisConfig.copyFromDtoH(type->config);
+
+ if (outSpecFlags != NULL) {
+ if (typeClass->typeSpecFlags != NULL) {
+ *outSpecFlags |= dtohl(typeClass->typeSpecFlags[E]);
+ } else {
+ *outSpecFlags = -1;
+ }
+ }
+
+ if (bestPackage != NULL &&
+ (bestItem.isMoreSpecificThan(thisConfig) || bestItem.diff(thisConfig) == 0)) {
+ // Discard thisConfig not only if bestItem is more specific, but also if the two configs
+ // are identical (diff == 0), or overlay packages will not take effect.
+ continue;
+ }
+
+ bestItem = thisConfig;
+ bestValue = item;
+ bestPackage = package;
+ }
+
+ TABLE_NOISY(printf("Found result: package %p\n", bestPackage));
+
+ if (bestValue) {
+ outValue->size = dtohs(bestValue->size);
+ outValue->res0 = bestValue->res0;
+ outValue->dataType = bestValue->dataType;
+ outValue->data = dtohl(bestValue->data);
+ if (outConfig != NULL) {
+ *outConfig = bestItem;
+ }
+ TABLE_NOISY(size_t len;
+ printf("Found value: pkg=%d, type=%d, str=%s, int=%d\n",
+ bestPackage->header->index,
+ outValue->dataType,
+ outValue->dataType == bestValue->TYPE_STRING
+ ? String8(bestPackage->header->values.stringAt(
+ outValue->data, &len)).string()
+ : "",
+ outValue->data));
+ rc = bestPackage->header->index;
+ goto out;
+ }
+
+out:
+ if (overrideConfig != NULL) {
+ free(overrideConfig);
+ }
+
+ return rc;
+}
+
+ssize_t ResTable::resolveReference(Res_value* value, ssize_t blockIndex,
+ uint32_t* outLastRef, uint32_t* inoutTypeSpecFlags,
+ ResTable_config* outConfig) const
+{
+ int count=0;
+ while (blockIndex >= 0 && value->dataType == value->TYPE_REFERENCE
+ && value->data != 0 && count < 20) {
+ if (outLastRef) *outLastRef = value->data;
+ uint32_t lastRef = value->data;
+ uint32_t newFlags = 0;
+ const ssize_t newIndex = getResource(value->data, value, true, 0, &newFlags,
+ outConfig);
+ if (newIndex == BAD_INDEX) {
+ return BAD_INDEX;
+ }
+ TABLE_THEME(ALOGI("Resolving reference %p: newIndex=%d, type=0x%x, data=%p\n",
+ (void*)lastRef, (int)newIndex, (int)value->dataType, (void*)value->data));
+ //printf("Getting reference 0x%08x: newIndex=%d\n", value->data, newIndex);
+ if (inoutTypeSpecFlags != NULL) *inoutTypeSpecFlags |= newFlags;
+ if (newIndex < 0) {
+ // This can fail if the resource being referenced is a style...
+ // in this case, just return the reference, and expect the
+ // caller to deal with.
+ return blockIndex;
+ }
+ blockIndex = newIndex;
+ count++;
+ }
+ return blockIndex;
+}
+
+const char16_t* ResTable::valueToString(
+ const Res_value* value, size_t stringBlock,
+ char16_t tmpBuffer[TMP_BUFFER_SIZE], size_t* outLen)
+{
+ if (!value) {
+ return NULL;
+ }
+ if (value->dataType == value->TYPE_STRING) {
+ return getTableStringBlock(stringBlock)->stringAt(value->data, outLen);
+ }
+ // XXX do int to string conversions.
+ return NULL;
+}
+
+ssize_t ResTable::lockBag(uint32_t resID, const bag_entry** outBag) const
+{
+ mLock.lock();
+ ssize_t err = getBagLocked(resID, outBag);
+ if (err < NO_ERROR) {
+ //printf("*** get failed! unlocking\n");
+ mLock.unlock();
+ }
+ return err;
+}
+
+void ResTable::unlockBag(const bag_entry* bag) const
+{
+ //printf("<<< unlockBag %p\n", this);
+ mLock.unlock();
+}
+
+void ResTable::lock() const
+{
+ mLock.lock();
+}
+
+void ResTable::unlock() const
+{
+ mLock.unlock();
+}
+
+ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag,
+ uint32_t* outTypeSpecFlags) const
+{
+ if (mError != NO_ERROR) {
+ return mError;
+ }
+
+ const ssize_t p = getResourcePackageIndex(resID);
+ const int t = Res_GETTYPE(resID);
+ const int e = Res_GETENTRY(resID);
+
+ if (p < 0) {
+ ALOGW("Invalid package identifier when getting bag for resource number 0x%08x", resID);
+ return BAD_INDEX;
+ }
+ if (t < 0) {
+ ALOGW("No type identifier when getting bag for resource number 0x%08x", resID);
+ return BAD_INDEX;
+ }
+
+ //printf("Get bag: id=0x%08x, p=%d, t=%d\n", resID, p, t);
+ PackageGroup* const grp = mPackageGroups[p];
+ if (grp == NULL) {
+ ALOGW("Bad identifier when getting bag for resource number 0x%08x", resID);
+ return false;
+ }
+
+ if (t >= (int)grp->typeCount) {
+ ALOGW("Type identifier 0x%x is larger than type count 0x%x",
+ t+1, (int)grp->typeCount);
+ return BAD_INDEX;
+ }
+
+ const Package* const basePackage = grp->packages[0];
+
+ const Type* const typeConfigs = basePackage->getType(t);
+
+ const size_t NENTRY = typeConfigs->entryCount;
+ if (e >= (int)NENTRY) {
+ ALOGW("Entry identifier 0x%x is larger than entry count 0x%x",
+ e, (int)typeConfigs->entryCount);
+ return BAD_INDEX;
+ }
+
+ // First see if we've already computed this bag...
+ if (grp->bags) {
+ bag_set** typeSet = grp->bags[t];
+ if (typeSet) {
+ bag_set* set = typeSet[e];
+ if (set) {
+ if (set != (bag_set*)0xFFFFFFFF) {
+ if (outTypeSpecFlags != NULL) {
+ *outTypeSpecFlags = set->typeSpecFlags;
+ }
+ *outBag = (bag_entry*)(set+1);
+ //ALOGI("Found existing bag for: %p\n", (void*)resID);
+ return set->numAttrs;
+ }
+ ALOGW("Attempt to retrieve bag 0x%08x which is invalid or in a cycle.",
+ resID);
+ return BAD_INDEX;
+ }
+ }
+ }
+
+ // Bag not found, we need to compute it!
+ if (!grp->bags) {
+ grp->bags = (bag_set***)calloc(grp->typeCount, sizeof(bag_set*));
+ if (!grp->bags) return NO_MEMORY;
+ }
+
+ bag_set** typeSet = grp->bags[t];
+ if (!typeSet) {
+ typeSet = (bag_set**)calloc(NENTRY, sizeof(bag_set*));
+ if (!typeSet) return NO_MEMORY;
+ grp->bags[t] = typeSet;
+ }
+
+ // Mark that we are currently working on this one.
+ typeSet[e] = (bag_set*)0xFFFFFFFF;
+
+ // This is what we are building.
+ bag_set* set = NULL;
+
+ TABLE_NOISY(ALOGI("Building bag: %p\n", (void*)resID));
+
+ ResTable_config bestConfig;
+ memset(&bestConfig, 0, sizeof(bestConfig));
+
+ // Now collect all bag attributes from all packages.
+ size_t ip = grp->packages.size();
+ while (ip > 0) {
+ ip--;
+ int T = t;
+ int E = e;
+
+ const Package* const package = grp->packages[ip];
+ if (package->header->resourceIDMap) {
+ uint32_t overlayResID = 0x0;
+ status_t retval = idmapLookup(package->header->resourceIDMap,
+ package->header->resourceIDMapSize,
+ resID, &overlayResID);
+ if (retval == NO_ERROR && overlayResID != 0x0) {
+ // for this loop iteration, this is the type and entry we really want
+ ALOGV("resource map 0x%08x -> 0x%08x\n", resID, overlayResID);
+ T = Res_GETTYPE(overlayResID);
+ E = Res_GETENTRY(overlayResID);
+ } else {
+ // resource not present in overlay package, continue with the next package
+ continue;
+ }
+ }
+
+ const ResTable_type* type;
+ const ResTable_entry* entry;
+ const Type* typeClass;
+ ALOGV("Getting entry pkg=%p, t=%d, e=%d\n", package, T, E);
+ ssize_t offset = getEntry(package, T, E, &mParams, &type, &entry, &typeClass);
+ ALOGV("Resulting offset=%d\n", offset);
+ if (offset <= 0) {
+ // No {entry, appropriate config} pair found in package. If this
+ // package is an overlay package (ip != 0), this simply means the
+ // overlay package did not specify a default.
+ // Non-overlay packages are still required to provide a default.
+ if (offset < 0 && ip == 0) {
+ if (set) free(set);
+ return offset;
+ }
+ continue;
+ }
+
+ if ((dtohs(entry->flags)&entry->FLAG_COMPLEX) == 0) {
+ ALOGW("Skipping entry %p in package table %d because it is not complex!\n",
+ (void*)resID, (int)ip);
+ continue;
+ }
+
+ if (set != NULL && !type->config.isBetterThan(bestConfig, NULL)) {
+ continue;
+ }
+ bestConfig = type->config;
+ if (set) {
+ free(set);
+ set = NULL;
+ }
+
+ const uint16_t entrySize = dtohs(entry->size);
+ const uint32_t parent = entrySize >= sizeof(ResTable_map_entry)
+ ? dtohl(((const ResTable_map_entry*)entry)->parent.ident) : 0;
+ const uint32_t count = entrySize >= sizeof(ResTable_map_entry)
+ ? dtohl(((const ResTable_map_entry*)entry)->count) : 0;
+
+ size_t N = count;
+
+ TABLE_NOISY(ALOGI("Found map: size=%p parent=%p count=%d\n",
+ entrySize, parent, count));
+
+ // If this map inherits from another, we need to start
+ // with its parent's values. Otherwise start out empty.
+ TABLE_NOISY(printf("Creating new bag, entrySize=0x%08x, parent=0x%08x\n",
+ entrySize, parent));
+ if (parent) {
+ const bag_entry* parentBag;
+ uint32_t parentTypeSpecFlags = 0;
+ const ssize_t NP = getBagLocked(parent, &parentBag, &parentTypeSpecFlags);
+ const size_t NT = ((NP >= 0) ? NP : 0) + N;
+ set = (bag_set*)malloc(sizeof(bag_set)+sizeof(bag_entry)*NT);
+ if (set == NULL) {
+ return NO_MEMORY;
+ }
+ if (NP > 0) {
+ memcpy(set+1, parentBag, NP*sizeof(bag_entry));
+ set->numAttrs = NP;
+ TABLE_NOISY(ALOGI("Initialized new bag with %d inherited attributes.\n", NP));
+ } else {
+ TABLE_NOISY(ALOGI("Initialized new bag with no inherited attributes.\n"));
+ set->numAttrs = 0;
+ }
+ set->availAttrs = NT;
+ set->typeSpecFlags = parentTypeSpecFlags;
+ } else {
+ set = (bag_set*)malloc(sizeof(bag_set)+sizeof(bag_entry)*N);
+ if (set == NULL) {
+ return NO_MEMORY;
+ }
+ set->numAttrs = 0;
+ set->availAttrs = N;
+ set->typeSpecFlags = 0;
+ }
+
+ if (typeClass->typeSpecFlags != NULL) {
+ set->typeSpecFlags |= dtohl(typeClass->typeSpecFlags[E]);
+ } else {
+ set->typeSpecFlags = -1;
+ }
+
+ // Now merge in the new attributes...
+ ssize_t curOff = offset;
+ const ResTable_map* map;
+ bag_entry* entries = (bag_entry*)(set+1);
+ size_t curEntry = 0;
+ uint32_t pos = 0;
+ TABLE_NOISY(ALOGI("Starting with set %p, entries=%p, avail=%d\n",
+ set, entries, set->availAttrs));
+ while (pos < count) {
+ TABLE_NOISY(printf("Now at %p\n", (void*)curOff));
+
+ if ((size_t)curOff > (dtohl(type->header.size)-sizeof(ResTable_map))) {
+ ALOGW("ResTable_map at %d is beyond type chunk data %d",
+ (int)curOff, dtohl(type->header.size));
+ return BAD_TYPE;
+ }
+ map = (const ResTable_map*)(((const uint8_t*)type) + curOff);
+ N++;
+
+ const uint32_t newName = htodl(map->name.ident);
+ bool isInside;
+ uint32_t oldName = 0;
+ while ((isInside=(curEntry < set->numAttrs))
+ && (oldName=entries[curEntry].map.name.ident) < newName) {
+ TABLE_NOISY(printf("#%d: Keeping existing attribute: 0x%08x\n",
+ curEntry, entries[curEntry].map.name.ident));
+ curEntry++;
+ }
+
+ if ((!isInside) || oldName != newName) {
+ // This is a new attribute... figure out what to do with it.
+ if (set->numAttrs >= set->availAttrs) {
+ // Need to alloc more memory...
+ const size_t newAvail = set->availAttrs+N;
+ set = (bag_set*)realloc(set,
+ sizeof(bag_set)
+ + sizeof(bag_entry)*newAvail);
+ if (set == NULL) {
+ return NO_MEMORY;
+ }
+ set->availAttrs = newAvail;
+ entries = (bag_entry*)(set+1);
+ TABLE_NOISY(printf("Reallocated set %p, entries=%p, avail=%d\n",
+ set, entries, set->availAttrs));
+ }
+ if (isInside) {
+ // Going in the middle, need to make space.
+ memmove(entries+curEntry+1, entries+curEntry,
+ sizeof(bag_entry)*(set->numAttrs-curEntry));
+ set->numAttrs++;
+ }
+ TABLE_NOISY(printf("#%d: Inserting new attribute: 0x%08x\n",
+ curEntry, newName));
+ } else {
+ TABLE_NOISY(printf("#%d: Replacing existing attribute: 0x%08x\n",
+ curEntry, oldName));
+ }
+
+ bag_entry* cur = entries+curEntry;
+
+ cur->stringBlock = package->header->index;
+ cur->map.name.ident = newName;
+ cur->map.value.copyFrom_dtoh(map->value);
+ TABLE_NOISY(printf("Setting entry #%d %p: block=%d, name=0x%08x, type=%d, data=0x%08x\n",
+ curEntry, cur, cur->stringBlock, cur->map.name.ident,
+ cur->map.value.dataType, cur->map.value.data));
+
+ // On to the next!
+ curEntry++;
+ pos++;
+ const size_t size = dtohs(map->value.size);
+ curOff += size + sizeof(*map)-sizeof(map->value);
+ };
+ if (curEntry > set->numAttrs) {
+ set->numAttrs = curEntry;
+ }
+ }
+
+ // And this is it...
+ typeSet[e] = set;
+ if (set) {
+ if (outTypeSpecFlags != NULL) {
+ *outTypeSpecFlags = set->typeSpecFlags;
+ }
+ *outBag = (bag_entry*)(set+1);
+ TABLE_NOISY(ALOGI("Returning %d attrs\n", set->numAttrs));
+ return set->numAttrs;
+ }
+ return BAD_INDEX;
+}
+
+void ResTable::setParameters(const ResTable_config* params)
+{
+ mLock.lock();
+ TABLE_GETENTRY(ALOGI("Setting parameters: %s\n", params->toString().string()));
+ mParams = *params;
+ for (size_t i=0; i<mPackageGroups.size(); i++) {
+ TABLE_NOISY(ALOGI("CLEARING BAGS FOR GROUP %d!", i));
+ mPackageGroups[i]->clearBagCache();
+ }
+ mLock.unlock();
+}
+
+void ResTable::getParameters(ResTable_config* params) const
+{
+ mLock.lock();
+ *params = mParams;
+ mLock.unlock();
+}
+
+struct id_name_map {
+ uint32_t id;
+ size_t len;
+ char16_t name[6];
+};
+
+const static id_name_map ID_NAMES[] = {
+ { ResTable_map::ATTR_TYPE, 5, { '^', 't', 'y', 'p', 'e' } },
+ { ResTable_map::ATTR_L10N, 5, { '^', 'l', '1', '0', 'n' } },
+ { ResTable_map::ATTR_MIN, 4, { '^', 'm', 'i', 'n' } },
+ { ResTable_map::ATTR_MAX, 4, { '^', 'm', 'a', 'x' } },
+ { ResTable_map::ATTR_OTHER, 6, { '^', 'o', 't', 'h', 'e', 'r' } },
+ { ResTable_map::ATTR_ZERO, 5, { '^', 'z', 'e', 'r', 'o' } },
+ { ResTable_map::ATTR_ONE, 4, { '^', 'o', 'n', 'e' } },
+ { ResTable_map::ATTR_TWO, 4, { '^', 't', 'w', 'o' } },
+ { ResTable_map::ATTR_FEW, 4, { '^', 'f', 'e', 'w' } },
+ { ResTable_map::ATTR_MANY, 5, { '^', 'm', 'a', 'n', 'y' } },
+};
+
+uint32_t ResTable::identifierForName(const char16_t* name, size_t nameLen,
+ const char16_t* type, size_t typeLen,
+ const char16_t* package,
+ size_t packageLen,
+ uint32_t* outTypeSpecFlags) const
+{
+ TABLE_SUPER_NOISY(printf("Identifier for name: error=%d\n", mError));
+
+ // Check for internal resource identifier as the very first thing, so
+ // that we will always find them even when there are no resources.
+ if (name[0] == '^') {
+ const int N = (sizeof(ID_NAMES)/sizeof(ID_NAMES[0]));
+ size_t len;
+ for (int i=0; i<N; i++) {
+ const id_name_map* m = ID_NAMES + i;
+ len = m->len;
+ if (len != nameLen) {
+ continue;
+ }
+ for (size_t j=1; j<len; j++) {
+ if (m->name[j] != name[j]) {
+ goto nope;
+ }
+ }
+ if (outTypeSpecFlags) {
+ *outTypeSpecFlags = ResTable_typeSpec::SPEC_PUBLIC;
+ }
+ return m->id;
+nope:
+ ;
+ }
+ if (nameLen > 7) {
+ if (name[1] == 'i' && name[2] == 'n'
+ && name[3] == 'd' && name[4] == 'e' && name[5] == 'x'
+ && name[6] == '_') {
+ int index = atoi(String8(name + 7, nameLen - 7).string());
+ if (Res_CHECKID(index)) {
+ ALOGW("Array resource index: %d is too large.",
+ index);
+ return 0;
+ }
+ if (outTypeSpecFlags) {
+ *outTypeSpecFlags = ResTable_typeSpec::SPEC_PUBLIC;
+ }
+ return Res_MAKEARRAY(index);
+ }
+ }
+ return 0;
+ }
+
+ if (mError != NO_ERROR) {
+ return 0;
+ }
+
+ bool fakePublic = false;
+
+ // Figure out the package and type we are looking in...
+
+ const char16_t* packageEnd = NULL;
+ const char16_t* typeEnd = NULL;
+ const char16_t* const nameEnd = name+nameLen;
+ const char16_t* p = name;
+ while (p < nameEnd) {
+ if (*p == ':') packageEnd = p;
+ else if (*p == '/') typeEnd = p;
+ p++;
+ }
+ if (*name == '@') {
+ name++;
+ if (*name == '*') {
+ fakePublic = true;
+ name++;
+ }
+ }
+ if (name >= nameEnd) {
+ return 0;
+ }
+
+ if (packageEnd) {
+ package = name;
+ packageLen = packageEnd-name;
+ name = packageEnd+1;
+ } else if (!package) {
+ return 0;
+ }
+
+ if (typeEnd) {
+ type = name;
+ typeLen = typeEnd-name;
+ name = typeEnd+1;
+ } else if (!type) {
+ return 0;
+ }
+
+ if (name >= nameEnd) {
+ return 0;
+ }
+ nameLen = nameEnd-name;
+
+ TABLE_NOISY(printf("Looking for identifier: type=%s, name=%s, package=%s\n",
+ String8(type, typeLen).string(),
+ String8(name, nameLen).string(),
+ String8(package, packageLen).string()));
+
+ const size_t NG = mPackageGroups.size();
+ for (size_t ig=0; ig<NG; ig++) {
+ const PackageGroup* group = mPackageGroups[ig];
+
+ if (strzcmp16(package, packageLen,
+ group->name.string(), group->name.size())) {
+ TABLE_NOISY(printf("Skipping package group: %s\n", String8(group->name).string()));
+ continue;
+ }
+
+ const ssize_t ti = group->basePackage->typeStrings.indexOfString(type, typeLen);
+ if (ti < 0) {
+ TABLE_NOISY(printf("Type not found in package %s\n", String8(group->name).string()));
+ continue;
+ }
+
+ const ssize_t ei = group->basePackage->keyStrings.indexOfString(name, nameLen);
+ if (ei < 0) {
+ TABLE_NOISY(printf("Name not found in package %s\n", String8(group->name).string()));
+ continue;
+ }
+
+ TABLE_NOISY(printf("Search indices: type=%d, name=%d\n", ti, ei));
+
+ const Type* const typeConfigs = group->packages[0]->getType(ti);
+ if (typeConfigs == NULL || typeConfigs->configs.size() <= 0) {
+ TABLE_NOISY(printf("Expected type structure not found in package %s for idnex %d\n",
+ String8(group->name).string(), ti));
+ }
+
+ size_t NTC = typeConfigs->configs.size();
+ for (size_t tci=0; tci<NTC; tci++) {
+ const ResTable_type* const ty = typeConfigs->configs[tci];
+ const uint32_t typeOffset = dtohl(ty->entriesStart);
+
+ const uint8_t* const end = ((const uint8_t*)ty) + dtohl(ty->header.size);
+ const uint32_t* const eindex = (const uint32_t*)
+ (((const uint8_t*)ty) + dtohs(ty->header.headerSize));
+
+ const size_t NE = dtohl(ty->entryCount);
+ for (size_t i=0; i<NE; i++) {
+ uint32_t offset = dtohl(eindex[i]);
+ if (offset == ResTable_type::NO_ENTRY) {
+ continue;
+ }
+
+ offset += typeOffset;
+
+ if (offset > (dtohl(ty->header.size)-sizeof(ResTable_entry))) {
+ ALOGW("ResTable_entry at %d is beyond type chunk data %d",
+ offset, dtohl(ty->header.size));
+ return 0;
+ }
+ if ((offset&0x3) != 0) {
+ ALOGW("ResTable_entry at %d (pkg=%d type=%d ent=%d) is not on an integer boundary when looking for %s:%s/%s",
+ (int)offset, (int)group->id, (int)ti+1, (int)i,
+ String8(package, packageLen).string(),
+ String8(type, typeLen).string(),
+ String8(name, nameLen).string());
+ return 0;
+ }
+
+ const ResTable_entry* const entry = (const ResTable_entry*)
+ (((const uint8_t*)ty) + offset);
+ if (dtohs(entry->size) < sizeof(*entry)) {
+ ALOGW("ResTable_entry size %d is too small", dtohs(entry->size));
+ return BAD_TYPE;
+ }
+
+ TABLE_SUPER_NOISY(printf("Looking at entry #%d: want str %d, have %d\n",
+ i, ei, dtohl(entry->key.index)));
+ if (dtohl(entry->key.index) == (size_t)ei) {
+ if (outTypeSpecFlags) {
+ *outTypeSpecFlags = typeConfigs->typeSpecFlags[i];
+ if (fakePublic) {
+ *outTypeSpecFlags |= ResTable_typeSpec::SPEC_PUBLIC;
+ }
+ }
+ return Res_MAKEID(group->id-1, ti, i);
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+bool ResTable::expandResourceRef(const uint16_t* refStr, size_t refLen,
+ String16* outPackage,
+ String16* outType,
+ String16* outName,
+ const String16* defType,
+ const String16* defPackage,
+ const char** outErrorMsg,
+ bool* outPublicOnly)
+{
+ const char16_t* packageEnd = NULL;
+ const char16_t* typeEnd = NULL;
+ const char16_t* p = refStr;
+ const char16_t* const end = p + refLen;
+ while (p < end) {
+ if (*p == ':') packageEnd = p;
+ else if (*p == '/') {
+ typeEnd = p;
+ break;
+ }
+ p++;
+ }
+ p = refStr;
+ if (*p == '@') p++;
+
+ if (outPublicOnly != NULL) {
+ *outPublicOnly = true;
+ }
+ if (*p == '*') {
+ p++;
+ if (outPublicOnly != NULL) {
+ *outPublicOnly = false;
+ }
+ }
+
+ if (packageEnd) {
+ *outPackage = String16(p, packageEnd-p);
+ p = packageEnd+1;
+ } else {
+ if (!defPackage) {
+ if (outErrorMsg) {
+ *outErrorMsg = "No resource package specified";
+ }
+ return false;
+ }
+ *outPackage = *defPackage;
+ }
+ if (typeEnd) {
+ *outType = String16(p, typeEnd-p);
+ p = typeEnd+1;
+ } else {
+ if (!defType) {
+ if (outErrorMsg) {
+ *outErrorMsg = "No resource type specified";
+ }
+ return false;
+ }
+ *outType = *defType;
+ }
+ *outName = String16(p, end-p);
+ if(**outPackage == 0) {
+ if(outErrorMsg) {
+ *outErrorMsg = "Resource package cannot be an empty string";
+ }
+ return false;
+ }
+ if(**outType == 0) {
+ if(outErrorMsg) {
+ *outErrorMsg = "Resource type cannot be an empty string";
+ }
+ return false;
+ }
+ if(**outName == 0) {
+ if(outErrorMsg) {
+ *outErrorMsg = "Resource id cannot be an empty string";
+ }
+ return false;
+ }
+ return true;
+}
+
+static uint32_t get_hex(char c, bool* outError)
+{
+ if (c >= '0' && c <= '9') {
+ return c - '0';
+ } else if (c >= 'a' && c <= 'f') {
+ return c - 'a' + 0xa;
+ } else if (c >= 'A' && c <= 'F') {
+ return c - 'A' + 0xa;
+ }
+ *outError = true;
+ return 0;
+}
+
+struct unit_entry
+{
+ const char* name;
+ size_t len;
+ uint8_t type;
+ uint32_t unit;
+ float scale;
+};
+
+static const unit_entry unitNames[] = {
+ { "px", strlen("px"), Res_value::TYPE_DIMENSION, Res_value::COMPLEX_UNIT_PX, 1.0f },
+ { "dip", strlen("dip"), Res_value::TYPE_DIMENSION, Res_value::COMPLEX_UNIT_DIP, 1.0f },
+ { "dp", strlen("dp"), Res_value::TYPE_DIMENSION, Res_value::COMPLEX_UNIT_DIP, 1.0f },
+ { "sp", strlen("sp"), Res_value::TYPE_DIMENSION, Res_value::COMPLEX_UNIT_SP, 1.0f },
+ { "pt", strlen("pt"), Res_value::TYPE_DIMENSION, Res_value::COMPLEX_UNIT_PT, 1.0f },
+ { "in", strlen("in"), Res_value::TYPE_DIMENSION, Res_value::COMPLEX_UNIT_IN, 1.0f },
+ { "mm", strlen("mm"), Res_value::TYPE_DIMENSION, Res_value::COMPLEX_UNIT_MM, 1.0f },
+ { "%", strlen("%"), Res_value::TYPE_FRACTION, Res_value::COMPLEX_UNIT_FRACTION, 1.0f/100 },
+ { "%p", strlen("%p"), Res_value::TYPE_FRACTION, Res_value::COMPLEX_UNIT_FRACTION_PARENT, 1.0f/100 },
+ { NULL, 0, 0, 0, 0 }
+};
+
+static bool parse_unit(const char* str, Res_value* outValue,
+ float* outScale, const char** outEnd)
+{
+ const char* end = str;
+ while (*end != 0 && !isspace((unsigned char)*end)) {
+ end++;
+ }
+ const size_t len = end-str;
+
+ const char* realEnd = end;
+ while (*realEnd != 0 && isspace((unsigned char)*realEnd)) {
+ realEnd++;
+ }
+ if (*realEnd != 0) {
+ return false;
+ }
+
+ const unit_entry* cur = unitNames;
+ while (cur->name) {
+ if (len == cur->len && strncmp(cur->name, str, len) == 0) {
+ outValue->dataType = cur->type;
+ outValue->data = cur->unit << Res_value::COMPLEX_UNIT_SHIFT;
+ *outScale = cur->scale;
+ *outEnd = end;
+ //printf("Found unit %s for %s\n", cur->name, str);
+ return true;
+ }
+ cur++;
+ }
+
+ return false;
+}
+
+
+bool ResTable::stringToInt(const char16_t* s, size_t len, Res_value* outValue)
+{
+ while (len > 0 && isspace16(*s)) {
+ s++;
+ len--;
+ }
+
+ if (len <= 0) {
+ return false;
+ }
+
+ size_t i = 0;
+ int32_t val = 0;
+ bool neg = false;
+
+ if (*s == '-') {
+ neg = true;
+ i++;
+ }
+
+ if (s[i] < '0' || s[i] > '9') {
+ return false;
+ }
+
+ // Decimal or hex?
+ if (s[i] == '0' && s[i+1] == 'x') {
+ if (outValue)
+ outValue->dataType = outValue->TYPE_INT_HEX;
+ i += 2;
+ bool error = false;
+ while (i < len && !error) {
+ val = (val*16) + get_hex(s[i], &error);
+ i++;
+ }
+ if (error) {
+ return false;
+ }
+ } else {
+ if (outValue)
+ outValue->dataType = outValue->TYPE_INT_DEC;
+ while (i < len) {
+ if (s[i] < '0' || s[i] > '9') {
+ return false;
+ }
+ val = (val*10) + s[i]-'0';
+ i++;
+ }
+ }
+
+ if (neg) val = -val;
+
+ while (i < len && isspace16(s[i])) {
+ i++;
+ }
+
+ if (i == len) {
+ if (outValue)
+ outValue->data = val;
+ return true;
+ }
+
+ return false;
+}
+
+bool ResTable::stringToFloat(const char16_t* s, size_t len, Res_value* outValue)
+{
+ while (len > 0 && isspace16(*s)) {
+ s++;
+ len--;
+ }
+
+ if (len <= 0) {
+ return false;
+ }
+
+ char buf[128];
+ int i=0;
+ while (len > 0 && *s != 0 && i < 126) {
+ if (*s > 255) {
+ return false;
+ }
+ buf[i++] = *s++;
+ len--;
+ }
+
+ if (len > 0) {
+ return false;
+ }
+ if (buf[0] < '0' && buf[0] > '9' && buf[0] != '.') {
+ return false;
+ }
+
+ buf[i] = 0;
+ const char* end;
+ float f = strtof(buf, (char**)&end);
+
+ if (*end != 0 && !isspace((unsigned char)*end)) {
+ // Might be a unit...
+ float scale;
+ if (parse_unit(end, outValue, &scale, &end)) {
+ f *= scale;
+ const bool neg = f < 0;
+ if (neg) f = -f;
+ uint64_t bits = (uint64_t)(f*(1<<23)+.5f);
+ uint32_t radix;
+ uint32_t shift;
+ if ((bits&0x7fffff) == 0) {
+ // Always use 23p0 if there is no fraction, just to make
+ // things easier to read.
+ radix = Res_value::COMPLEX_RADIX_23p0;
+ shift = 23;
+ } else if ((bits&0xffffffffff800000LL) == 0) {
+ // Magnitude is zero -- can fit in 0 bits of precision.
+ radix = Res_value::COMPLEX_RADIX_0p23;
+ shift = 0;
+ } else if ((bits&0xffffffff80000000LL) == 0) {
+ // Magnitude can fit in 8 bits of precision.
+ radix = Res_value::COMPLEX_RADIX_8p15;
+ shift = 8;
+ } else if ((bits&0xffffff8000000000LL) == 0) {
+ // Magnitude can fit in 16 bits of precision.
+ radix = Res_value::COMPLEX_RADIX_16p7;
+ shift = 16;
+ } else {
+ // Magnitude needs entire range, so no fractional part.
+ radix = Res_value::COMPLEX_RADIX_23p0;
+ shift = 23;
+ }
+ int32_t mantissa = (int32_t)(
+ (bits>>shift) & Res_value::COMPLEX_MANTISSA_MASK);
+ if (neg) {
+ mantissa = (-mantissa) & Res_value::COMPLEX_MANTISSA_MASK;
+ }
+ outValue->data |=
+ (radix<<Res_value::COMPLEX_RADIX_SHIFT)
+ | (mantissa<<Res_value::COMPLEX_MANTISSA_SHIFT);
+ //printf("Input value: %f 0x%016Lx, mult: %f, radix: %d, shift: %d, final: 0x%08x\n",
+ // f * (neg ? -1 : 1), bits, f*(1<<23),
+ // radix, shift, outValue->data);
+ return true;
+ }
+ return false;
+ }
+
+ while (*end != 0 && isspace((unsigned char)*end)) {
+ end++;
+ }
+
+ if (*end == 0) {
+ if (outValue) {
+ outValue->dataType = outValue->TYPE_FLOAT;
+ *(float*)(&outValue->data) = f;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool ResTable::stringToValue(Res_value* outValue, String16* outString,
+ const char16_t* s, size_t len,
+ bool preserveSpaces, bool coerceType,
+ uint32_t attrID,
+ const String16* defType,
+ const String16* defPackage,
+ Accessor* accessor,
+ void* accessorCookie,
+ uint32_t attrType,
+ bool enforcePrivate) const
+{
+ bool localizationSetting = accessor != NULL && accessor->getLocalizationSetting();
+ const char* errorMsg = NULL;
+
+ outValue->size = sizeof(Res_value);
+ outValue->res0 = 0;
+
+ // First strip leading/trailing whitespace. Do this before handling
+ // escapes, so they can be used to force whitespace into the string.
+ if (!preserveSpaces) {
+ while (len > 0 && isspace16(*s)) {
+ s++;
+ len--;
+ }
+ while (len > 0 && isspace16(s[len-1])) {
+ len--;
+ }
+ // If the string ends with '\', then we keep the space after it.
+ if (len > 0 && s[len-1] == '\\' && s[len] != 0) {
+ len++;
+ }
+ }
+
+ //printf("Value for: %s\n", String8(s, len).string());
+
+ uint32_t l10nReq = ResTable_map::L10N_NOT_REQUIRED;
+ uint32_t attrMin = 0x80000000, attrMax = 0x7fffffff;
+ bool fromAccessor = false;
+ if (attrID != 0 && !Res_INTERNALID(attrID)) {
+ const ssize_t p = getResourcePackageIndex(attrID);
+ const bag_entry* bag;
+ ssize_t cnt = p >= 0 ? lockBag(attrID, &bag) : -1;
+ //printf("For attr 0x%08x got bag of %d\n", attrID, cnt);
+ if (cnt >= 0) {
+ while (cnt > 0) {
+ //printf("Entry 0x%08x = 0x%08x\n", bag->map.name.ident, bag->map.value.data);
+ switch (bag->map.name.ident) {
+ case ResTable_map::ATTR_TYPE:
+ attrType = bag->map.value.data;
+ break;
+ case ResTable_map::ATTR_MIN:
+ attrMin = bag->map.value.data;
+ break;
+ case ResTable_map::ATTR_MAX:
+ attrMax = bag->map.value.data;
+ break;
+ case ResTable_map::ATTR_L10N:
+ l10nReq = bag->map.value.data;
+ break;
+ }
+ bag++;
+ cnt--;
+ }
+ unlockBag(bag);
+ } else if (accessor && accessor->getAttributeType(attrID, &attrType)) {
+ fromAccessor = true;
+ if (attrType == ResTable_map::TYPE_ENUM
+ || attrType == ResTable_map::TYPE_FLAGS
+ || attrType == ResTable_map::TYPE_INTEGER) {
+ accessor->getAttributeMin(attrID, &attrMin);
+ accessor->getAttributeMax(attrID, &attrMax);
+ }
+ if (localizationSetting) {
+ l10nReq = accessor->getAttributeL10N(attrID);
+ }
+ }
+ }
+
+ const bool canStringCoerce =
+ coerceType && (attrType&ResTable_map::TYPE_STRING) != 0;
+
+ if (*s == '@') {
+ outValue->dataType = outValue->TYPE_REFERENCE;
+
+ // Note: we don't check attrType here because the reference can
+ // be to any other type; we just need to count on the client making
+ // sure the referenced type is correct.
+
+ //printf("Looking up ref: %s\n", String8(s, len).string());
+
+ // It's a reference!
+ if (len == 5 && s[1]=='n' && s[2]=='u' && s[3]=='l' && s[4]=='l') {
+ outValue->data = 0;
+ return true;
+ } else {
+ bool createIfNotFound = false;
+ const char16_t* resourceRefName;
+ int resourceNameLen;
+ if (len > 2 && s[1] == '+') {
+ createIfNotFound = true;
+ resourceRefName = s + 2;
+ resourceNameLen = len - 2;
+ } else if (len > 2 && s[1] == '*') {
+ enforcePrivate = false;
+ resourceRefName = s + 2;
+ resourceNameLen = len - 2;
+ } else {
+ createIfNotFound = false;
+ resourceRefName = s + 1;
+ resourceNameLen = len - 1;
+ }
+ String16 package, type, name;
+ if (!expandResourceRef(resourceRefName,resourceNameLen, &package, &type, &name,
+ defType, defPackage, &errorMsg)) {
+ if (accessor != NULL) {
+ accessor->reportError(accessorCookie, errorMsg);
+ }
+ return false;
+ }
+
+ uint32_t specFlags = 0;
+ uint32_t rid = identifierForName(name.string(), name.size(), type.string(),
+ type.size(), package.string(), package.size(), &specFlags);
+ if (rid != 0) {
+ if (enforcePrivate) {
+ if ((specFlags&ResTable_typeSpec::SPEC_PUBLIC) == 0) {
+ if (accessor != NULL) {
+ accessor->reportError(accessorCookie, "Resource is not public.");
+ }
+ return false;
+ }
+ }
+ if (!accessor) {
+ outValue->data = rid;
+ return true;
+ }
+ rid = Res_MAKEID(
+ accessor->getRemappedPackage(Res_GETPACKAGE(rid)),
+ Res_GETTYPE(rid), Res_GETENTRY(rid));
+ TABLE_NOISY(printf("Incl %s:%s/%s: 0x%08x\n",
+ String8(package).string(), String8(type).string(),
+ String8(name).string(), rid));
+ outValue->data = rid;
+ return true;
+ }
+
+ if (accessor) {
+ uint32_t rid = accessor->getCustomResourceWithCreation(package, type, name,
+ createIfNotFound);
+ if (rid != 0) {
+ TABLE_NOISY(printf("Pckg %s:%s/%s: 0x%08x\n",
+ String8(package).string(), String8(type).string(),
+ String8(name).string(), rid));
+ outValue->data = rid;
+ return true;
+ }
+ }
+ }
+
+ if (accessor != NULL) {
+ accessor->reportError(accessorCookie, "No resource found that matches the given name");
+ }
+ return false;
+ }
+
+ // if we got to here, and localization is required and it's not a reference,
+ // complain and bail.
+ if (l10nReq == ResTable_map::L10N_SUGGESTED) {
+ if (localizationSetting) {
+ if (accessor != NULL) {
+ accessor->reportError(accessorCookie, "This attribute must be localized.");
+ }
+ }
+ }
+
+ if (*s == '#') {
+ // It's a color! Convert to an integer of the form 0xaarrggbb.
+ uint32_t color = 0;
+ bool error = false;
+ if (len == 4) {
+ outValue->dataType = outValue->TYPE_INT_COLOR_RGB4;
+ color |= 0xFF000000;
+ color |= get_hex(s[1], &error) << 20;
+ color |= get_hex(s[1], &error) << 16;
+ color |= get_hex(s[2], &error) << 12;
+ color |= get_hex(s[2], &error) << 8;
+ color |= get_hex(s[3], &error) << 4;
+ color |= get_hex(s[3], &error);
+ } else if (len == 5) {
+ outValue->dataType = outValue->TYPE_INT_COLOR_ARGB4;
+ color |= get_hex(s[1], &error) << 28;
+ color |= get_hex(s[1], &error) << 24;
+ color |= get_hex(s[2], &error) << 20;
+ color |= get_hex(s[2], &error) << 16;
+ color |= get_hex(s[3], &error) << 12;
+ color |= get_hex(s[3], &error) << 8;
+ color |= get_hex(s[4], &error) << 4;
+ color |= get_hex(s[4], &error);
+ } else if (len == 7) {
+ outValue->dataType = outValue->TYPE_INT_COLOR_RGB8;
+ color |= 0xFF000000;
+ color |= get_hex(s[1], &error) << 20;
+ color |= get_hex(s[2], &error) << 16;
+ color |= get_hex(s[3], &error) << 12;
+ color |= get_hex(s[4], &error) << 8;
+ color |= get_hex(s[5], &error) << 4;
+ color |= get_hex(s[6], &error);
+ } else if (len == 9) {
+ outValue->dataType = outValue->TYPE_INT_COLOR_ARGB8;
+ color |= get_hex(s[1], &error) << 28;
+ color |= get_hex(s[2], &error) << 24;
+ color |= get_hex(s[3], &error) << 20;
+ color |= get_hex(s[4], &error) << 16;
+ color |= get_hex(s[5], &error) << 12;
+ color |= get_hex(s[6], &error) << 8;
+ color |= get_hex(s[7], &error) << 4;
+ color |= get_hex(s[8], &error);
+ } else {
+ error = true;
+ }
+ if (!error) {
+ if ((attrType&ResTable_map::TYPE_COLOR) == 0) {
+ if (!canStringCoerce) {
+ if (accessor != NULL) {
+ accessor->reportError(accessorCookie,
+ "Color types not allowed");
+ }
+ return false;
+ }
+ } else {
+ outValue->data = color;
+ //printf("Color input=%s, output=0x%x\n", String8(s, len).string(), color);
+ return true;
+ }
+ } else {
+ if ((attrType&ResTable_map::TYPE_COLOR) != 0) {
+ if (accessor != NULL) {
+ accessor->reportError(accessorCookie, "Color value not valid --"
+ " must be #rgb, #argb, #rrggbb, or #aarrggbb");
+ }
+ #if 0
+ fprintf(stderr, "%s: Color ID %s value %s is not valid\n",
+ "Resource File", //(const char*)in->getPrintableSource(),
+ String8(*curTag).string(),
+ String8(s, len).string());
+ #endif
+ return false;
+ }
+ }
+ }
+
+ if (*s == '?') {
+ outValue->dataType = outValue->TYPE_ATTRIBUTE;
+
+ // Note: we don't check attrType here because the reference can
+ // be to any other type; we just need to count on the client making
+ // sure the referenced type is correct.
+
+ //printf("Looking up attr: %s\n", String8(s, len).string());
+
+ static const String16 attr16("attr");
+ String16 package, type, name;
+ if (!expandResourceRef(s+1, len-1, &package, &type, &name,
+ &attr16, defPackage, &errorMsg)) {
+ if (accessor != NULL) {
+ accessor->reportError(accessorCookie, errorMsg);
+ }
+ return false;
+ }
+
+ //printf("Pkg: %s, Type: %s, Name: %s\n",
+ // String8(package).string(), String8(type).string(),
+ // String8(name).string());
+ uint32_t specFlags = 0;
+ uint32_t rid =
+ identifierForName(name.string(), name.size(),
+ type.string(), type.size(),
+ package.string(), package.size(), &specFlags);
+ if (rid != 0) {
+ if (enforcePrivate) {
+ if ((specFlags&ResTable_typeSpec::SPEC_PUBLIC) == 0) {
+ if (accessor != NULL) {
+ accessor->reportError(accessorCookie, "Attribute is not public.");
+ }
+ return false;
+ }
+ }
+ if (!accessor) {
+ outValue->data = rid;
+ return true;
+ }
+ rid = Res_MAKEID(
+ accessor->getRemappedPackage(Res_GETPACKAGE(rid)),
+ Res_GETTYPE(rid), Res_GETENTRY(rid));
+ //printf("Incl %s:%s/%s: 0x%08x\n",
+ // String8(package).string(), String8(type).string(),
+ // String8(name).string(), rid);
+ outValue->data = rid;
+ return true;
+ }
+
+ if (accessor) {
+ uint32_t rid = accessor->getCustomResource(package, type, name);
+ if (rid != 0) {
+ //printf("Mine %s:%s/%s: 0x%08x\n",
+ // String8(package).string(), String8(type).string(),
+ // String8(name).string(), rid);
+ outValue->data = rid;
+ return true;
+ }
+ }
+
+ if (accessor != NULL) {
+ accessor->reportError(accessorCookie, "No resource found that matches the given name");
+ }
+ return false;
+ }
+
+ if (stringToInt(s, len, outValue)) {
+ if ((attrType&ResTable_map::TYPE_INTEGER) == 0) {
+ // If this type does not allow integers, but does allow floats,
+ // fall through on this error case because the float type should
+ // be able to accept any integer value.
+ if (!canStringCoerce && (attrType&ResTable_map::TYPE_FLOAT) == 0) {
+ if (accessor != NULL) {
+ accessor->reportError(accessorCookie, "Integer types not allowed");
+ }
+ return false;
+ }
+ } else {
+ if (((int32_t)outValue->data) < ((int32_t)attrMin)
+ || ((int32_t)outValue->data) > ((int32_t)attrMax)) {
+ if (accessor != NULL) {
+ accessor->reportError(accessorCookie, "Integer value out of range");
+ }
+ return false;
+ }
+ return true;
+ }
+ }
+
+ if (stringToFloat(s, len, outValue)) {
+ if (outValue->dataType == Res_value::TYPE_DIMENSION) {
+ if ((attrType&ResTable_map::TYPE_DIMENSION) != 0) {
+ return true;
+ }
+ if (!canStringCoerce) {
+ if (accessor != NULL) {
+ accessor->reportError(accessorCookie, "Dimension types not allowed");
+ }
+ return false;
+ }
+ } else if (outValue->dataType == Res_value::TYPE_FRACTION) {
+ if ((attrType&ResTable_map::TYPE_FRACTION) != 0) {
+ return true;
+ }
+ if (!canStringCoerce) {
+ if (accessor != NULL) {
+ accessor->reportError(accessorCookie, "Fraction types not allowed");
+ }
+ return false;
+ }
+ } else if ((attrType&ResTable_map::TYPE_FLOAT) == 0) {
+ if (!canStringCoerce) {
+ if (accessor != NULL) {
+ accessor->reportError(accessorCookie, "Float types not allowed");
+ }
+ return false;
+ }
+ } else {
+ return true;
+ }
+ }
+
+ if (len == 4) {
+ if ((s[0] == 't' || s[0] == 'T') &&
+ (s[1] == 'r' || s[1] == 'R') &&
+ (s[2] == 'u' || s[2] == 'U') &&
+ (s[3] == 'e' || s[3] == 'E')) {
+ if ((attrType&ResTable_map::TYPE_BOOLEAN) == 0) {
+ if (!canStringCoerce) {
+ if (accessor != NULL) {
+ accessor->reportError(accessorCookie, "Boolean types not allowed");
+ }
+ return false;
+ }
+ } else {
+ outValue->dataType = outValue->TYPE_INT_BOOLEAN;
+ outValue->data = (uint32_t)-1;
+ return true;
+ }
+ }
+ }
+
+ if (len == 5) {
+ if ((s[0] == 'f' || s[0] == 'F') &&
+ (s[1] == 'a' || s[1] == 'A') &&
+ (s[2] == 'l' || s[2] == 'L') &&
+ (s[3] == 's' || s[3] == 'S') &&
+ (s[4] == 'e' || s[4] == 'E')) {
+ if ((attrType&ResTable_map::TYPE_BOOLEAN) == 0) {
+ if (!canStringCoerce) {
+ if (accessor != NULL) {
+ accessor->reportError(accessorCookie, "Boolean types not allowed");
+ }
+ return false;
+ }
+ } else {
+ outValue->dataType = outValue->TYPE_INT_BOOLEAN;
+ outValue->data = 0;
+ return true;
+ }
+ }
+ }
+
+ if ((attrType&ResTable_map::TYPE_ENUM) != 0) {
+ const ssize_t p = getResourcePackageIndex(attrID);
+ const bag_entry* bag;
+ ssize_t cnt = p >= 0 ? lockBag(attrID, &bag) : -1;
+ //printf("Got %d for enum\n", cnt);
+ if (cnt >= 0) {
+ resource_name rname;
+ while (cnt > 0) {
+ if (!Res_INTERNALID(bag->map.name.ident)) {
+ //printf("Trying attr #%08x\n", bag->map.name.ident);
+ if (getResourceName(bag->map.name.ident, false, &rname)) {
+ #if 0
+ printf("Matching %s against %s (0x%08x)\n",
+ String8(s, len).string(),
+ String8(rname.name, rname.nameLen).string(),
+ bag->map.name.ident);
+ #endif
+ if (strzcmp16(s, len, rname.name, rname.nameLen) == 0) {
+ outValue->dataType = bag->map.value.dataType;
+ outValue->data = bag->map.value.data;
+ unlockBag(bag);
+ return true;
+ }
+ }
+
+ }
+ bag++;
+ cnt--;
+ }
+ unlockBag(bag);
+ }
+
+ if (fromAccessor) {
+ if (accessor->getAttributeEnum(attrID, s, len, outValue)) {
+ return true;
+ }
+ }
+ }
+
+ if ((attrType&ResTable_map::TYPE_FLAGS) != 0) {
+ const ssize_t p = getResourcePackageIndex(attrID);
+ const bag_entry* bag;
+ ssize_t cnt = p >= 0 ? lockBag(attrID, &bag) : -1;
+ //printf("Got %d for flags\n", cnt);
+ if (cnt >= 0) {
+ bool failed = false;
+ resource_name rname;
+ outValue->dataType = Res_value::TYPE_INT_HEX;
+ outValue->data = 0;
+ const char16_t* end = s + len;
+ const char16_t* pos = s;
+ while (pos < end && !failed) {
+ const char16_t* start = pos;
+ pos++;
+ while (pos < end && *pos != '|') {
+ pos++;
+ }
+ //printf("Looking for: %s\n", String8(start, pos-start).string());
+ const bag_entry* bagi = bag;
+ ssize_t i;
+ for (i=0; i<cnt; i++, bagi++) {
+ if (!Res_INTERNALID(bagi->map.name.ident)) {
+ //printf("Trying attr #%08x\n", bagi->map.name.ident);
+ if (getResourceName(bagi->map.name.ident, false, &rname)) {
+ #if 0
+ printf("Matching %s against %s (0x%08x)\n",
+ String8(start,pos-start).string(),
+ String8(rname.name, rname.nameLen).string(),
+ bagi->map.name.ident);
+ #endif
+ if (strzcmp16(start, pos-start, rname.name, rname.nameLen) == 0) {
+ outValue->data |= bagi->map.value.data;
+ break;
+ }
+ }
+ }
+ }
+ if (i >= cnt) {
+ // Didn't find this flag identifier.
+ failed = true;
+ }
+ if (pos < end) {
+ pos++;
+ }
+ }
+ unlockBag(bag);
+ if (!failed) {
+ //printf("Final flag value: 0x%lx\n", outValue->data);
+ return true;
+ }
+ }
+
+
+ if (fromAccessor) {
+ if (accessor->getAttributeFlags(attrID, s, len, outValue)) {
+ //printf("Final flag value: 0x%lx\n", outValue->data);
+ return true;
+ }
+ }
+ }
+
+ if ((attrType&ResTable_map::TYPE_STRING) == 0) {
+ if (accessor != NULL) {
+ accessor->reportError(accessorCookie, "String types not allowed");
+ }
+ return false;
+ }
+
+ // Generic string handling...
+ outValue->dataType = outValue->TYPE_STRING;
+ if (outString) {
+ bool failed = collectString(outString, s, len, preserveSpaces, &errorMsg);
+ if (accessor != NULL) {
+ accessor->reportError(accessorCookie, errorMsg);
+ }
+ return failed;
+ }
+
+ return true;
+}
+
+bool ResTable::collectString(String16* outString,
+ const char16_t* s, size_t len,
+ bool preserveSpaces,
+ const char** outErrorMsg,
+ bool append)
+{
+ String16 tmp;
+
+ char quoted = 0;
+ const char16_t* p = s;
+ while (p < (s+len)) {
+ while (p < (s+len)) {
+ const char16_t c = *p;
+ if (c == '\\') {
+ break;
+ }
+ if (!preserveSpaces) {
+ if (quoted == 0 && isspace16(c)
+ && (c != ' ' || isspace16(*(p+1)))) {
+ break;
+ }
+ if (c == '"' && (quoted == 0 || quoted == '"')) {
+ break;
+ }
+ if (c == '\'' && (quoted == 0 || quoted == '\'')) {
+ /*
+ * In practice, when people write ' instead of \'
+ * in a string, they are doing it by accident
+ * instead of really meaning to use ' as a quoting
+ * character. Warn them so they don't lose it.
+ */
+ if (outErrorMsg) {
+ *outErrorMsg = "Apostrophe not preceded by \\";
+ }
+ return false;
+ }
+ }
+ p++;
+ }
+ if (p < (s+len)) {
+ if (p > s) {
+ tmp.append(String16(s, p-s));
+ }
+ if (!preserveSpaces && (*p == '"' || *p == '\'')) {
+ if (quoted == 0) {
+ quoted = *p;
+ } else {
+ quoted = 0;
+ }
+ p++;
+ } else if (!preserveSpaces && isspace16(*p)) {
+ // Space outside of a quote -- consume all spaces and
+ // leave a single plain space char.
+ tmp.append(String16(" "));
+ p++;
+ while (p < (s+len) && isspace16(*p)) {
+ p++;
+ }
+ } else if (*p == '\\') {
+ p++;
+ if (p < (s+len)) {
+ switch (*p) {
+ case 't':
+ tmp.append(String16("\t"));
+ break;
+ case 'n':
+ tmp.append(String16("\n"));
+ break;
+ case '#':
+ tmp.append(String16("#"));
+ break;
+ case '@':
+ tmp.append(String16("@"));
+ break;
+ case '?':
+ tmp.append(String16("?"));
+ break;
+ case '"':
+ tmp.append(String16("\""));
+ break;
+ case '\'':
+ tmp.append(String16("'"));
+ break;
+ case '\\':
+ tmp.append(String16("\\"));
+ break;
+ case 'u':
+ {
+ char16_t chr = 0;
+ int i = 0;
+ while (i < 4 && p[1] != 0) {
+ p++;
+ i++;
+ int c;
+ if (*p >= '0' && *p <= '9') {
+ c = *p - '0';
+ } else if (*p >= 'a' && *p <= 'f') {
+ c = *p - 'a' + 10;
+ } else if (*p >= 'A' && *p <= 'F') {
+ c = *p - 'A' + 10;
+ } else {
+ if (outErrorMsg) {
+ *outErrorMsg = "Bad character in \\u unicode escape sequence";
+ }
+ return false;
+ }
+ chr = (chr<<4) | c;
+ }
+ tmp.append(String16(&chr, 1));
+ } break;
+ default:
+ // ignore unknown escape chars.
+ break;
+ }
+ p++;
+ }
+ }
+ len -= (p-s);
+ s = p;
+ }
+ }
+
+ if (tmp.size() != 0) {
+ if (len > 0) {
+ tmp.append(String16(s, len));
+ }
+ if (append) {
+ outString->append(tmp);
+ } else {
+ outString->setTo(tmp);
+ }
+ } else {
+ if (append) {
+ outString->append(String16(s, len));
+ } else {
+ outString->setTo(s, len);
+ }
+ }
+
+ return true;
+}
+
+size_t ResTable::getBasePackageCount() const
+{
+ if (mError != NO_ERROR) {
+ return 0;
+ }
+ return mPackageGroups.size();
+}
+
+const char16_t* ResTable::getBasePackageName(size_t idx) const
+{
+ if (mError != NO_ERROR) {
+ return 0;
+ }
+ LOG_FATAL_IF(idx >= mPackageGroups.size(),
+ "Requested package index %d past package count %d",
+ (int)idx, (int)mPackageGroups.size());
+ return mPackageGroups[idx]->name.string();
+}
+
+uint32_t ResTable::getBasePackageId(size_t idx) const
+{
+ if (mError != NO_ERROR) {
+ return 0;
+ }
+ LOG_FATAL_IF(idx >= mPackageGroups.size(),
+ "Requested package index %d past package count %d",
+ (int)idx, (int)mPackageGroups.size());
+ return mPackageGroups[idx]->id;
+}
+
+size_t ResTable::getTableCount() const
+{
+ return mHeaders.size();
+}
+
+const ResStringPool* ResTable::getTableStringBlock(size_t index) const
+{
+ return &mHeaders[index]->values;
+}
+
+void* ResTable::getTableCookie(size_t index) const
+{
+ return mHeaders[index]->cookie;
+}
+
+void ResTable::getConfigurations(Vector<ResTable_config>* configs) const
+{
+ const size_t I = mPackageGroups.size();
+ for (size_t i=0; i<I; i++) {
+ const PackageGroup* packageGroup = mPackageGroups[i];
+ const size_t J = packageGroup->packages.size();
+ for (size_t j=0; j<J; j++) {
+ const Package* package = packageGroup->packages[j];
+ const size_t K = package->types.size();
+ for (size_t k=0; k<K; k++) {
+ const Type* type = package->types[k];
+ if (type == NULL) continue;
+ const size_t L = type->configs.size();
+ for (size_t l=0; l<L; l++) {
+ const ResTable_type* config = type->configs[l];
+ const ResTable_config* cfg = &config->config;
+ // only insert unique
+ const size_t M = configs->size();
+ size_t m;
+ for (m=0; m<M; m++) {
+ if (0 == (*configs)[m].compare(*cfg)) {
+ break;
+ }
+ }
+ // if we didn't find it
+ if (m == M) {
+ configs->add(*cfg);
+ }
+ }
+ }
+ }
+ }
+}
+
+void ResTable::getLocales(Vector<String8>* locales) const
+{
+ Vector<ResTable_config> configs;
+ ALOGV("calling getConfigurations");
+ getConfigurations(&configs);
+ ALOGV("called getConfigurations size=%d", (int)configs.size());
+ const size_t I = configs.size();
+ for (size_t i=0; i<I; i++) {
+ char locale[6];
+ configs[i].getLocale(locale);
+ const size_t J = locales->size();
+ size_t j;
+ for (j=0; j<J; j++) {
+ if (0 == strcmp(locale, (*locales)[j].string())) {
+ break;
+ }
+ }
+ if (j == J) {
+ locales->add(String8(locale));
+ }
+ }
+}
+
+ssize_t ResTable::getEntry(
+ const Package* package, int typeIndex, int entryIndex,
+ const ResTable_config* config,
+ const ResTable_type** outType, const ResTable_entry** outEntry,
+ const Type** outTypeClass) const
+{
+ ALOGV("Getting entry from package %p\n", package);
+ const ResTable_package* const pkg = package->package;
+
+ const Type* allTypes = package->getType(typeIndex);
+ ALOGV("allTypes=%p\n", allTypes);
+ if (allTypes == NULL) {
+ ALOGV("Skipping entry type index 0x%02x because type is NULL!\n", typeIndex);
+ return 0;
+ }
+
+ if ((size_t)entryIndex >= allTypes->entryCount) {
+ ALOGW("getEntry failing because entryIndex %d is beyond type entryCount %d",
+ entryIndex, (int)allTypes->entryCount);
+ return BAD_TYPE;
+ }
+
+ const ResTable_type* type = NULL;
+ uint32_t offset = ResTable_type::NO_ENTRY;
+ ResTable_config bestConfig;
+ memset(&bestConfig, 0, sizeof(bestConfig)); // make the compiler shut up
+
+ const size_t NT = allTypes->configs.size();
+ for (size_t i=0; i<NT; i++) {
+ const ResTable_type* const thisType = allTypes->configs[i];
+ if (thisType == NULL) continue;
+
+ ResTable_config thisConfig;
+ thisConfig.copyFromDtoH(thisType->config);
+
+ TABLE_GETENTRY(ALOGI("Match entry 0x%x in type 0x%x (sz 0x%x): %s\n",
+ entryIndex, typeIndex+1, dtohl(thisType->config.size),
+ thisConfig.toString().string()));
+
+ // Check to make sure this one is valid for the current parameters.
+ if (config && !thisConfig.match(*config)) {
+ TABLE_GETENTRY(ALOGI("Does not match config!\n"));
+ continue;
+ }
+
+ // Check if there is the desired entry in this type.
+
+ const uint8_t* const end = ((const uint8_t*)thisType)
+ + dtohl(thisType->header.size);
+ const uint32_t* const eindex = (const uint32_t*)
+ (((const uint8_t*)thisType) + dtohs(thisType->header.headerSize));
+
+ uint32_t thisOffset = dtohl(eindex[entryIndex]);
+ if (thisOffset == ResTable_type::NO_ENTRY) {
+ TABLE_GETENTRY(ALOGI("Skipping because it is not defined!\n"));
+ continue;
+ }
+
+ if (type != NULL) {
+ // Check if this one is less specific than the last found. If so,
+ // we will skip it. We check starting with things we most care
+ // about to those we least care about.
+ if (!thisConfig.isBetterThan(bestConfig, config)) {
+ TABLE_GETENTRY(ALOGI("This config is worse than last!\n"));
+ continue;
+ }
+ }
+
+ type = thisType;
+ offset = thisOffset;
+ bestConfig = thisConfig;
+ TABLE_GETENTRY(ALOGI("Best entry so far -- using it!\n"));
+ if (!config) break;
+ }
+
+ if (type == NULL) {
+ TABLE_GETENTRY(ALOGI("No value found for requested entry!\n"));
+ return BAD_INDEX;
+ }
+
+ offset += dtohl(type->entriesStart);
+ TABLE_NOISY(aout << "Looking in resource table " << package->header->header
+ << ", typeOff="
+ << (void*)(((const char*)type)-((const char*)package->header->header))
+ << ", offset=" << (void*)offset << endl);
+
+ if (offset > (dtohl(type->header.size)-sizeof(ResTable_entry))) {
+ ALOGW("ResTable_entry at 0x%x is beyond type chunk data 0x%x",
+ offset, dtohl(type->header.size));
+ return BAD_TYPE;
+ }
+ if ((offset&0x3) != 0) {
+ ALOGW("ResTable_entry at 0x%x is not on an integer boundary",
+ offset);
+ return BAD_TYPE;
+ }
+
+ const ResTable_entry* const entry = (const ResTable_entry*)
+ (((const uint8_t*)type) + offset);
+ if (dtohs(entry->size) < sizeof(*entry)) {
+ ALOGW("ResTable_entry size 0x%x is too small", dtohs(entry->size));
+ return BAD_TYPE;
+ }
+
+ *outType = type;
+ *outEntry = entry;
+ if (outTypeClass != NULL) {
+ *outTypeClass = allTypes;
+ }
+ return offset + dtohs(entry->size);
+}
+
+status_t ResTable::parsePackage(const ResTable_package* const pkg,
+ const Header* const header, uint32_t idmap_id)
+{
+ const uint8_t* base = (const uint8_t*)pkg;
+ status_t err = validate_chunk(&pkg->header, sizeof(*pkg),
+ header->dataEnd, "ResTable_package");
+ if (err != NO_ERROR) {
+ return (mError=err);
+ }
+
+ const size_t pkgSize = dtohl(pkg->header.size);
+
+ if (dtohl(pkg->typeStrings) >= pkgSize) {
+ ALOGW("ResTable_package type strings at %p are past chunk size %p.",
+ (void*)dtohl(pkg->typeStrings), (void*)pkgSize);
+ return (mError=BAD_TYPE);
+ }
+ if ((dtohl(pkg->typeStrings)&0x3) != 0) {
+ ALOGW("ResTable_package type strings at %p is not on an integer boundary.",
+ (void*)dtohl(pkg->typeStrings));
+ return (mError=BAD_TYPE);
+ }
+ if (dtohl(pkg->keyStrings) >= pkgSize) {
+ ALOGW("ResTable_package key strings at %p are past chunk size %p.",
+ (void*)dtohl(pkg->keyStrings), (void*)pkgSize);
+ return (mError=BAD_TYPE);
+ }
+ if ((dtohl(pkg->keyStrings)&0x3) != 0) {
+ ALOGW("ResTable_package key strings at %p is not on an integer boundary.",
+ (void*)dtohl(pkg->keyStrings));
+ return (mError=BAD_TYPE);
+ }
+
+ Package* package = NULL;
+ PackageGroup* group = NULL;
+ uint32_t id = idmap_id != 0 ? idmap_id : dtohl(pkg->id);
+ // If at this point id == 0, pkg is an overlay package without a
+ // corresponding idmap. During regular usage, overlay packages are
+ // always loaded alongside their idmaps, but during idmap creation
+ // the package is temporarily loaded by itself.
+ if (id < 256) {
+
+ package = new Package(this, header, pkg);
+ if (package == NULL) {
+ return (mError=NO_MEMORY);
+ }
+
+ size_t idx = mPackageMap[id];
+ if (idx == 0) {
+ idx = mPackageGroups.size()+1;
+
+ char16_t tmpName[sizeof(pkg->name)/sizeof(char16_t)];
+ strcpy16_dtoh(tmpName, pkg->name, sizeof(pkg->name)/sizeof(char16_t));
+ group = new PackageGroup(this, String16(tmpName), id);
+ if (group == NULL) {
+ delete package;
+ return (mError=NO_MEMORY);
+ }
+
+ err = package->typeStrings.setTo(base+dtohl(pkg->typeStrings),
+ header->dataEnd-(base+dtohl(pkg->typeStrings)));
+ if (err != NO_ERROR) {
+ delete group;
+ delete package;
+ return (mError=err);
+ }
+ err = package->keyStrings.setTo(base+dtohl(pkg->keyStrings),
+ header->dataEnd-(base+dtohl(pkg->keyStrings)));
+ if (err != NO_ERROR) {
+ delete group;
+ delete package;
+ return (mError=err);
+ }
+
+ //printf("Adding new package id %d at index %d\n", id, idx);
+ err = mPackageGroups.add(group);
+ if (err < NO_ERROR) {
+ return (mError=err);
+ }
+ group->basePackage = package;
+
+ mPackageMap[id] = (uint8_t)idx;
+ } else {
+ group = mPackageGroups.itemAt(idx-1);
+ if (group == NULL) {
+ return (mError=UNKNOWN_ERROR);
+ }
+ }
+ err = group->packages.add(package);
+ if (err < NO_ERROR) {
+ return (mError=err);
+ }
+ } else {
+ LOG_ALWAYS_FATAL("Package id out of range");
+ return NO_ERROR;
+ }
+
+
+ // Iterate through all chunks.
+ size_t curPackage = 0;
+
+ const ResChunk_header* chunk =
+ (const ResChunk_header*)(((const uint8_t*)pkg)
+ + dtohs(pkg->header.headerSize));
+ const uint8_t* endPos = ((const uint8_t*)pkg) + dtohs(pkg->header.size);
+ while (((const uint8_t*)chunk) <= (endPos-sizeof(ResChunk_header)) &&
+ ((const uint8_t*)chunk) <= (endPos-dtohl(chunk->size))) {
+ TABLE_NOISY(ALOGV("PackageChunk: type=0x%x, headerSize=0x%x, size=0x%x, pos=%p\n",
+ dtohs(chunk->type), dtohs(chunk->headerSize), dtohl(chunk->size),
+ (void*)(((const uint8_t*)chunk) - ((const uint8_t*)header->header))));
+ const size_t csize = dtohl(chunk->size);
+ const uint16_t ctype = dtohs(chunk->type);
+ if (ctype == RES_TABLE_TYPE_SPEC_TYPE) {
+ const ResTable_typeSpec* typeSpec = (const ResTable_typeSpec*)(chunk);
+ err = validate_chunk(&typeSpec->header, sizeof(*typeSpec),
+ endPos, "ResTable_typeSpec");
+ if (err != NO_ERROR) {
+ return (mError=err);
+ }
+
+ const size_t typeSpecSize = dtohl(typeSpec->header.size);
+
+ LOAD_TABLE_NOISY(printf("TypeSpec off %p: type=0x%x, headerSize=0x%x, size=%p\n",
+ (void*)(base-(const uint8_t*)chunk),
+ dtohs(typeSpec->header.type),
+ dtohs(typeSpec->header.headerSize),
+ (void*)typeSize));
+ // look for block overrun or int overflow when multiplying by 4
+ if ((dtohl(typeSpec->entryCount) > (INT32_MAX/sizeof(uint32_t))
+ || dtohs(typeSpec->header.headerSize)+(sizeof(uint32_t)*dtohl(typeSpec->entryCount))
+ > typeSpecSize)) {
+ ALOGW("ResTable_typeSpec entry index to %p extends beyond chunk end %p.",
+ (void*)(dtohs(typeSpec->header.headerSize)
+ +(sizeof(uint32_t)*dtohl(typeSpec->entryCount))),
+ (void*)typeSpecSize);
+ return (mError=BAD_TYPE);
+ }
+
+ if (typeSpec->id == 0) {
+ ALOGW("ResTable_type has an id of 0.");
+ return (mError=BAD_TYPE);
+ }
+
+ while (package->types.size() < typeSpec->id) {
+ package->types.add(NULL);
+ }
+ Type* t = package->types[typeSpec->id-1];
+ if (t == NULL) {
+ t = new Type(header, package, dtohl(typeSpec->entryCount));
+ package->types.editItemAt(typeSpec->id-1) = t;
+ } else if (dtohl(typeSpec->entryCount) != t->entryCount) {
+ ALOGW("ResTable_typeSpec entry count inconsistent: given %d, previously %d",
+ (int)dtohl(typeSpec->entryCount), (int)t->entryCount);
+ return (mError=BAD_TYPE);
+ }
+ t->typeSpecFlags = (const uint32_t*)(
+ ((const uint8_t*)typeSpec) + dtohs(typeSpec->header.headerSize));
+ t->typeSpec = typeSpec;
+
+ } else if (ctype == RES_TABLE_TYPE_TYPE) {
+ const ResTable_type* type = (const ResTable_type*)(chunk);
+ err = validate_chunk(&type->header, sizeof(*type)-sizeof(ResTable_config)+4,
+ endPos, "ResTable_type");
+ if (err != NO_ERROR) {
+ return (mError=err);
+ }
+
+ const size_t typeSize = dtohl(type->header.size);
+
+ LOAD_TABLE_NOISY(printf("Type off %p: type=0x%x, headerSize=0x%x, size=%p\n",
+ (void*)(base-(const uint8_t*)chunk),
+ dtohs(type->header.type),
+ dtohs(type->header.headerSize),
+ (void*)typeSize));
+ if (dtohs(type->header.headerSize)+(sizeof(uint32_t)*dtohl(type->entryCount))
+ > typeSize) {
+ ALOGW("ResTable_type entry index to %p extends beyond chunk end %p.",
+ (void*)(dtohs(type->header.headerSize)
+ +(sizeof(uint32_t)*dtohl(type->entryCount))),
+ (void*)typeSize);
+ return (mError=BAD_TYPE);
+ }
+ if (dtohl(type->entryCount) != 0
+ && dtohl(type->entriesStart) > (typeSize-sizeof(ResTable_entry))) {
+ ALOGW("ResTable_type entriesStart at %p extends beyond chunk end %p.",
+ (void*)dtohl(type->entriesStart), (void*)typeSize);
+ return (mError=BAD_TYPE);
+ }
+ if (type->id == 0) {
+ ALOGW("ResTable_type has an id of 0.");
+ return (mError=BAD_TYPE);
+ }
+
+ while (package->types.size() < type->id) {
+ package->types.add(NULL);
+ }
+ Type* t = package->types[type->id-1];
+ if (t == NULL) {
+ t = new Type(header, package, dtohl(type->entryCount));
+ package->types.editItemAt(type->id-1) = t;
+ } else if (dtohl(type->entryCount) != t->entryCount) {
+ ALOGW("ResTable_type entry count inconsistent: given %d, previously %d",
+ (int)dtohl(type->entryCount), (int)t->entryCount);
+ return (mError=BAD_TYPE);
+ }
+
+ TABLE_GETENTRY(
+ ResTable_config thisConfig;
+ thisConfig.copyFromDtoH(type->config);
+ ALOGI("Adding config to type %d: %s\n",
+ type->id, thisConfig.toString().string()));
+ t->configs.add(type);
+ } else {
+ status_t err = validate_chunk(chunk, sizeof(ResChunk_header),
+ endPos, "ResTable_package:unknown");
+ if (err != NO_ERROR) {
+ return (mError=err);
+ }
+ }
+ chunk = (const ResChunk_header*)
+ (((const uint8_t*)chunk) + csize);
+ }
+
+ if (group->typeCount == 0) {
+ group->typeCount = package->types.size();
+ }
+
+ return NO_ERROR;
+}
+
+status_t ResTable::createIdmap(const ResTable& overlay, uint32_t originalCrc, uint32_t overlayCrc,
+ void** outData, size_t* outSize) const
+{
+ // see README for details on the format of map
+ if (mPackageGroups.size() == 0) {
+ return UNKNOWN_ERROR;
+ }
+ if (mPackageGroups[0]->packages.size() == 0) {
+ return UNKNOWN_ERROR;
+ }
+
+ Vector<Vector<uint32_t> > map;
+ const PackageGroup* pg = mPackageGroups[0];
+ const Package* pkg = pg->packages[0];
+ size_t typeCount = pkg->types.size();
+ // starting size is header + first item (number of types in map)
+ *outSize = (IDMAP_HEADER_SIZE + 1) * sizeof(uint32_t);
+ const String16 overlayPackage(overlay.mPackageGroups[0]->packages[0]->package->name);
+ const uint32_t pkg_id = pkg->package->id << 24;
+
+ for (size_t typeIndex = 0; typeIndex < typeCount; ++typeIndex) {
+ ssize_t first = -1;
+ ssize_t last = -1;
+ const Type* typeConfigs = pkg->getType(typeIndex);
+ ssize_t mapIndex = map.add();
+ if (mapIndex < 0) {
+ return NO_MEMORY;
+ }
+ Vector<uint32_t>& vector = map.editItemAt(mapIndex);
+ for (size_t entryIndex = 0; entryIndex < typeConfigs->entryCount; ++entryIndex) {
+ uint32_t resID = pkg_id
+ | (0x00ff0000 & ((typeIndex+1)<<16))
+ | (0x0000ffff & (entryIndex));
+ resource_name resName;
+ if (!this->getResourceName(resID, true, &resName)) {
+ ALOGW("idmap: resource 0x%08x has spec but lacks values, skipping\n", resID);
+ // add dummy value, or trimming leading/trailing zeroes later will fail
+ vector.push(0);
+ continue;
+ }
+
+ const String16 overlayType(resName.type, resName.typeLen);
+ const String16 overlayName(resName.name, resName.nameLen);
+ uint32_t overlayResID = overlay.identifierForName(overlayName.string(),
+ overlayName.size(),
+ overlayType.string(),
+ overlayType.size(),
+ overlayPackage.string(),
+ overlayPackage.size());
+ if (overlayResID != 0) {
+ overlayResID = pkg_id | (0x00ffffff & overlayResID);
+ last = Res_GETENTRY(resID);
+ if (first == -1) {
+ first = Res_GETENTRY(resID);
+ }
+ }
+ vector.push(overlayResID);
+#if 0
+ if (overlayResID != 0) {
+ ALOGD("%s/%s 0x%08x -> 0x%08x\n",
+ String8(String16(resName.type)).string(),
+ String8(String16(resName.name)).string(),
+ resID, overlayResID);
+ }
+#endif
+ }
+
+ if (first != -1) {
+ // shave off trailing entries which lack overlay values
+ const size_t last_past_one = last + 1;
+ if (last_past_one < vector.size()) {
+ vector.removeItemsAt(last_past_one, vector.size() - last_past_one);
+ }
+ // shave off leading entries which lack overlay values
+ vector.removeItemsAt(0, first);
+ // store offset to first overlaid resource ID of this type
+ vector.insertAt((uint32_t)first, 0, 1);
+ // reserve space for number and offset of entries, and the actual entries
+ *outSize += (2 + vector.size()) * sizeof(uint32_t);
+ } else {
+ // no entries of current type defined in overlay package
+ vector.clear();
+ // reserve space for type offset
+ *outSize += 1 * sizeof(uint32_t);
+ }
+ }
+
+ if ((*outData = malloc(*outSize)) == NULL) {
+ return NO_MEMORY;
+ }
+ uint32_t* data = (uint32_t*)*outData;
+ *data++ = htodl(IDMAP_MAGIC);
+ *data++ = htodl(originalCrc);
+ *data++ = htodl(overlayCrc);
+ const size_t mapSize = map.size();
+ *data++ = htodl(mapSize);
+ size_t offset = mapSize;
+ for (size_t i = 0; i < mapSize; ++i) {
+ const Vector<uint32_t>& vector = map.itemAt(i);
+ const size_t N = vector.size();
+ if (N == 0) {
+ *data++ = htodl(0);
+ } else {
+ offset++;
+ *data++ = htodl(offset);
+ offset += N;
+ }
+ }
+ for (size_t i = 0; i < mapSize; ++i) {
+ const Vector<uint32_t>& vector = map.itemAt(i);
+ const size_t N = vector.size();
+ if (N == 0) {
+ continue;
+ }
+ if (N == 1) { // vector expected to hold (offset) + (N > 0 entries)
+ ALOGW("idmap: type %d supposedly has entries, but no entries found\n", i);
+ return UNKNOWN_ERROR;
+ }
+ *data++ = htodl(N - 1); // do not count the offset (which is vector's first element)
+ for (size_t j = 0; j < N; ++j) {
+ const uint32_t& overlayResID = vector.itemAt(j);
+ *data++ = htodl(overlayResID);
+ }
+ }
+
+ return NO_ERROR;
+}
+
+bool ResTable::getIdmapInfo(const void* idmap, size_t sizeBytes,
+ uint32_t* pOriginalCrc, uint32_t* pOverlayCrc)
+{
+ const uint32_t* map = (const uint32_t*)idmap;
+ if (!assertIdmapHeader(map, sizeBytes)) {
+ return false;
+ }
+ *pOriginalCrc = map[1];
+ *pOverlayCrc = map[2];
+ return true;
+}
+
+
+#define CHAR16_TO_CSTR(c16, len) (String8(String16(c16,len)).string())
+
+#define CHAR16_ARRAY_EQ(constant, var, len) \
+ ((len == (sizeof(constant)/sizeof(constant[0]))) && (0 == memcmp((var), (constant), (len))))
+
+static void print_complex(uint32_t complex, bool isFraction)
+{
+ const float MANTISSA_MULT =
+ 1.0f / (1<<Res_value::COMPLEX_MANTISSA_SHIFT);
+ const float RADIX_MULTS[] = {
+ 1.0f*MANTISSA_MULT, 1.0f/(1<<7)*MANTISSA_MULT,
+ 1.0f/(1<<15)*MANTISSA_MULT, 1.0f/(1<<23)*MANTISSA_MULT
+ };
+
+ float value = (complex&(Res_value::COMPLEX_MANTISSA_MASK
+ <<Res_value::COMPLEX_MANTISSA_SHIFT))
+ * RADIX_MULTS[(complex>>Res_value::COMPLEX_RADIX_SHIFT)
+ & Res_value::COMPLEX_RADIX_MASK];
+ printf("%f", value);
+
+ if (!isFraction) {
+ switch ((complex>>Res_value::COMPLEX_UNIT_SHIFT)&Res_value::COMPLEX_UNIT_MASK) {
+ case Res_value::COMPLEX_UNIT_PX: printf("px"); break;
+ case Res_value::COMPLEX_UNIT_DIP: printf("dp"); break;
+ case Res_value::COMPLEX_UNIT_SP: printf("sp"); break;
+ case Res_value::COMPLEX_UNIT_PT: printf("pt"); break;
+ case Res_value::COMPLEX_UNIT_IN: printf("in"); break;
+ case Res_value::COMPLEX_UNIT_MM: printf("mm"); break;
+ default: printf(" (unknown unit)"); break;
+ }
+ } else {
+ switch ((complex>>Res_value::COMPLEX_UNIT_SHIFT)&Res_value::COMPLEX_UNIT_MASK) {
+ case Res_value::COMPLEX_UNIT_FRACTION: printf("%%"); break;
+ case Res_value::COMPLEX_UNIT_FRACTION_PARENT: printf("%%p"); break;
+ default: printf(" (unknown unit)"); break;
+ }
+ }
+}
+
+// Normalize a string for output
+String8 ResTable::normalizeForOutput( const char *input )
+{
+ String8 ret;
+ char buff[2];
+ buff[1] = '\0';
+
+ while (*input != '\0') {
+ switch (*input) {
+ // All interesting characters are in the ASCII zone, so we are making our own lives
+ // easier by scanning the string one byte at a time.
+ case '\\':
+ ret += "\\\\";
+ break;
+ case '\n':
+ ret += "\\n";
+ break;
+ case '"':
+ ret += "\\\"";
+ break;
+ default:
+ buff[0] = *input;
+ ret += buff;
+ break;
+ }
+
+ input++;
+ }
+
+ return ret;
+}
+
+void ResTable::print_value(const Package* pkg, const Res_value& value) const
+{
+ if (value.dataType == Res_value::TYPE_NULL) {
+ printf("(null)\n");
+ } else if (value.dataType == Res_value::TYPE_REFERENCE) {
+ printf("(reference) 0x%08x\n", value.data);
+ } else if (value.dataType == Res_value::TYPE_ATTRIBUTE) {
+ printf("(attribute) 0x%08x\n", value.data);
+ } else if (value.dataType == Res_value::TYPE_STRING) {
+ size_t len;
+ const char* str8 = pkg->header->values.string8At(
+ value.data, &len);
+ if (str8 != NULL) {
+ printf("(string8) \"%s\"\n", normalizeForOutput(str8).string());
+ } else {
+ const char16_t* str16 = pkg->header->values.stringAt(
+ value.data, &len);
+ if (str16 != NULL) {
+ printf("(string16) \"%s\"\n",
+ normalizeForOutput(String8(str16, len).string()).string());
+ } else {
+ printf("(string) null\n");
+ }
+ }
+ } else if (value.dataType == Res_value::TYPE_FLOAT) {
+ printf("(float) %g\n", *(const float*)&value.data);
+ } else if (value.dataType == Res_value::TYPE_DIMENSION) {
+ printf("(dimension) ");
+ print_complex(value.data, false);
+ printf("\n");
+ } else if (value.dataType == Res_value::TYPE_FRACTION) {
+ printf("(fraction) ");
+ print_complex(value.data, true);
+ printf("\n");
+ } else if (value.dataType >= Res_value::TYPE_FIRST_COLOR_INT
+ || value.dataType <= Res_value::TYPE_LAST_COLOR_INT) {
+ printf("(color) #%08x\n", value.data);
+ } else if (value.dataType == Res_value::TYPE_INT_BOOLEAN) {
+ printf("(boolean) %s\n", value.data ? "true" : "false");
+ } else if (value.dataType >= Res_value::TYPE_FIRST_INT
+ || value.dataType <= Res_value::TYPE_LAST_INT) {
+ printf("(int) 0x%08x or %d\n", value.data, value.data);
+ } else {
+ printf("(unknown type) t=0x%02x d=0x%08x (s=0x%04x r=0x%02x)\n",
+ (int)value.dataType, (int)value.data,
+ (int)value.size, (int)value.res0);
+ }
+}
+
+void ResTable::print(bool inclValues) const
+{
+ if (mError != 0) {
+ printf("mError=0x%x (%s)\n", mError, strerror(mError));
+ }
+#if 0
+ printf("mParams=%c%c-%c%c,\n",
+ mParams.language[0], mParams.language[1],
+ mParams.country[0], mParams.country[1]);
+#endif
+ size_t pgCount = mPackageGroups.size();
+ printf("Package Groups (%d)\n", (int)pgCount);
+ for (size_t pgIndex=0; pgIndex<pgCount; pgIndex++) {
+ const PackageGroup* pg = mPackageGroups[pgIndex];
+ printf("Package Group %d id=%d packageCount=%d name=%s\n",
+ (int)pgIndex, pg->id, (int)pg->packages.size(),
+ String8(pg->name).string());
+
+ size_t pkgCount = pg->packages.size();
+ for (size_t pkgIndex=0; pkgIndex<pkgCount; pkgIndex++) {
+ const Package* pkg = pg->packages[pkgIndex];
+ size_t typeCount = pkg->types.size();
+ printf(" Package %d id=%d name=%s typeCount=%d\n", (int)pkgIndex,
+ pkg->package->id, String8(String16(pkg->package->name)).string(),
+ (int)typeCount);
+ for (size_t typeIndex=0; typeIndex<typeCount; typeIndex++) {
+ const Type* typeConfigs = pkg->getType(typeIndex);
+ if (typeConfigs == NULL) {
+ printf(" type %d NULL\n", (int)typeIndex);
+ continue;
+ }
+ const size_t NTC = typeConfigs->configs.size();
+ printf(" type %d configCount=%d entryCount=%d\n",
+ (int)typeIndex, (int)NTC, (int)typeConfigs->entryCount);
+ if (typeConfigs->typeSpecFlags != NULL) {
+ for (size_t entryIndex=0; entryIndex<typeConfigs->entryCount; entryIndex++) {
+ uint32_t resID = (0xff000000 & ((pkg->package->id)<<24))
+ | (0x00ff0000 & ((typeIndex+1)<<16))
+ | (0x0000ffff & (entryIndex));
+ resource_name resName;
+ if (this->getResourceName(resID, true, &resName)) {
+ String8 type8;
+ String8 name8;
+ if (resName.type8 != NULL) {
+ type8 = String8(resName.type8, resName.typeLen);
+ } else {
+ type8 = String8(resName.type, resName.typeLen);
+ }
+ if (resName.name8 != NULL) {
+ name8 = String8(resName.name8, resName.nameLen);
+ } else {
+ name8 = String8(resName.name, resName.nameLen);
+ }
+ printf(" spec resource 0x%08x %s:%s/%s: flags=0x%08x\n",
+ resID,
+ CHAR16_TO_CSTR(resName.package, resName.packageLen),
+ type8.string(), name8.string(),
+ dtohl(typeConfigs->typeSpecFlags[entryIndex]));
+ } else {
+ printf(" INVALID TYPE CONFIG FOR RESOURCE 0x%08x\n", resID);
+ }
+ }
+ }
+ for (size_t configIndex=0; configIndex<NTC; configIndex++) {
+ const ResTable_type* type = typeConfigs->configs[configIndex];
+ if ((((uint64_t)type)&0x3) != 0) {
+ printf(" NON-INTEGER ResTable_type ADDRESS: %p\n", type);
+ continue;
+ }
+ String8 configStr = type->config.toString();
+ printf(" config %s:\n", configStr.size() > 0
+ ? configStr.string() : "(default)");
+ size_t entryCount = dtohl(type->entryCount);
+ uint32_t entriesStart = dtohl(type->entriesStart);
+ if ((entriesStart&0x3) != 0) {
+ printf(" NON-INTEGER ResTable_type entriesStart OFFSET: %p\n", (void*)entriesStart);
+ continue;
+ }
+ uint32_t typeSize = dtohl(type->header.size);
+ if ((typeSize&0x3) != 0) {
+ printf(" NON-INTEGER ResTable_type header.size: %p\n", (void*)typeSize);
+ continue;
+ }
+ for (size_t entryIndex=0; entryIndex<entryCount; entryIndex++) {
+
+ const uint8_t* const end = ((const uint8_t*)type)
+ + dtohl(type->header.size);
+ const uint32_t* const eindex = (const uint32_t*)
+ (((const uint8_t*)type) + dtohs(type->header.headerSize));
+
+ uint32_t thisOffset = dtohl(eindex[entryIndex]);
+ if (thisOffset == ResTable_type::NO_ENTRY) {
+ continue;
+ }
+
+ uint32_t resID = (0xff000000 & ((pkg->package->id)<<24))
+ | (0x00ff0000 & ((typeIndex+1)<<16))
+ | (0x0000ffff & (entryIndex));
+ resource_name resName;
+ if (this->getResourceName(resID, true, &resName)) {
+ String8 type8;
+ String8 name8;
+ if (resName.type8 != NULL) {
+ type8 = String8(resName.type8, resName.typeLen);
+ } else {
+ type8 = String8(resName.type, resName.typeLen);
+ }
+ if (resName.name8 != NULL) {
+ name8 = String8(resName.name8, resName.nameLen);
+ } else {
+ name8 = String8(resName.name, resName.nameLen);
+ }
+ printf(" resource 0x%08x %s:%s/%s: ", resID,
+ CHAR16_TO_CSTR(resName.package, resName.packageLen),
+ type8.string(), name8.string());
+ } else {
+ printf(" INVALID RESOURCE 0x%08x: ", resID);
+ }
+ if ((thisOffset&0x3) != 0) {
+ printf("NON-INTEGER OFFSET: %p\n", (void*)thisOffset);
+ continue;
+ }
+ if ((thisOffset+sizeof(ResTable_entry)) > typeSize) {
+ printf("OFFSET OUT OF BOUNDS: %p+%p (size is %p)\n",
+ (void*)entriesStart, (void*)thisOffset,
+ (void*)typeSize);
+ continue;
+ }
+
+ const ResTable_entry* ent = (const ResTable_entry*)
+ (((const uint8_t*)type) + entriesStart + thisOffset);
+ if (((entriesStart + thisOffset)&0x3) != 0) {
+ printf("NON-INTEGER ResTable_entry OFFSET: %p\n",
+ (void*)(entriesStart + thisOffset));
+ continue;
+ }
+
+ uint16_t esize = dtohs(ent->size);
+ if ((esize&0x3) != 0) {
+ printf("NON-INTEGER ResTable_entry SIZE: %p\n", (void*)esize);
+ continue;
+ }
+ if ((thisOffset+esize) > typeSize) {
+ printf("ResTable_entry OUT OF BOUNDS: %p+%p+%p (size is %p)\n",
+ (void*)entriesStart, (void*)thisOffset,
+ (void*)esize, (void*)typeSize);
+ continue;
+ }
+
+ const Res_value* valuePtr = NULL;
+ const ResTable_map_entry* bagPtr = NULL;
+ Res_value value;
+ if ((dtohs(ent->flags)&ResTable_entry::FLAG_COMPLEX) != 0) {
+ printf("<bag>");
+ bagPtr = (const ResTable_map_entry*)ent;
+ } else {
+ valuePtr = (const Res_value*)
+ (((const uint8_t*)ent) + esize);
+ value.copyFrom_dtoh(*valuePtr);
+ printf("t=0x%02x d=0x%08x (s=0x%04x r=0x%02x)",
+ (int)value.dataType, (int)value.data,
+ (int)value.size, (int)value.res0);
+ }
+
+ if ((dtohs(ent->flags)&ResTable_entry::FLAG_PUBLIC) != 0) {
+ printf(" (PUBLIC)");
+ }
+ printf("\n");
+
+ if (inclValues) {
+ if (valuePtr != NULL) {
+ printf(" ");
+ print_value(pkg, value);
+ } else if (bagPtr != NULL) {
+ const int N = dtohl(bagPtr->count);
+ const uint8_t* baseMapPtr = (const uint8_t*)ent;
+ size_t mapOffset = esize;
+ const ResTable_map* mapPtr = (ResTable_map*)(baseMapPtr+mapOffset);
+ printf(" Parent=0x%08x, Count=%d\n",
+ dtohl(bagPtr->parent.ident), N);
+ for (int i=0; i<N && mapOffset < (typeSize-sizeof(ResTable_map)); i++) {
+ printf(" #%i (Key=0x%08x): ",
+ i, dtohl(mapPtr->name.ident));
+ value.copyFrom_dtoh(mapPtr->value);
+ print_value(pkg, value);
+ const size_t size = dtohs(mapPtr->value.size);
+ mapOffset += size + sizeof(*mapPtr)-sizeof(mapPtr->value);
+ mapPtr = (ResTable_map*)(baseMapPtr+mapOffset);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+} // namespace android
diff --git a/libs/androidfw/StreamingZipInflater.cpp b/libs/androidfw/StreamingZipInflater.cpp
new file mode 100644
index 0000000..1dfec23
--- /dev/null
+++ b/libs/androidfw/StreamingZipInflater.cpp
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "szipinf"
+#include <utils/Log.h>
+
+#include <androidfw/StreamingZipInflater.h>
+#include <utils/FileMap.h>
+#include <string.h>
+#include <stddef.h>
+#include <assert.h>
+#include <unistd.h>
+#include <errno.h>
+
+/*
+ * TEMP_FAILURE_RETRY is defined by some, but not all, versions of
+ * <unistd.h>. (Alas, it is not as standard as we'd hoped!) So, if it's
+ * not already defined, then define it here.
+ */
+#ifndef TEMP_FAILURE_RETRY
+/* Used to retry syscalls that can return EINTR. */
+#define TEMP_FAILURE_RETRY(exp) ({ \
+ typeof (exp) _rc; \
+ do { \
+ _rc = (exp); \
+ } while (_rc == -1 && errno == EINTR); \
+ _rc; })
+#endif
+
+static inline size_t min_of(size_t a, size_t b) { return (a < b) ? a : b; }
+
+using namespace android;
+
+/*
+ * Streaming access to compressed asset data in an open fd
+ */
+StreamingZipInflater::StreamingZipInflater(int fd, off64_t compDataStart,
+ size_t uncompSize, size_t compSize) {
+ mFd = fd;
+ mDataMap = NULL;
+ mInFileStart = compDataStart;
+ mOutTotalSize = uncompSize;
+ mInTotalSize = compSize;
+
+ mInBufSize = StreamingZipInflater::INPUT_CHUNK_SIZE;
+ mInBuf = new uint8_t[mInBufSize];
+
+ mOutBufSize = StreamingZipInflater::OUTPUT_CHUNK_SIZE;
+ mOutBuf = new uint8_t[mOutBufSize];
+
+ initInflateState();
+}
+
+/*
+ * Streaming access to compressed data held in an mmapped region of memory
+ */
+StreamingZipInflater::StreamingZipInflater(FileMap* dataMap, size_t uncompSize) {
+ mFd = -1;
+ mDataMap = dataMap;
+ mOutTotalSize = uncompSize;
+ mInTotalSize = dataMap->getDataLength();
+
+ mInBuf = (uint8_t*) dataMap->getDataPtr();
+ mInBufSize = mInTotalSize;
+
+ mOutBufSize = StreamingZipInflater::OUTPUT_CHUNK_SIZE;
+ mOutBuf = new uint8_t[mOutBufSize];
+
+ initInflateState();
+}
+
+StreamingZipInflater::~StreamingZipInflater() {
+ // tear down the in-flight zip state just in case
+ ::inflateEnd(&mInflateState);
+
+ if (mDataMap == NULL) {
+ delete [] mInBuf;
+ }
+ delete [] mOutBuf;
+}
+
+void StreamingZipInflater::initInflateState() {
+ ALOGV("Initializing inflate state");
+
+ memset(&mInflateState, 0, sizeof(mInflateState));
+ mInflateState.zalloc = Z_NULL;
+ mInflateState.zfree = Z_NULL;
+ mInflateState.opaque = Z_NULL;
+ mInflateState.next_in = (Bytef*)mInBuf;
+ mInflateState.next_out = (Bytef*) mOutBuf;
+ mInflateState.avail_out = mOutBufSize;
+ mInflateState.data_type = Z_UNKNOWN;
+
+ mOutLastDecoded = mOutDeliverable = mOutCurPosition = 0;
+ mInNextChunkOffset = 0;
+ mStreamNeedsInit = true;
+
+ if (mDataMap == NULL) {
+ ::lseek(mFd, mInFileStart, SEEK_SET);
+ mInflateState.avail_in = 0; // set when a chunk is read in
+ } else {
+ mInflateState.avail_in = mInBufSize;
+ }
+}
+
+/*
+ * Basic approach:
+ *
+ * 1. If we have undelivered uncompressed data, send it. At this point
+ * either we've satisfied the request, or we've exhausted the available
+ * output data in mOutBuf.
+ *
+ * 2. While we haven't sent enough data to satisfy the request:
+ * 0. if the request is for more data than exists, bail.
+ * a. if there is no input data to decode, read some into the input buffer
+ * and readjust the z_stream input pointers
+ * b. point the output to the start of the output buffer and decode what we can
+ * c. deliver whatever output data we can
+ */
+ssize_t StreamingZipInflater::read(void* outBuf, size_t count) {
+ uint8_t* dest = (uint8_t*) outBuf;
+ size_t bytesRead = 0;
+ size_t toRead = min_of(count, size_t(mOutTotalSize - mOutCurPosition));
+ while (toRead > 0) {
+ // First, write from whatever we already have decoded and ready to go
+ size_t deliverable = min_of(toRead, mOutLastDecoded - mOutDeliverable);
+ if (deliverable > 0) {
+ if (outBuf != NULL) memcpy(dest, mOutBuf + mOutDeliverable, deliverable);
+ mOutDeliverable += deliverable;
+ mOutCurPosition += deliverable;
+ dest += deliverable;
+ bytesRead += deliverable;
+ toRead -= deliverable;
+ }
+
+ // need more data? time to decode some.
+ if (toRead > 0) {
+ // if we don't have any data to decode, read some in. If we're working
+ // from mmapped data this won't happen, because the clipping to total size
+ // will prevent reading off the end of the mapped input chunk.
+ if ((mInflateState.avail_in == 0) && (mDataMap == NULL)) {
+ int err = readNextChunk();
+ if (err < 0) {
+ ALOGE("Unable to access asset data: %d", err);
+ if (!mStreamNeedsInit) {
+ ::inflateEnd(&mInflateState);
+ initInflateState();
+ }
+ return -1;
+ }
+ }
+ // we know we've drained whatever is in the out buffer now, so just
+ // start from scratch there, reading all the input we have at present.
+ mInflateState.next_out = (Bytef*) mOutBuf;
+ mInflateState.avail_out = mOutBufSize;
+
+ /*
+ ALOGV("Inflating to outbuf: avail_in=%u avail_out=%u next_in=%p next_out=%p",
+ mInflateState.avail_in, mInflateState.avail_out,
+ mInflateState.next_in, mInflateState.next_out);
+ */
+ int result = Z_OK;
+ if (mStreamNeedsInit) {
+ ALOGV("Initializing zlib to inflate");
+ result = inflateInit2(&mInflateState, -MAX_WBITS);
+ mStreamNeedsInit = false;
+ }
+ if (result == Z_OK) result = ::inflate(&mInflateState, Z_SYNC_FLUSH);
+ if (result < 0) {
+ // Whoops, inflation failed
+ ALOGE("Error inflating asset: %d", result);
+ ::inflateEnd(&mInflateState);
+ initInflateState();
+ return -1;
+ } else {
+ if (result == Z_STREAM_END) {
+ // we know we have to have reached the target size here and will
+ // not try to read any further, so just wind things up.
+ ::inflateEnd(&mInflateState);
+ }
+
+ // Note how much data we got, and off we go
+ mOutDeliverable = 0;
+ mOutLastDecoded = mOutBufSize - mInflateState.avail_out;
+ }
+ }
+ }
+ return bytesRead;
+}
+
+int StreamingZipInflater::readNextChunk() {
+ assert(mDataMap == NULL);
+
+ if (mInNextChunkOffset < mInTotalSize) {
+ size_t toRead = min_of(mInBufSize, mInTotalSize - mInNextChunkOffset);
+ if (toRead > 0) {
+ ssize_t didRead = TEMP_FAILURE_RETRY(::read(mFd, mInBuf, toRead));
+ //ALOGV("Reading input chunk, size %08x didread %08x", toRead, didRead);
+ if (didRead < 0) {
+ ALOGE("Error reading asset data: %s", strerror(errno));
+ return didRead;
+ } else {
+ mInNextChunkOffset += didRead;
+ mInflateState.next_in = (Bytef*) mInBuf;
+ mInflateState.avail_in = didRead;
+ }
+ }
+ }
+ return 0;
+}
+
+// seeking backwards requires uncompressing fom the beginning, so is very
+// expensive. seeking forwards only requires uncompressing from the current
+// position to the destination.
+off64_t StreamingZipInflater::seekAbsolute(off64_t absoluteInputPosition) {
+ if (absoluteInputPosition < mOutCurPosition) {
+ // rewind and reprocess the data from the beginning
+ if (!mStreamNeedsInit) {
+ ::inflateEnd(&mInflateState);
+ }
+ initInflateState();
+ read(NULL, absoluteInputPosition);
+ } else if (absoluteInputPosition > mOutCurPosition) {
+ read(NULL, absoluteInputPosition - mOutCurPosition);
+ }
+ // else if the target position *is* our current position, do nothing
+ return absoluteInputPosition;
+}
diff --git a/libs/androidfw/ZipFileRO.cpp b/libs/androidfw/ZipFileRO.cpp
new file mode 100644
index 0000000..ec5f95c
--- /dev/null
+++ b/libs/androidfw/ZipFileRO.cpp
@@ -0,0 +1,995 @@
+/*
+ * Copyright (C) 2007 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.
+ */
+
+//
+// Read-only access to Zip archives, with minimal heap allocation.
+//
+#define LOG_TAG "zipro"
+//#define LOG_NDEBUG 0
+#include <androidfw/ZipFileRO.h>
+#include <utils/Log.h>
+#include <utils/Compat.h>
+#include <utils/misc.h>
+#include <utils/threads.h>
+
+#include <zlib.h>
+
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <assert.h>
+#include <unistd.h>
+
+/*
+ * We must open binary files using open(path, ... | O_BINARY) under Windows.
+ * Otherwise strange read errors will happen.
+ */
+#ifndef O_BINARY
+# define O_BINARY 0
+#endif
+
+using namespace android;
+
+/*
+ * Zip file constants.
+ */
+#define kEOCDSignature 0x06054b50
+#define kEOCDLen 22
+#define kEOCDDiskNumber 4 // number of the current disk
+#define kEOCDDiskNumberForCD 6 // disk number with the Central Directory
+#define kEOCDNumEntries 8 // offset to #of entries in file
+#define kEOCDTotalNumEntries 10 // offset to total #of entries in spanned archives
+#define kEOCDSize 12 // size of the central directory
+#define kEOCDFileOffset 16 // offset to central directory
+#define kEOCDCommentSize 20 // offset to the length of the file comment
+
+#define kMaxCommentLen 65535 // longest possible in ushort
+#define kMaxEOCDSearch (kMaxCommentLen + kEOCDLen)
+
+#define kLFHSignature 0x04034b50
+#define kLFHLen 30 // excluding variable-len fields
+#define kLFHGPBFlags 6 // offset to GPB flags
+#define kLFHNameLen 26 // offset to filename length
+#define kLFHExtraLen 28 // offset to extra length
+
+#define kCDESignature 0x02014b50
+#define kCDELen 46 // excluding variable-len fields
+#define kCDEGPBFlags 8 // offset to GPB flags
+#define kCDEMethod 10 // offset to compression method
+#define kCDEModWhen 12 // offset to modification timestamp
+#define kCDECRC 16 // offset to entry CRC
+#define kCDECompLen 20 // offset to compressed length
+#define kCDEUncompLen 24 // offset to uncompressed length
+#define kCDENameLen 28 // offset to filename length
+#define kCDEExtraLen 30 // offset to extra length
+#define kCDECommentLen 32 // offset to comment length
+#define kCDELocalOffset 42 // offset to local hdr
+
+/* General Purpose Bit Flag */
+#define kGPFEncryptedFlag (1 << 0)
+#define kGPFUnsupportedMask (kGPFEncryptedFlag)
+
+/*
+ * The values we return for ZipEntryRO use 0 as an invalid value, so we
+ * want to adjust the hash table index by a fixed amount. Using a large
+ * value helps insure that people don't mix & match arguments, e.g. to
+ * findEntryByIndex().
+ */
+#define kZipEntryAdj 10000
+
+ZipFileRO::~ZipFileRO() {
+ free(mHashTable);
+ if (mDirectoryMap)
+ mDirectoryMap->release();
+ if (mFd >= 0)
+ TEMP_FAILURE_RETRY(close(mFd));
+ if (mFileName)
+ free(mFileName);
+}
+
+/*
+ * Convert a ZipEntryRO to a hash table index, verifying that it's in a
+ * valid range.
+ */
+int ZipFileRO::entryToIndex(const ZipEntryRO entry) const
+{
+ long ent = ((intptr_t) entry) - kZipEntryAdj;
+ if (ent < 0 || ent >= mHashTableSize || mHashTable[ent].name == NULL) {
+ ALOGW("Invalid ZipEntryRO %p (%ld)\n", entry, ent);
+ return -1;
+ }
+ return ent;
+}
+
+
+/*
+ * Open the specified file read-only. We memory-map the entire thing and
+ * close the file before returning.
+ */
+status_t ZipFileRO::open(const char* zipFileName)
+{
+ int fd = -1;
+
+ assert(mDirectoryMap == NULL);
+
+ /*
+ * Open and map the specified file.
+ */
+ fd = TEMP_FAILURE_RETRY(::open(zipFileName, O_RDONLY | O_BINARY));
+ if (fd < 0) {
+ ALOGW("Unable to open zip '%s': %s\n", zipFileName, strerror(errno));
+ return NAME_NOT_FOUND;
+ }
+
+ mFileLength = lseek64(fd, 0, SEEK_END);
+ if (mFileLength < kEOCDLen) {
+ TEMP_FAILURE_RETRY(close(fd));
+ return UNKNOWN_ERROR;
+ }
+
+ if (mFileName != NULL) {
+ free(mFileName);
+ }
+ mFileName = strdup(zipFileName);
+
+ mFd = fd;
+
+ /*
+ * Find the Central Directory and store its size and number of entries.
+ */
+ if (!mapCentralDirectory()) {
+ goto bail;
+ }
+
+ /*
+ * Verify Central Directory and create data structures for fast access.
+ */
+ if (!parseZipArchive()) {
+ goto bail;
+ }
+
+ return OK;
+
+bail:
+ free(mFileName);
+ mFileName = NULL;
+ TEMP_FAILURE_RETRY(close(fd));
+ return UNKNOWN_ERROR;
+}
+
+/*
+ * Parse the Zip archive, verifying its contents and initializing internal
+ * data structures.
+ */
+bool ZipFileRO::mapCentralDirectory(void)
+{
+ ssize_t readAmount = kMaxEOCDSearch;
+ if (readAmount > (ssize_t) mFileLength)
+ readAmount = mFileLength;
+
+ if (readAmount < kEOCDSize) {
+ ALOGW("File too short to be a zip file");
+ return false;
+ }
+
+ unsigned char* scanBuf = (unsigned char*) malloc(readAmount);
+ if (scanBuf == NULL) {
+ ALOGW("couldn't allocate scanBuf: %s", strerror(errno));
+ free(scanBuf);
+ return false;
+ }
+
+ /*
+ * Make sure this is a Zip archive.
+ */
+ if (lseek64(mFd, 0, SEEK_SET) != 0) {
+ ALOGW("seek to start failed: %s", strerror(errno));
+ free(scanBuf);
+ return false;
+ }
+
+ ssize_t actual = TEMP_FAILURE_RETRY(read(mFd, scanBuf, sizeof(int32_t)));
+ if (actual != (ssize_t) sizeof(int32_t)) {
+ ALOGI("couldn't read first signature from zip archive: %s", strerror(errno));
+ free(scanBuf);
+ return false;
+ }
+
+ unsigned int header = get4LE(scanBuf);
+ if (header != kLFHSignature) {
+ ALOGV("Not a Zip archive (found 0x%08x)\n", header);
+ free(scanBuf);
+ return false;
+ }
+
+ /*
+ * Perform the traditional EOCD snipe hunt.
+ *
+ * We're searching for the End of Central Directory magic number,
+ * which appears at the start of the EOCD block. It's followed by
+ * 18 bytes of EOCD stuff and up to 64KB of archive comment. We
+ * need to read the last part of the file into a buffer, dig through
+ * it to find the magic number, parse some values out, and use those
+ * to determine the extent of the CD.
+ *
+ * We start by pulling in the last part of the file.
+ */
+ off64_t searchStart = mFileLength - readAmount;
+
+ if (lseek64(mFd, searchStart, SEEK_SET) != searchStart) {
+ ALOGW("seek %ld failed: %s\n", (long) searchStart, strerror(errno));
+ free(scanBuf);
+ return false;
+ }
+ actual = TEMP_FAILURE_RETRY(read(mFd, scanBuf, readAmount));
+ if (actual != (ssize_t) readAmount) {
+ ALOGW("Zip: read " ZD ", expected " ZD ". Failed: %s\n",
+ (ZD_TYPE) actual, (ZD_TYPE) readAmount, strerror(errno));
+ free(scanBuf);
+ return false;
+ }
+
+ /*
+ * Scan backward for the EOCD magic. In an archive without a trailing
+ * comment, we'll find it on the first try. (We may want to consider
+ * doing an initial minimal read; if we don't find it, retry with a
+ * second read as above.)
+ */
+ int i;
+ for (i = readAmount - kEOCDLen; i >= 0; i--) {
+ if (scanBuf[i] == 0x50 && get4LE(&scanBuf[i]) == kEOCDSignature) {
+ ALOGV("+++ Found EOCD at buf+%d\n", i);
+ break;
+ }
+ }
+ if (i < 0) {
+ ALOGD("Zip: EOCD not found, %s is not zip\n", mFileName);
+ free(scanBuf);
+ return false;
+ }
+
+ off64_t eocdOffset = searchStart + i;
+ const unsigned char* eocdPtr = scanBuf + i;
+
+ assert(eocdOffset < mFileLength);
+
+ /*
+ * Grab the CD offset and size, and the number of entries in the
+ * archive. After that, we can release our EOCD hunt buffer.
+ */
+ unsigned int diskNumber = get2LE(eocdPtr + kEOCDDiskNumber);
+ unsigned int diskWithCentralDir = get2LE(eocdPtr + kEOCDDiskNumberForCD);
+ unsigned int numEntries = get2LE(eocdPtr + kEOCDNumEntries);
+ unsigned int totalNumEntries = get2LE(eocdPtr + kEOCDTotalNumEntries);
+ unsigned int centralDirSize = get4LE(eocdPtr + kEOCDSize);
+ unsigned int centralDirOffset = get4LE(eocdPtr + kEOCDFileOffset);
+ unsigned int commentSize = get2LE(eocdPtr + kEOCDCommentSize);
+ free(scanBuf);
+
+ // Verify that they look reasonable.
+ if ((long long) centralDirOffset + (long long) centralDirSize > (long long) eocdOffset) {
+ ALOGW("bad offsets (dir %ld, size %u, eocd %ld)\n",
+ (long) centralDirOffset, centralDirSize, (long) eocdOffset);
+ return false;
+ }
+ if (numEntries == 0) {
+ ALOGW("empty archive?\n");
+ return false;
+ } else if (numEntries != totalNumEntries || diskNumber != 0 || diskWithCentralDir != 0) {
+ ALOGW("spanned archives not supported");
+ return false;
+ }
+
+ // Check to see if comment is a sane size
+ if ((commentSize > (mFileLength - kEOCDLen))
+ || (eocdOffset > (mFileLength - kEOCDLen) - commentSize)) {
+ ALOGW("comment size runs off end of file");
+ return false;
+ }
+
+ ALOGV("+++ numEntries=%d dirSize=%d dirOffset=%d\n",
+ numEntries, centralDirSize, centralDirOffset);
+
+ mDirectoryMap = new FileMap();
+ if (mDirectoryMap == NULL) {
+ ALOGW("Unable to create directory map: %s", strerror(errno));
+ return false;
+ }
+
+ if (!mDirectoryMap->create(mFileName, mFd, centralDirOffset, centralDirSize, true)) {
+ ALOGW("Unable to map '%s' (" ZD " to " ZD "): %s\n", mFileName,
+ (ZD_TYPE) centralDirOffset, (ZD_TYPE) (centralDirOffset + centralDirSize), strerror(errno));
+ return false;
+ }
+
+ mNumEntries = numEntries;
+ mDirectoryOffset = centralDirOffset;
+
+ return true;
+}
+
+
+/*
+ * Round up to the next highest power of 2.
+ *
+ * Found on http://graphics.stanford.edu/~seander/bithacks.html.
+ */
+static unsigned int roundUpPower2(unsigned int val)
+{
+ val--;
+ val |= val >> 1;
+ val |= val >> 2;
+ val |= val >> 4;
+ val |= val >> 8;
+ val |= val >> 16;
+ val++;
+
+ return val;
+}
+
+bool ZipFileRO::parseZipArchive(void)
+{
+ bool result = false;
+ const unsigned char* cdPtr = (const unsigned char*) mDirectoryMap->getDataPtr();
+ size_t cdLength = mDirectoryMap->getDataLength();
+ int numEntries = mNumEntries;
+
+ /*
+ * Create hash table. We have a minimum 75% load factor, possibly as
+ * low as 50% after we round off to a power of 2.
+ */
+ mHashTableSize = roundUpPower2(1 + (numEntries * 4) / 3);
+ mHashTable = (HashEntry*) calloc(mHashTableSize, sizeof(HashEntry));
+
+ /*
+ * Walk through the central directory, adding entries to the hash
+ * table.
+ */
+ const unsigned char* ptr = cdPtr;
+ for (int i = 0; i < numEntries; i++) {
+ if (get4LE(ptr) != kCDESignature) {
+ ALOGW("Missed a central dir sig (at %d)\n", i);
+ goto bail;
+ }
+ if (ptr + kCDELen > cdPtr + cdLength) {
+ ALOGW("Ran off the end (at %d)\n", i);
+ goto bail;
+ }
+
+ long localHdrOffset = (long) get4LE(ptr + kCDELocalOffset);
+ if (localHdrOffset >= mDirectoryOffset) {
+ ALOGW("bad LFH offset %ld at entry %d\n", localHdrOffset, i);
+ goto bail;
+ }
+
+ unsigned int gpbf = get2LE(ptr + kCDEGPBFlags);
+ if ((gpbf & kGPFUnsupportedMask) != 0) {
+ ALOGW("Invalid General Purpose Bit Flag: %d", gpbf);
+ goto bail;
+ }
+
+ unsigned int nameLen = get2LE(ptr + kCDENameLen);
+ unsigned int extraLen = get2LE(ptr + kCDEExtraLen);
+ unsigned int commentLen = get2LE(ptr + kCDECommentLen);
+
+ const char *name = (const char *) ptr + kCDELen;
+
+ /* Check name for NULL characters */
+ if (memchr(name, 0, nameLen) != NULL) {
+ ALOGW("Filename contains NUL byte");
+ goto bail;
+ }
+
+ /* add the CDE filename to the hash table */
+ unsigned int hash = computeHash(name, nameLen);
+ addToHash(name, nameLen, hash);
+
+ /* We don't care about the comment or extra data. */
+ ptr += kCDELen + nameLen + extraLen + commentLen;
+ if ((size_t)(ptr - cdPtr) > cdLength) {
+ ALOGW("bad CD advance (%d vs " ZD ") at entry %d\n",
+ (int) (ptr - cdPtr), (ZD_TYPE) cdLength, i);
+ goto bail;
+ }
+ }
+ ALOGV("+++ zip good scan %d entries\n", numEntries);
+ result = true;
+
+bail:
+ return result;
+}
+
+/*
+ * Simple string hash function for non-null-terminated strings.
+ */
+/*static*/ unsigned int ZipFileRO::computeHash(const char* str, int len)
+{
+ unsigned int hash = 0;
+
+ while (len--)
+ hash = hash * 31 + *str++;
+
+ return hash;
+}
+
+/*
+ * Add a new entry to the hash table.
+ */
+void ZipFileRO::addToHash(const char* str, int strLen, unsigned int hash)
+{
+ int ent = hash & (mHashTableSize-1);
+
+ /*
+ * We over-allocate the table, so we're guaranteed to find an empty slot.
+ */
+ while (mHashTable[ent].name != NULL)
+ ent = (ent + 1) & (mHashTableSize-1);
+
+ mHashTable[ent].name = str;
+ mHashTable[ent].nameLen = strLen;
+}
+
+/*
+ * Find a matching entry.
+ *
+ * Returns NULL if not found.
+ */
+ZipEntryRO ZipFileRO::findEntryByName(const char* fileName) const
+{
+ /*
+ * If the ZipFileRO instance is not initialized, the entry number will
+ * end up being garbage since mHashTableSize is -1.
+ */
+ if (mHashTableSize <= 0) {
+ return NULL;
+ }
+
+ int nameLen = strlen(fileName);
+ unsigned int hash = computeHash(fileName, nameLen);
+ int ent = hash & (mHashTableSize-1);
+
+ while (mHashTable[ent].name != NULL) {
+ if (mHashTable[ent].nameLen == nameLen &&
+ memcmp(mHashTable[ent].name, fileName, nameLen) == 0)
+ {
+ /* match */
+ return (ZipEntryRO)(long)(ent + kZipEntryAdj);
+ }
+
+ ent = (ent + 1) & (mHashTableSize-1);
+ }
+
+ return NULL;
+}
+
+/*
+ * Find the Nth entry.
+ *
+ * This currently involves walking through the sparse hash table, counting
+ * non-empty entries. If we need to speed this up we can either allocate
+ * a parallel lookup table or (perhaps better) provide an iterator interface.
+ */
+ZipEntryRO ZipFileRO::findEntryByIndex(int idx) const
+{
+ if (idx < 0 || idx >= mNumEntries) {
+ ALOGW("Invalid index %d\n", idx);
+ return NULL;
+ }
+
+ for (int ent = 0; ent < mHashTableSize; ent++) {
+ if (mHashTable[ent].name != NULL) {
+ if (idx-- == 0)
+ return (ZipEntryRO) (intptr_t)(ent + kZipEntryAdj);
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * Get the useful fields from the zip entry.
+ *
+ * Returns "false" if the offsets to the fields or the contents of the fields
+ * appear to be bogus.
+ */
+bool ZipFileRO::getEntryInfo(ZipEntryRO entry, int* pMethod, size_t* pUncompLen,
+ size_t* pCompLen, off64_t* pOffset, long* pModWhen, long* pCrc32) const
+{
+ bool ret = false;
+
+ const int ent = entryToIndex(entry);
+ if (ent < 0) {
+ ALOGW("cannot find entry");
+ return false;
+ }
+
+ HashEntry hashEntry = mHashTable[ent];
+
+ /*
+ * Recover the start of the central directory entry from the filename
+ * pointer. The filename is the first entry past the fixed-size data,
+ * so we can just subtract back from that.
+ */
+ const unsigned char* ptr = (const unsigned char*) hashEntry.name;
+ off64_t cdOffset = mDirectoryOffset;
+
+ ptr -= kCDELen;
+
+ int method = get2LE(ptr + kCDEMethod);
+ if (pMethod != NULL)
+ *pMethod = method;
+
+ if (pModWhen != NULL)
+ *pModWhen = get4LE(ptr + kCDEModWhen);
+ if (pCrc32 != NULL)
+ *pCrc32 = get4LE(ptr + kCDECRC);
+
+ size_t compLen = get4LE(ptr + kCDECompLen);
+ if (pCompLen != NULL)
+ *pCompLen = compLen;
+ size_t uncompLen = get4LE(ptr + kCDEUncompLen);
+ if (pUncompLen != NULL)
+ *pUncompLen = uncompLen;
+
+ /*
+ * If requested, determine the offset of the start of the data. All we
+ * have is the offset to the Local File Header, which is variable size,
+ * so we have to read the contents of the struct to figure out where
+ * the actual data starts.
+ *
+ * We also need to make sure that the lengths are not so large that
+ * somebody trying to map the compressed or uncompressed data runs
+ * off the end of the mapped region.
+ *
+ * Note we don't verify compLen/uncompLen if they don't request the
+ * dataOffset, because dataOffset is expensive to determine. However,
+ * if they don't have the file offset, they're not likely to be doing
+ * anything with the contents.
+ */
+ if (pOffset != NULL) {
+ long localHdrOffset = get4LE(ptr + kCDELocalOffset);
+ if (localHdrOffset + kLFHLen >= cdOffset) {
+ ALOGE("ERROR: bad local hdr offset in zip\n");
+ return false;
+ }
+
+ unsigned char lfhBuf[kLFHLen];
+
+#ifdef HAVE_PREAD
+ /*
+ * This file descriptor might be from zygote's preloaded assets,
+ * so we need to do an pread64() instead of a lseek64() + read() to
+ * guarantee atomicity across the processes with the shared file
+ * descriptors.
+ */
+ ssize_t actual =
+ TEMP_FAILURE_RETRY(pread64(mFd, lfhBuf, sizeof(lfhBuf), localHdrOffset));
+
+ if (actual != sizeof(lfhBuf)) {
+ ALOGW("failed reading lfh from offset %ld\n", localHdrOffset);
+ return false;
+ }
+
+ if (get4LE(lfhBuf) != kLFHSignature) {
+ ALOGW("didn't find signature at start of lfh; wanted: offset=%ld data=0x%08x; "
+ "got: data=0x%08lx\n",
+ localHdrOffset, kLFHSignature, get4LE(lfhBuf));
+ return false;
+ }
+#else /* HAVE_PREAD */
+ /*
+ * For hosts don't have pread64() we cannot guarantee atomic reads from
+ * an offset in a file. Android should never run on those platforms.
+ * File descriptors inherited from a fork() share file offsets and
+ * there would be nothing to protect from two different processes
+ * calling lseek64() concurrently.
+ */
+
+ {
+ AutoMutex _l(mFdLock);
+
+ if (lseek64(mFd, localHdrOffset, SEEK_SET) != localHdrOffset) {
+ ALOGW("failed seeking to lfh at offset %ld\n", localHdrOffset);
+ return false;
+ }
+
+ ssize_t actual =
+ TEMP_FAILURE_RETRY(read(mFd, lfhBuf, sizeof(lfhBuf)));
+ if (actual != sizeof(lfhBuf)) {
+ ALOGW("failed reading lfh from offset %ld\n", localHdrOffset);
+ return false;
+ }
+
+ if (get4LE(lfhBuf) != kLFHSignature) {
+ off64_t actualOffset = lseek64(mFd, 0, SEEK_CUR);
+ ALOGW("didn't find signature at start of lfh; wanted: offset=%ld data=0x%08x; "
+ "got: offset=" ZD " data=0x%08lx\n",
+ localHdrOffset, kLFHSignature, (ZD_TYPE) actualOffset, get4LE(lfhBuf));
+ return false;
+ }
+ }
+#endif /* HAVE_PREAD */
+
+ unsigned int gpbf = get2LE(lfhBuf + kLFHGPBFlags);
+ if ((gpbf & kGPFUnsupportedMask) != 0) {
+ ALOGW("Invalid General Purpose Bit Flag: %d", gpbf);
+ return false;
+ }
+
+ off64_t dataOffset = localHdrOffset + kLFHLen
+ + get2LE(lfhBuf + kLFHNameLen) + get2LE(lfhBuf + kLFHExtraLen);
+ if (dataOffset >= cdOffset) {
+ ALOGW("bad data offset %ld in zip\n", (long) dataOffset);
+ return false;
+ }
+
+ /* check lengths */
+ if ((dataOffset >= cdOffset) || (compLen > (cdOffset - dataOffset))) {
+ ALOGW("bad compressed length in zip (%ld + " ZD " > %ld)\n",
+ (long) dataOffset, (ZD_TYPE) compLen, (long) cdOffset);
+ return false;
+ }
+
+ if (method == kCompressStored &&
+ ((dataOffset >= cdOffset) ||
+ (uncompLen > (cdOffset - dataOffset))))
+ {
+ ALOGE("ERROR: bad uncompressed length in zip (%ld + " ZD " > %ld)\n",
+ (long) dataOffset, (ZD_TYPE) uncompLen, (long) cdOffset);
+ return false;
+ }
+
+ *pOffset = dataOffset;
+ }
+
+ return true;
+}
+
+/*
+ * Copy the entry's filename to the buffer.
+ */
+int ZipFileRO::getEntryFileName(ZipEntryRO entry, char* buffer, int bufLen)
+ const
+{
+ int ent = entryToIndex(entry);
+ if (ent < 0)
+ return -1;
+
+ int nameLen = mHashTable[ent].nameLen;
+ if (bufLen < nameLen+1)
+ return nameLen+1;
+
+ memcpy(buffer, mHashTable[ent].name, nameLen);
+ buffer[nameLen] = '\0';
+ return 0;
+}
+
+/*
+ * Create a new FileMap object that spans the data in "entry".
+ */
+FileMap* ZipFileRO::createEntryFileMap(ZipEntryRO entry) const
+{
+ /*
+ * TODO: the efficient way to do this is to modify FileMap to allow
+ * sub-regions of a file to be mapped. A reference-counting scheme
+ * can manage the base memory mapping. For now, we just create a brand
+ * new mapping off of the Zip archive file descriptor.
+ */
+
+ FileMap* newMap;
+ int method;
+ size_t uncompLen;
+ size_t compLen;
+ off64_t offset;
+
+ if (!getEntryInfo(entry, &method, &uncompLen, &compLen, &offset, NULL, NULL)) {
+ return NULL;
+ }
+
+ size_t actualLen;
+ if (method == kCompressStored) {
+ actualLen = uncompLen;
+ } else {
+ actualLen = compLen;
+ }
+
+ newMap = new FileMap();
+ if (!newMap->create(mFileName, mFd, offset, actualLen, true)) {
+ newMap->release();
+ return NULL;
+ }
+
+ return newMap;
+}
+
+/*
+ * Uncompress an entry, in its entirety, into the provided output buffer.
+ *
+ * This doesn't verify the data's CRC, which might be useful for
+ * uncompressed data. The caller should be able to manage it.
+ */
+bool ZipFileRO::uncompressEntry(ZipEntryRO entry, void* buffer) const
+{
+ const size_t kSequentialMin = 32768;
+ bool result = false;
+ int ent = entryToIndex(entry);
+ if (ent < 0) {
+ return false;
+ }
+
+ int method;
+ size_t uncompLen, compLen;
+ off64_t offset;
+ const unsigned char* ptr;
+ FileMap *file;
+
+ if (!getEntryInfo(entry, &method, &uncompLen, &compLen, &offset, NULL, NULL)) {
+ goto bail;
+ }
+
+ file = createEntryFileMap(entry);
+ if (file == NULL) {
+ goto bail;
+ }
+
+ ptr = (const unsigned char*) file->getDataPtr();
+
+ /*
+ * Experiment with madvise hint. When we want to uncompress a file,
+ * we pull some stuff out of the central dir entry and then hit a
+ * bunch of compressed or uncompressed data sequentially. The CDE
+ * visit will cause a limited amount of read-ahead because it's at
+ * the end of the file. We could end up doing lots of extra disk
+ * access if the file we're prying open is small. Bottom line is we
+ * probably don't want to turn MADV_SEQUENTIAL on and leave it on.
+ *
+ * So, if the compressed size of the file is above a certain minimum
+ * size, temporarily boost the read-ahead in the hope that the extra
+ * pair of system calls are negated by a reduction in page faults.
+ */
+ if (compLen > kSequentialMin)
+ file->advise(FileMap::SEQUENTIAL);
+
+ if (method == kCompressStored) {
+ memcpy(buffer, ptr, uncompLen);
+ } else {
+ if (!inflateBuffer(buffer, ptr, uncompLen, compLen))
+ goto unmap;
+ }
+
+ if (compLen > kSequentialMin)
+ file->advise(FileMap::NORMAL);
+
+ result = true;
+
+unmap:
+ file->release();
+bail:
+ return result;
+}
+
+/*
+ * Uncompress an entry, in its entirety, to an open file descriptor.
+ *
+ * This doesn't verify the data's CRC, but probably should.
+ */
+bool ZipFileRO::uncompressEntry(ZipEntryRO entry, int fd) const
+{
+ bool result = false;
+ int ent = entryToIndex(entry);
+ if (ent < 0) {
+ return false;
+ }
+
+ int method;
+ size_t uncompLen, compLen;
+ off64_t offset;
+ const unsigned char* ptr;
+ FileMap *file;
+
+ if (!getEntryInfo(entry, &method, &uncompLen, &compLen, &offset, NULL, NULL)) {
+ goto bail;
+ }
+
+ file = createEntryFileMap(entry);
+ if (file == NULL) {
+ goto bail;
+ }
+
+ ptr = (const unsigned char*) file->getDataPtr();
+
+ if (method == kCompressStored) {
+ ssize_t actual = TEMP_FAILURE_RETRY(write(fd, ptr, uncompLen));
+ if (actual < 0) {
+ ALOGE("Write failed: %s\n", strerror(errno));
+ goto unmap;
+ } else if ((size_t) actual != uncompLen) {
+ ALOGE("Partial write during uncompress (" ZD " of " ZD ")\n",
+ (ZD_TYPE) actual, (ZD_TYPE) uncompLen);
+ goto unmap;
+ } else {
+ ALOGI("+++ successful write\n");
+ }
+ } else {
+ if (!inflateBuffer(fd, ptr, uncompLen, compLen)) {
+ goto unmap;
+ }
+ }
+
+ result = true;
+
+unmap:
+ file->release();
+bail:
+ return result;
+}
+
+/*
+ * Uncompress "deflate" data from one buffer to another.
+ */
+/*static*/ bool ZipFileRO::inflateBuffer(void* outBuf, const void* inBuf,
+ size_t uncompLen, size_t compLen)
+{
+ bool result = false;
+ z_stream zstream;
+ int zerr;
+
+ /*
+ * Initialize the zlib stream struct.
+ */
+ memset(&zstream, 0, sizeof(zstream));
+ zstream.zalloc = Z_NULL;
+ zstream.zfree = Z_NULL;
+ zstream.opaque = Z_NULL;
+ zstream.next_in = (Bytef*)inBuf;
+ zstream.avail_in = compLen;
+ zstream.next_out = (Bytef*) outBuf;
+ zstream.avail_out = uncompLen;
+ zstream.data_type = Z_UNKNOWN;
+
+ /*
+ * Use the undocumented "negative window bits" feature to tell zlib
+ * that there's no zlib header waiting for it.
+ */
+ zerr = inflateInit2(&zstream, -MAX_WBITS);
+ if (zerr != Z_OK) {
+ if (zerr == Z_VERSION_ERROR) {
+ ALOGE("Installed zlib is not compatible with linked version (%s)\n",
+ ZLIB_VERSION);
+ } else {
+ ALOGE("Call to inflateInit2 failed (zerr=%d)\n", zerr);
+ }
+ goto bail;
+ }
+
+ /*
+ * Expand data.
+ */
+ zerr = inflate(&zstream, Z_FINISH);
+ if (zerr != Z_STREAM_END) {
+ ALOGW("Zip inflate failed, zerr=%d (nIn=%p aIn=%u nOut=%p aOut=%u)\n",
+ zerr, zstream.next_in, zstream.avail_in,
+ zstream.next_out, zstream.avail_out);
+ goto z_bail;
+ }
+
+ /* paranoia */
+ if (zstream.total_out != uncompLen) {
+ ALOGW("Size mismatch on inflated file (%ld vs " ZD ")\n",
+ zstream.total_out, (ZD_TYPE) uncompLen);
+ goto z_bail;
+ }
+
+ result = true;
+
+z_bail:
+ inflateEnd(&zstream); /* free up any allocated structures */
+
+bail:
+ return result;
+}
+
+/*
+ * Uncompress "deflate" data from one buffer to an open file descriptor.
+ */
+/*static*/ bool ZipFileRO::inflateBuffer(int fd, const void* inBuf,
+ size_t uncompLen, size_t compLen)
+{
+ bool result = false;
+ const size_t kWriteBufSize = 32768;
+ unsigned char writeBuf[kWriteBufSize];
+ z_stream zstream;
+ int zerr;
+
+ /*
+ * Initialize the zlib stream struct.
+ */
+ memset(&zstream, 0, sizeof(zstream));
+ zstream.zalloc = Z_NULL;
+ zstream.zfree = Z_NULL;
+ zstream.opaque = Z_NULL;
+ zstream.next_in = (Bytef*)inBuf;
+ zstream.avail_in = compLen;
+ zstream.next_out = (Bytef*) writeBuf;
+ zstream.avail_out = sizeof(writeBuf);
+ zstream.data_type = Z_UNKNOWN;
+
+ /*
+ * Use the undocumented "negative window bits" feature to tell zlib
+ * that there's no zlib header waiting for it.
+ */
+ zerr = inflateInit2(&zstream, -MAX_WBITS);
+ if (zerr != Z_OK) {
+ if (zerr == Z_VERSION_ERROR) {
+ ALOGE("Installed zlib is not compatible with linked version (%s)\n",
+ ZLIB_VERSION);
+ } else {
+ ALOGE("Call to inflateInit2 failed (zerr=%d)\n", zerr);
+ }
+ goto bail;
+ }
+
+ /*
+ * Loop while we have more to do.
+ */
+ do {
+ /*
+ * Expand data.
+ */
+ zerr = inflate(&zstream, Z_NO_FLUSH);
+ if (zerr != Z_OK && zerr != Z_STREAM_END) {
+ ALOGW("zlib inflate: zerr=%d (nIn=%p aIn=%u nOut=%p aOut=%u)\n",
+ zerr, zstream.next_in, zstream.avail_in,
+ zstream.next_out, zstream.avail_out);
+ goto z_bail;
+ }
+
+ /* write when we're full or when we're done */
+ if (zstream.avail_out == 0 ||
+ (zerr == Z_STREAM_END && zstream.avail_out != sizeof(writeBuf)))
+ {
+ long writeSize = zstream.next_out - writeBuf;
+ int cc = TEMP_FAILURE_RETRY(write(fd, writeBuf, writeSize));
+ if (cc < 0) {
+ ALOGW("write failed in inflate: %s", strerror(errno));
+ goto z_bail;
+ } else if (cc != (int) writeSize) {
+ ALOGW("write failed in inflate (%d vs %ld)", cc, writeSize);
+ goto z_bail;
+ }
+
+ zstream.next_out = writeBuf;
+ zstream.avail_out = sizeof(writeBuf);
+ }
+ } while (zerr == Z_OK);
+
+ assert(zerr == Z_STREAM_END); /* other errors should've been caught */
+
+ /* paranoia */
+ if (zstream.total_out != uncompLen) {
+ ALOGW("Size mismatch on inflated file (%ld vs " ZD ")\n",
+ zstream.total_out, (ZD_TYPE) uncompLen);
+ goto z_bail;
+ }
+
+ result = true;
+
+z_bail:
+ inflateEnd(&zstream); /* free up any allocated structures */
+
+bail:
+ return result;
+}
diff --git a/libs/androidfw/ZipUtils.cpp b/libs/androidfw/ZipUtils.cpp
new file mode 100644
index 0000000..997eb7d
--- /dev/null
+++ b/libs/androidfw/ZipUtils.cpp
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2007 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.
+ */
+
+//
+// Misc zip/gzip utility functions.
+//
+
+#define LOG_TAG "ziputil"
+
+#include <androidfw/ZipUtils.h>
+#include <androidfw/ZipFileRO.h>
+#include <utils/Log.h>
+#include <utils/Compat.h>
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include <zlib.h>
+
+using namespace android;
+
+/*
+ * Utility function that expands zip/gzip "deflate" compressed data
+ * into a buffer.
+ *
+ * "fd" is an open file positioned at the start of the "deflate" data
+ * "buf" must hold at least "uncompressedLen" bytes.
+ */
+/*static*/ bool ZipUtils::inflateToBuffer(int fd, void* buf,
+ long uncompressedLen, long compressedLen)
+{
+ bool result = false;
+ const unsigned long kReadBufSize = 32768;
+ unsigned char* readBuf = NULL;
+ z_stream zstream;
+ int zerr;
+ unsigned long compRemaining;
+
+ assert(uncompressedLen >= 0);
+ assert(compressedLen >= 0);
+
+ readBuf = new unsigned char[kReadBufSize];
+ if (readBuf == NULL)
+ goto bail;
+ compRemaining = compressedLen;
+
+ /*
+ * Initialize the zlib stream.
+ */
+ memset(&zstream, 0, sizeof(zstream));
+ zstream.zalloc = Z_NULL;
+ zstream.zfree = Z_NULL;
+ zstream.opaque = Z_NULL;
+ zstream.next_in = NULL;
+ zstream.avail_in = 0;
+ zstream.next_out = (Bytef*) buf;
+ zstream.avail_out = uncompressedLen;
+ zstream.data_type = Z_UNKNOWN;
+
+ /*
+ * Use the undocumented "negative window bits" feature to tell zlib
+ * that there's no zlib header waiting for it.
+ */
+ zerr = inflateInit2(&zstream, -MAX_WBITS);
+ if (zerr != Z_OK) {
+ if (zerr == Z_VERSION_ERROR) {
+ ALOGE("Installed zlib is not compatible with linked version (%s)\n",
+ ZLIB_VERSION);
+ } else {
+ ALOGE("Call to inflateInit2 failed (zerr=%d)\n", zerr);
+ }
+ goto bail;
+ }
+
+ /*
+ * Loop while we have data.
+ */
+ do {
+ unsigned long getSize;
+
+ /* read as much as we can */
+ if (zstream.avail_in == 0) {
+ getSize = (compRemaining > kReadBufSize) ?
+ kReadBufSize : compRemaining;
+ ALOGV("+++ reading %ld bytes (%ld left)\n",
+ getSize, compRemaining);
+
+ int cc = TEMP_FAILURE_RETRY(read(fd, readBuf, getSize));
+ if (cc < 0) {
+ ALOGW("inflate read failed: %s", strerror(errno));
+ } else if (cc != (int) getSize) {
+ ALOGW("inflate read failed (%d vs %ld)", cc, getSize);
+ goto z_bail;
+ }
+
+ compRemaining -= getSize;
+
+ zstream.next_in = readBuf;
+ zstream.avail_in = getSize;
+ }
+
+ /* uncompress the data */
+ zerr = inflate(&zstream, Z_NO_FLUSH);
+ if (zerr != Z_OK && zerr != Z_STREAM_END) {
+ ALOGD("zlib inflate call failed (zerr=%d)\n", zerr);
+ goto z_bail;
+ }
+
+ /* output buffer holds all, so no need to write the output */
+ } while (zerr == Z_OK);
+
+ assert(zerr == Z_STREAM_END); /* other errors should've been caught */
+
+ if ((long) zstream.total_out != uncompressedLen) {
+ ALOGW("Size mismatch on inflated file (%ld vs %ld)\n",
+ zstream.total_out, uncompressedLen);
+ goto z_bail;
+ }
+
+ // success!
+ result = true;
+
+z_bail:
+ inflateEnd(&zstream); /* free up any allocated structures */
+
+bail:
+ delete[] readBuf;
+ return result;
+}
+
+/*
+ * Utility function that expands zip/gzip "deflate" compressed data
+ * into a buffer.
+ *
+ * (This is a clone of the previous function, but it takes a FILE* instead
+ * of an fd. We could pass fileno(fd) to the above, but we can run into
+ * trouble when "fp" has a different notion of what fd's file position is.)
+ *
+ * "fp" is an open file positioned at the start of the "deflate" data
+ * "buf" must hold at least "uncompressedLen" bytes.
+ */
+/*static*/ bool ZipUtils::inflateToBuffer(FILE* fp, void* buf,
+ long uncompressedLen, long compressedLen)
+{
+ bool result = false;
+ const unsigned long kReadBufSize = 32768;
+ unsigned char* readBuf = NULL;
+ z_stream zstream;
+ int zerr;
+ unsigned long compRemaining;
+
+ assert(uncompressedLen >= 0);
+ assert(compressedLen >= 0);
+
+ readBuf = new unsigned char[kReadBufSize];
+ if (readBuf == NULL)
+ goto bail;
+ compRemaining = compressedLen;
+
+ /*
+ * Initialize the zlib stream.
+ */
+ memset(&zstream, 0, sizeof(zstream));
+ zstream.zalloc = Z_NULL;
+ zstream.zfree = Z_NULL;
+ zstream.opaque = Z_NULL;
+ zstream.next_in = NULL;
+ zstream.avail_in = 0;
+ zstream.next_out = (Bytef*) buf;
+ zstream.avail_out = uncompressedLen;
+ zstream.data_type = Z_UNKNOWN;
+
+ /*
+ * Use the undocumented "negative window bits" feature to tell zlib
+ * that there's no zlib header waiting for it.
+ */
+ zerr = inflateInit2(&zstream, -MAX_WBITS);
+ if (zerr != Z_OK) {
+ if (zerr == Z_VERSION_ERROR) {
+ ALOGE("Installed zlib is not compatible with linked version (%s)\n",
+ ZLIB_VERSION);
+ } else {
+ ALOGE("Call to inflateInit2 failed (zerr=%d)\n", zerr);
+ }
+ goto bail;
+ }
+
+ /*
+ * Loop while we have data.
+ */
+ do {
+ unsigned long getSize;
+
+ /* read as much as we can */
+ if (zstream.avail_in == 0) {
+ getSize = (compRemaining > kReadBufSize) ?
+ kReadBufSize : compRemaining;
+ ALOGV("+++ reading %ld bytes (%ld left)\n",
+ getSize, compRemaining);
+
+ int cc = fread(readBuf, 1, getSize, fp);
+ if (cc != (int) getSize) {
+ ALOGD("inflate read failed (%d vs %ld)\n",
+ cc, getSize);
+ goto z_bail;
+ }
+
+ compRemaining -= getSize;
+
+ zstream.next_in = readBuf;
+ zstream.avail_in = getSize;
+ }
+
+ /* uncompress the data */
+ zerr = inflate(&zstream, Z_NO_FLUSH);
+ if (zerr != Z_OK && zerr != Z_STREAM_END) {
+ ALOGD("zlib inflate call failed (zerr=%d)\n", zerr);
+ goto z_bail;
+ }
+
+ /* output buffer holds all, so no need to write the output */
+ } while (zerr == Z_OK);
+
+ assert(zerr == Z_STREAM_END); /* other errors should've been caught */
+
+ if ((long) zstream.total_out != uncompressedLen) {
+ ALOGW("Size mismatch on inflated file (%ld vs %ld)\n",
+ zstream.total_out, uncompressedLen);
+ goto z_bail;
+ }
+
+ // success!
+ result = true;
+
+z_bail:
+ inflateEnd(&zstream); /* free up any allocated structures */
+
+bail:
+ delete[] readBuf;
+ return result;
+}
+
+/*
+ * Look at the contents of a gzip archive. We want to know where the
+ * data starts, and how long it will be after it is uncompressed.
+ *
+ * We expect to find the CRC and length as the last 8 bytes on the file.
+ * This is a pretty reasonable thing to expect for locally-compressed
+ * files, but there's a small chance that some extra padding got thrown
+ * on (the man page talks about compressed data written to tape). We
+ * don't currently deal with that here. If "gzip -l" whines, we're going
+ * to fail too.
+ *
+ * On exit, "fp" is pointing at the start of the compressed data.
+ */
+/*static*/ bool ZipUtils::examineGzip(FILE* fp, int* pCompressionMethod,
+ long* pUncompressedLen, long* pCompressedLen, unsigned long* pCRC32)
+{
+ enum { // flags
+ FTEXT = 0x01,
+ FHCRC = 0x02,
+ FEXTRA = 0x04,
+ FNAME = 0x08,
+ FCOMMENT = 0x10,
+ };
+ int ic;
+ int method, flags;
+ int i;
+
+ ic = getc(fp);
+ if (ic != 0x1f || getc(fp) != 0x8b)
+ return false; // not gzip
+ method = getc(fp);
+ flags = getc(fp);
+
+ /* quick sanity checks */
+ if (method == EOF || flags == EOF)
+ return false;
+ if (method != ZipFileRO::kCompressDeflated)
+ return false;
+
+ /* skip over 4 bytes of mod time, 1 byte XFL, 1 byte OS */
+ for (i = 0; i < 6; i++)
+ (void) getc(fp);
+ /* consume "extra" field, if present */
+ if ((flags & FEXTRA) != 0) {
+ int len;
+
+ len = getc(fp);
+ len |= getc(fp) << 8;
+ while (len-- && getc(fp) != EOF)
+ ;
+ }
+ /* consume filename, if present */
+ if ((flags & FNAME) != 0) {
+ do {
+ ic = getc(fp);
+ } while (ic != 0 && ic != EOF);
+ }
+ /* consume comment, if present */
+ if ((flags & FCOMMENT) != 0) {
+ do {
+ ic = getc(fp);
+ } while (ic != 0 && ic != EOF);
+ }
+ /* consume 16-bit header CRC, if present */
+ if ((flags & FHCRC) != 0) {
+ (void) getc(fp);
+ (void) getc(fp);
+ }
+
+ if (feof(fp) || ferror(fp))
+ return false;
+
+ /* seek to the end; CRC and length are in the last 8 bytes */
+ long curPosn = ftell(fp);
+ unsigned char buf[8];
+ fseek(fp, -8, SEEK_END);
+ *pCompressedLen = ftell(fp) - curPosn;
+
+ if (fread(buf, 1, 8, fp) != 8)
+ return false;
+ /* seek back to start of compressed data */
+ fseek(fp, curPosn, SEEK_SET);
+
+ *pCompressionMethod = method;
+ *pCRC32 = ZipFileRO::get4LE(&buf[0]);
+ *pUncompressedLen = ZipFileRO::get4LE(&buf[4]);
+
+ return true;
+}
diff --git a/libs/androidfw/misc.cpp b/libs/androidfw/misc.cpp
new file mode 100644
index 0000000..29686ef
--- /dev/null
+++ b/libs/androidfw/misc.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2005 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.
+ */
+
+#define LOG_TAG "misc"
+
+//
+// Miscellaneous utility functions.
+//
+#include <androidfw/misc.h>
+
+#include <sys/stat.h>
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+
+using namespace android;
+
+namespace android {
+
+/*
+ * Get a file's type.
+ */
+FileType getFileType(const char* fileName)
+{
+ struct stat sb;
+
+ if (stat(fileName, &sb) < 0) {
+ if (errno == ENOENT || errno == ENOTDIR)
+ return kFileTypeNonexistent;
+ else {
+ fprintf(stderr, "getFileType got errno=%d on '%s'\n",
+ errno, fileName);
+ return kFileTypeUnknown;
+ }
+ } else {
+ if (S_ISREG(sb.st_mode))
+ return kFileTypeRegular;
+ else if (S_ISDIR(sb.st_mode))
+ return kFileTypeDirectory;
+ else if (S_ISCHR(sb.st_mode))
+ return kFileTypeCharDev;
+ else if (S_ISBLK(sb.st_mode))
+ return kFileTypeBlockDev;
+ else if (S_ISFIFO(sb.st_mode))
+ return kFileTypeFifo;
+#ifdef HAVE_SYMLINKS
+ else if (S_ISLNK(sb.st_mode))
+ return kFileTypeSymlink;
+ else if (S_ISSOCK(sb.st_mode))
+ return kFileTypeSocket;
+#endif
+ else
+ return kFileTypeUnknown;
+ }
+}
+
+/*
+ * Get a file's modification date.
+ */
+time_t getFileModDate(const char* fileName)
+{
+ struct stat sb;
+
+ if (stat(fileName, &sb) < 0)
+ return (time_t) -1;
+
+ return sb.st_mtime;
+}
+
+}; // namespace android
diff --git a/libs/androidfw/tests/Android.mk b/libs/androidfw/tests/Android.mk
new file mode 100644
index 0000000..c8e3f2b
--- /dev/null
+++ b/libs/androidfw/tests/Android.mk
@@ -0,0 +1,32 @@
+# Build the unit tests.
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# Build the unit tests.
+test_src_files := \
+ BackupData_test.cpp \
+ ObbFile_test.cpp \
+ ZipFileRO_test.cpp
+
+shared_libraries := \
+ libandroidfw \
+ libcutils \
+ libutils \
+ libui \
+ libstlport
+
+static_libraries := \
+ libgtest \
+ libgtest_main
+
+$(foreach file,$(test_src_files), \
+ $(eval include $(CLEAR_VARS)) \
+ $(eval LOCAL_SHARED_LIBRARIES := $(shared_libraries)) \
+ $(eval LOCAL_STATIC_LIBRARIES := $(static_libraries)) \
+ $(eval LOCAL_SRC_FILES := $(file)) \
+ $(eval LOCAL_MODULE := $(notdir $(file:%.cpp=%))) \
+ $(eval include $(BUILD_NATIVE_TEST)) \
+)
+
+# Build the manual test programs.
+include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/libs/androidfw/tests/BackupData_test.cpp b/libs/androidfw/tests/BackupData_test.cpp
new file mode 100644
index 0000000..17f91ca
--- /dev/null
+++ b/libs/androidfw/tests/BackupData_test.cpp
@@ -0,0 +1,438 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#define LOG_TAG "ObbFile_test"
+#include <androidfw/BackupHelpers.h>
+#include <utils/Log.h>
+#include <utils/String8.h>
+
+#include <gtest/gtest.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+
+namespace android {
+
+#define TEST_FILENAME "/test.bd"
+
+// keys of different lengths to test padding
+#define KEY1 "key1"
+#define KEY2 "key2a"
+#define KEY3 "key3bc"
+#define KEY4 "key4def"
+
+// payloads of different lengths to test padding
+#define DATA1 "abcdefg"
+#define DATA2 "hijklmnopq"
+#define DATA3 "rstuvwxyz"
+// KEY4 is only ever deleted
+
+class BackupDataTest : public testing::Test {
+protected:
+ char* m_external_storage;
+ char* m_filename;
+ String8 mKey1;
+ String8 mKey2;
+ String8 mKey3;
+ String8 mKey4;
+
+ virtual void SetUp() {
+ m_external_storage = getenv("EXTERNAL_STORAGE");
+
+ const int totalLen = strlen(m_external_storage) + strlen(TEST_FILENAME) + 1;
+ m_filename = new char[totalLen];
+ snprintf(m_filename, totalLen, "%s%s", m_external_storage, TEST_FILENAME);
+
+ ::unlink(m_filename);
+ int fd = ::open(m_filename, O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+ if (fd < 0) {
+ FAIL() << "Couldn't create " << m_filename << " for writing";
+ }
+ mKey1 = String8(KEY1);
+ mKey2 = String8(KEY2);
+ mKey3 = String8(KEY3);
+ mKey4 = String8(KEY4);
+ }
+
+ virtual void TearDown() {
+ }
+};
+
+TEST_F(BackupDataTest, WriteAndReadSingle) {
+ int fd = ::open(m_filename, O_WRONLY);
+ BackupDataWriter* writer = new BackupDataWriter(fd);
+
+ EXPECT_EQ(NO_ERROR, writer->WriteEntityHeader(mKey1, sizeof(DATA1)))
+ << "WriteEntityHeader returned an error";
+ EXPECT_EQ(NO_ERROR, writer->WriteEntityData(DATA1, sizeof(DATA1)))
+ << "WriteEntityData returned an error";
+
+ ::close(fd);
+ fd = ::open(m_filename, O_RDONLY);
+ BackupDataReader* reader = new BackupDataReader(fd);
+ EXPECT_EQ(NO_ERROR, reader->Status())
+ << "Reader ctor failed";
+
+ bool done;
+ int type;
+ reader->ReadNextHeader(&done, &type);
+ EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
+ << "wrong type from ReadNextHeader";
+
+ String8 key;
+ size_t dataSize;
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
+ << "ReadEntityHeader returned an error";
+ EXPECT_EQ(mKey1, key)
+ << "wrong key from ReadEntityHeader";
+ EXPECT_EQ(sizeof(DATA1), dataSize)
+ << "wrong size from ReadEntityHeader";
+
+ char* dataBytes = new char[dataSize];
+ EXPECT_EQ((int) dataSize, reader->ReadEntityData(dataBytes, dataSize))
+ << "ReadEntityData returned an error";
+ for (unsigned int i = 0; i < sizeof(DATA1); i++) {
+ EXPECT_EQ(DATA1[i], dataBytes[i])
+ << "data character " << i << " should be equal";
+ }
+ delete dataBytes;
+ delete writer;
+ delete reader;
+}
+
+TEST_F(BackupDataTest, WriteAndReadMultiple) {
+ int fd = ::open(m_filename, O_WRONLY);
+ BackupDataWriter* writer = new BackupDataWriter(fd);
+ writer->WriteEntityHeader(mKey1, sizeof(DATA1));
+ writer->WriteEntityData(DATA1, sizeof(DATA1));
+ writer->WriteEntityHeader(mKey2, sizeof(DATA2));
+ writer->WriteEntityData(DATA2, sizeof(DATA2));
+
+ ::close(fd);
+ fd = ::open(m_filename, O_RDONLY);
+ BackupDataReader* reader = new BackupDataReader(fd);
+
+ bool done;
+ int type;
+ String8 key;
+ size_t dataSize;
+ char* dataBytes;
+ // read first entity
+ reader->ReadNextHeader(&done, &type);
+ reader->ReadEntityHeader(&key, &dataSize);
+ dataBytes = new char[dataSize];
+ reader->ReadEntityData(dataBytes, dataSize);
+ delete dataBytes;
+
+ // read and verify second entity
+ reader->ReadNextHeader(&done, &type);
+ EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
+ << "wrong type from ReadNextHeader";
+
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
+ << "ReadEntityHeader returned an error on second entity";
+ EXPECT_EQ(mKey2, key)
+ << "wrong key from ReadEntityHeader on second entity";
+ EXPECT_EQ(sizeof(DATA2), dataSize)
+ << "wrong size from ReadEntityHeader on second entity";
+
+ dataBytes = new char[dataSize];
+ EXPECT_EQ((int)dataSize, reader->ReadEntityData(dataBytes, dataSize))
+ << "ReadEntityData returned an error on second entity";
+ for (unsigned int i = 0; i < sizeof(DATA2); i++) {
+ EXPECT_EQ(DATA2[i], dataBytes[i])
+ << "data character " << i << " should be equal";
+ }
+ delete dataBytes;
+ delete writer;
+ delete reader;
+}
+
+TEST_F(BackupDataTest, SkipEntity) {
+ int fd = ::open(m_filename, O_WRONLY);
+ BackupDataWriter* writer = new BackupDataWriter(fd);
+ writer->WriteEntityHeader(mKey1, sizeof(DATA1));
+ writer->WriteEntityData(DATA1, sizeof(DATA1));
+ writer->WriteEntityHeader(mKey2, sizeof(DATA2));
+ writer->WriteEntityData(DATA2, sizeof(DATA2));
+ writer->WriteEntityHeader(mKey3, sizeof(DATA3));
+ writer->WriteEntityData(DATA3, sizeof(DATA3));
+
+ ::close(fd);
+ fd = ::open(m_filename, O_RDONLY);
+ BackupDataReader* reader = new BackupDataReader(fd);
+
+ bool done;
+ int type;
+ String8 key;
+ size_t dataSize;
+ char* dataBytes;
+ // read first entity
+ reader->ReadNextHeader(&done, &type);
+ reader->ReadEntityHeader(&key, &dataSize);
+ dataBytes = new char[dataSize];
+ reader->ReadEntityData(dataBytes, dataSize);
+ delete dataBytes;
+
+ // skip second entity
+ reader->ReadNextHeader(&done, &type);
+ reader->ReadEntityHeader(&key, &dataSize);
+ reader->SkipEntityData();
+
+ // read and verify third entity
+ reader->ReadNextHeader(&done, &type);
+ EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
+ << "wrong type from ReadNextHeader after skip";
+
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
+ << "ReadEntityHeader returned an error on third entity";
+ EXPECT_EQ(mKey3, key)
+ << "wrong key from ReadEntityHeader on third entity";
+ EXPECT_EQ(sizeof(DATA3), dataSize)
+ << "wrong size from ReadEntityHeader on third entity";
+
+ dataBytes = new char[dataSize];
+ EXPECT_EQ((int) dataSize, reader->ReadEntityData(dataBytes, dataSize))
+ << "ReadEntityData returned an error on third entity";
+ for (unsigned int i = 0; i < sizeof(DATA3); i++) {
+ EXPECT_EQ(DATA3[i], dataBytes[i])
+ << "data character " << i << " should be equal";
+ }
+ delete dataBytes;
+ delete writer;
+ delete reader;
+}
+
+TEST_F(BackupDataTest, DeleteEntity) {
+ int fd = ::open(m_filename, O_WRONLY);
+ BackupDataWriter* writer = new BackupDataWriter(fd);
+ writer->WriteEntityHeader(mKey1, sizeof(DATA1));
+ writer->WriteEntityData(DATA1, sizeof(DATA1));
+ writer->WriteEntityHeader(mKey2, -1);
+
+ ::close(fd);
+ fd = ::open(m_filename, O_RDONLY);
+ BackupDataReader* reader = new BackupDataReader(fd);
+
+ bool done;
+ int type;
+ String8 key;
+ size_t dataSize;
+ char* dataBytes;
+ // read first entity
+ reader->ReadNextHeader(&done, &type);
+ reader->ReadEntityHeader(&key, &dataSize);
+ dataBytes = new char[dataSize];
+ reader->ReadEntityData(dataBytes, dataSize);
+ delete dataBytes;
+
+ // read and verify deletion
+ reader->ReadNextHeader(&done, &type);
+ EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
+ << "wrong type from ReadNextHeader on deletion";
+
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
+ << "ReadEntityHeader returned an error on second entity";
+ EXPECT_EQ(mKey2, key)
+ << "wrong key from ReadEntityHeader on second entity";
+ EXPECT_EQ(-1, (int) dataSize)
+ << "not recognizing deletion on second entity";
+
+ delete writer;
+ delete reader;
+}
+
+TEST_F(BackupDataTest, EneityAfterDelete) {
+ int fd = ::open(m_filename, O_WRONLY);
+ BackupDataWriter* writer = new BackupDataWriter(fd);
+ writer->WriteEntityHeader(mKey1, sizeof(DATA1));
+ writer->WriteEntityData(DATA1, sizeof(DATA1));
+ writer->WriteEntityHeader(mKey2, -1);
+ writer->WriteEntityHeader(mKey3, sizeof(DATA3));
+ writer->WriteEntityData(DATA3, sizeof(DATA3));
+
+ ::close(fd);
+ fd = ::open(m_filename, O_RDONLY);
+ BackupDataReader* reader = new BackupDataReader(fd);
+
+ bool done;
+ int type;
+ String8 key;
+ size_t dataSize;
+ char* dataBytes;
+ // read first entity
+ reader->ReadNextHeader(&done, &type);
+ reader->ReadEntityHeader(&key, &dataSize);
+ dataBytes = new char[dataSize];
+ reader->ReadEntityData(dataBytes, dataSize);
+ delete dataBytes;
+
+ // read and verify deletion
+ reader->ReadNextHeader(&done, &type);
+ EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
+ << "wrong type from ReadNextHeader on deletion";
+
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
+ << "ReadEntityHeader returned an error on second entity";
+ EXPECT_EQ(mKey2, key)
+ << "wrong key from ReadEntityHeader on second entity";
+ EXPECT_EQ(-1, (int)dataSize)
+ << "not recognizing deletion on second entity";
+
+ // read and verify third entity
+ reader->ReadNextHeader(&done, &type);
+ EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
+ << "wrong type from ReadNextHeader after deletion";
+
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
+ << "ReadEntityHeader returned an error on third entity";
+ EXPECT_EQ(mKey3, key)
+ << "wrong key from ReadEntityHeader on third entity";
+ EXPECT_EQ(sizeof(DATA3), dataSize)
+ << "wrong size from ReadEntityHeader on third entity";
+
+ dataBytes = new char[dataSize];
+ EXPECT_EQ((int) dataSize, reader->ReadEntityData(dataBytes, dataSize))
+ << "ReadEntityData returned an error on third entity";
+ for (unsigned int i = 0; i < sizeof(DATA3); i++) {
+ EXPECT_EQ(DATA3[i], dataBytes[i])
+ << "data character " << i << " should be equal";
+ }
+ delete dataBytes;
+ delete writer;
+ delete reader;
+}
+
+TEST_F(BackupDataTest, OnlyDeleteEntities) {
+ int fd = ::open(m_filename, O_WRONLY);
+ BackupDataWriter* writer = new BackupDataWriter(fd);
+ writer->WriteEntityHeader(mKey1, -1);
+ writer->WriteEntityHeader(mKey2, -1);
+ writer->WriteEntityHeader(mKey3, -1);
+ writer->WriteEntityHeader(mKey4, -1);
+
+ ::close(fd);
+ fd = ::open(m_filename, O_RDONLY);
+ BackupDataReader* reader = new BackupDataReader(fd);
+
+ bool done;
+ int type;
+ String8 key;
+ size_t dataSize;
+ // read and verify first deletion
+ reader->ReadNextHeader(&done, &type);
+ EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
+ << "wrong type from ReadNextHeader first deletion";
+
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
+ << "ReadEntityHeader returned an error on first entity";
+ EXPECT_EQ(mKey1, key)
+ << "wrong key from ReadEntityHeader on first entity";
+ EXPECT_EQ(-1, (int) dataSize)
+ << "not recognizing deletion on first entity";
+
+ // read and verify second deletion
+ reader->ReadNextHeader(&done, &type);
+ EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
+ << "wrong type from ReadNextHeader second deletion";
+
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
+ << "ReadEntityHeader returned an error on second entity";
+ EXPECT_EQ(mKey2, key)
+ << "wrong key from ReadEntityHeader on second entity";
+ EXPECT_EQ(-1, (int) dataSize)
+ << "not recognizing deletion on second entity";
+
+ // read and verify third deletion
+ reader->ReadNextHeader(&done, &type);
+ EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
+ << "wrong type from ReadNextHeader third deletion";
+
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
+ << "ReadEntityHeader returned an error on third entity";
+ EXPECT_EQ(mKey3, key)
+ << "wrong key from ReadEntityHeader on third entity";
+ EXPECT_EQ(-1, (int) dataSize)
+ << "not recognizing deletion on third entity";
+
+ // read and verify fourth deletion
+ reader->ReadNextHeader(&done, &type);
+ EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
+ << "wrong type from ReadNextHeader fourth deletion";
+
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
+ << "ReadEntityHeader returned an error on fourth entity";
+ EXPECT_EQ(mKey4, key)
+ << "wrong key from ReadEntityHeader on fourth entity";
+ EXPECT_EQ(-1, (int) dataSize)
+ << "not recognizing deletion on fourth entity";
+
+ delete writer;
+ delete reader;
+}
+
+TEST_F(BackupDataTest, ReadDeletedEntityData) {
+ int fd = ::open(m_filename, O_WRONLY);
+ BackupDataWriter* writer = new BackupDataWriter(fd);
+ writer->WriteEntityHeader(mKey1, -1);
+ writer->WriteEntityHeader(mKey2, -1);
+
+ ::close(fd);
+ fd = ::open(m_filename, O_RDONLY);
+ BackupDataReader* reader = new BackupDataReader(fd);
+
+ bool done;
+ int type;
+ String8 key;
+ size_t dataSize;
+ // read and verify first deletion
+ reader->ReadNextHeader(&done, &type);
+ EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
+ << "wrong type from ReadNextHeader first deletion";
+
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
+ << "ReadEntityHeader returned an error on first entity";
+ EXPECT_EQ(mKey1, key)
+ << "wrong key from ReadEntityHeader on first entity";
+ EXPECT_EQ(-1, (int) dataSize)
+ << "not recognizing deletion on first entity";
+
+ // erroneously try to read first entity data
+ char* dataBytes = new char[10];
+ dataBytes[0] = 'A';
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityData(dataBytes, dataSize));
+ // expect dataBytes to be unmodofied
+ EXPECT_EQ('A', dataBytes[0]);
+
+ // read and verify second deletion
+ reader->ReadNextHeader(&done, &type);
+ EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type)
+ << "wrong type from ReadNextHeader second deletion";
+
+ EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize))
+ << "ReadEntityHeader returned an error on second entity";
+ EXPECT_EQ(mKey2, key)
+ << "wrong key from ReadEntityHeader on second entity";
+ EXPECT_EQ(-1, (int) dataSize)
+ << "not recognizing deletion on second entity";
+
+ delete writer;
+ delete reader;
+}
+
+}
diff --git a/libs/androidfw/tests/ObbFile_test.cpp b/libs/androidfw/tests/ObbFile_test.cpp
new file mode 100644
index 0000000..2c9f650
--- /dev/null
+++ b/libs/androidfw/tests/ObbFile_test.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+#define LOG_TAG "ObbFile_test"
+#include <androidfw/ObbFile.h>
+#include <utils/Log.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+
+#include <gtest/gtest.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+
+namespace android {
+
+#define TEST_FILENAME "/test.obb"
+
+class ObbFileTest : public testing::Test {
+protected:
+ sp<ObbFile> mObbFile;
+ char* mExternalStorage;
+ char* mFileName;
+
+ virtual void SetUp() {
+ mObbFile = new ObbFile();
+ mExternalStorage = getenv("EXTERNAL_STORAGE");
+
+ const int totalLen = strlen(mExternalStorage) + strlen(TEST_FILENAME) + 1;
+ mFileName = new char[totalLen];
+ snprintf(mFileName, totalLen, "%s%s", mExternalStorage, TEST_FILENAME);
+
+ int fd = ::open(mFileName, O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+ if (fd < 0) {
+ FAIL() << "Couldn't create " << mFileName << " for tests";
+ }
+ }
+
+ virtual void TearDown() {
+ }
+};
+
+TEST_F(ObbFileTest, ReadFailure) {
+ EXPECT_FALSE(mObbFile->readFrom(-1))
+ << "No failure on invalid file descriptor";
+}
+
+TEST_F(ObbFileTest, WriteThenRead) {
+ const char* packageName = "com.example.obbfile";
+ const int32_t versionNum = 1;
+
+ mObbFile->setPackageName(String8(packageName));
+ mObbFile->setVersion(versionNum);
+#define SALT_SIZE 8
+ unsigned char salt[SALT_SIZE] = {0x01, 0x10, 0x55, 0xAA, 0xFF, 0x00, 0x5A, 0xA5};
+ EXPECT_TRUE(mObbFile->setSalt(salt, SALT_SIZE))
+ << "Salt should be successfully set";
+
+ EXPECT_TRUE(mObbFile->writeTo(mFileName))
+ << "couldn't write to fake .obb file";
+
+ mObbFile = new ObbFile();
+
+ EXPECT_TRUE(mObbFile->readFrom(mFileName))
+ << "couldn't read from fake .obb file";
+
+ EXPECT_EQ(versionNum, mObbFile->getVersion())
+ << "version didn't come out the same as it went in";
+ const char* currentPackageName = mObbFile->getPackageName().string();
+ EXPECT_STREQ(packageName, currentPackageName)
+ << "package name didn't come out the same as it went in";
+
+ size_t saltLen;
+ const unsigned char* newSalt = mObbFile->getSalt(&saltLen);
+
+ EXPECT_EQ(sizeof(salt), saltLen)
+ << "salt sizes were not the same";
+
+ for (int i = 0; i < sizeof(salt); i++) {
+ EXPECT_EQ(salt[i], newSalt[i])
+ << "salt character " << i << " should be equal";
+ }
+ EXPECT_TRUE(memcmp(newSalt, salt, sizeof(salt)) == 0)
+ << "salts should be the same";
+}
+
+}
diff --git a/libs/androidfw/tests/ZipFileRO_test.cpp b/libs/androidfw/tests/ZipFileRO_test.cpp
new file mode 100644
index 0000000..cb9c721
--- /dev/null
+++ b/libs/androidfw/tests/ZipFileRO_test.cpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#define LOG_TAG "ZipFileRO_test"
+#include <utils/Log.h>
+#include <androidfw/ZipFileRO.h>
+
+#include <gtest/gtest.h>
+
+#include <fcntl.h>
+#include <string.h>
+
+namespace android {
+
+class ZipFileROTest : public testing::Test {
+protected:
+ virtual void SetUp() {
+ }
+
+ virtual void TearDown() {
+ }
+};
+
+TEST_F(ZipFileROTest, ZipTimeConvertSuccess) {
+ struct tm t;
+
+ // 2011-06-29 14:40:40
+ long when = 0x3EDD7514;
+
+ ZipFileRO::zipTimeToTimespec(when, &t);
+
+ EXPECT_EQ(2011, t.tm_year + 1900)
+ << "Year was improperly converted.";
+
+ EXPECT_EQ(6, t.tm_mon)
+ << "Month was improperly converted.";
+
+ EXPECT_EQ(29, t.tm_mday)
+ << "Day was improperly converted.";
+
+ EXPECT_EQ(14, t.tm_hour)
+ << "Hour was improperly converted.";
+
+ EXPECT_EQ(40, t.tm_min)
+ << "Minute was improperly converted.";
+
+ EXPECT_EQ(40, t.tm_sec)
+ << "Second was improperly converted.";
+}
+
+}
diff --git a/libs/binder/Android.mk b/libs/binder/Android.mk
index f3f8daf..c8147ed 100644
--- a/libs/binder/Android.mk
+++ b/libs/binder/Android.mk
@@ -21,6 +21,7 @@
Debug.cpp \
IAppOpsCallback.cpp \
IAppOpsService.cpp \
+ IBatteryStats.cpp \
IInterface.cpp \
IMemory.cpp \
IPCThreadState.cpp \
diff --git a/libs/binder/IBatteryStats.cpp b/libs/binder/IBatteryStats.cpp
new file mode 100644
index 0000000..6469b08
--- /dev/null
+++ b/libs/binder/IBatteryStats.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#include <binder/IBatteryStats.h>
+
+#include <utils/Debug.h>
+#include <utils/Log.h>
+#include <binder/Parcel.h>
+#include <utils/String8.h>
+
+#include <private/binder/Static.h>
+
+namespace android {
+
+// ----------------------------------------------------------------------
+
+class BpBatteryStats : public BpInterface<IBatteryStats>
+{
+public:
+ BpBatteryStats(const sp<IBinder>& impl)
+ : BpInterface<IBatteryStats>(impl)
+ {
+ }
+
+ virtual void noteStartSensor(int uid, int sensor) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IBatteryStats::getInterfaceDescriptor());
+ data.writeInt32(uid);
+ data.writeInt32(sensor);
+ remote()->transact(NOTE_START_SENSOR_TRANSACTION, data, &reply);
+ }
+
+ virtual void noteStopSensor(int uid, int sensor) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IBatteryStats::getInterfaceDescriptor());
+ data.writeInt32(uid);
+ data.writeInt32(sensor);
+ remote()->transact(NOTE_STOP_SENSOR_TRANSACTION, data, &reply);
+ }
+};
+
+IMPLEMENT_META_INTERFACE(BatteryStats, "com.android.internal.app.IBatteryStats");
+
+// ----------------------------------------------------------------------
+
+status_t BnBatteryStats::onTransact(
+ uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
+{
+ switch(code) {
+ case NOTE_START_SENSOR_TRANSACTION: {
+ CHECK_INTERFACE(IBatteryStats, data, reply);
+ int uid = data.readInt32();
+ int sensor = data.readInt32();
+ noteStartSensor(uid, sensor);
+ reply->writeNoException();
+ return NO_ERROR;
+ } break;
+ case NOTE_STOP_SENSOR_TRANSACTION: {
+ CHECK_INTERFACE(IBatteryStats, data, reply);
+ int uid = data.readInt32();
+ int sensor = data.readInt32();
+ noteStopSensor(uid, sensor);
+ reply->writeNoException();
+ return NO_ERROR;
+ } break;
+ default:
+ return BBinder::onTransact(code, data, reply, flags);
+ }
+}
+
+}; // namespace android
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index 7a5919f..d130d7c 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -750,6 +750,32 @@
return err;
}
+// WARNING: This method must stay in sync with
+// Parcelable.Creator<ParcelFileDescriptor> CREATOR
+// in frameworks/base/core/java/android/os/ParcelFileDescriptor.java
+status_t Parcel::writeParcelFileDescriptor(int fd, int commChannel) {
+ status_t status;
+
+ if (fd < 0) {
+ status = writeInt32(0); // ParcelFileDescriptor is null
+ if (status) return status;
+ } else {
+ status = writeInt32(1); // ParcelFileDescriptor is not null
+ if (status) return status;
+ status = writeDupFileDescriptor(fd);
+ if (status) return status;
+ if (commChannel < 0) {
+ status = writeInt32(0); // commChannel is null
+ if (status) return status;
+ } else {
+ status = writeInt32(1); // commChannel is not null
+ if (status) return status;
+ status = writeDupFileDescriptor(commChannel);
+ }
+ }
+ return status;
+}
+
status_t Parcel::writeBlob(size_t len, WritableBlob* outBlob)
{
status_t status;
@@ -1148,6 +1174,23 @@
return BAD_TYPE;
}
+// WARNING: This method must stay in sync with writeToParcel()
+// in frameworks/base/core/java/android/os/ParcelFileDescriptor.java
+int Parcel::readParcelFileDescriptor(int& outCommChannel) const {
+ int fd;
+ outCommChannel = -1;
+
+ if (readInt32() == 0) {
+ fd = -1;
+ } else {
+ fd = readFileDescriptor();
+ if (fd >= 0 && readInt32() != 0) {
+ outCommChannel = readFileDescriptor();
+ }
+ }
+ return fd;
+}
+
status_t Parcel::readBlob(size_t len, ReadableBlob* outBlob) const
{
int32_t useAshmem;
diff --git a/libs/gui/BufferQueue.cpp b/libs/gui/BufferQueue.cpp
index c165a68..463206c 100644
--- a/libs/gui/BufferQueue.cpp
+++ b/libs/gui/BufferQueue.cpp
@@ -31,7 +31,6 @@
#include <utils/Log.h>
#include <utils/Trace.h>
-#include <utils/CallStack.h>
// Macros for including the BufferQueue name in log messages
#define ST_LOGV(x, ...) ALOGV("[%s] "x, mConsumerName.string(), ##__VA_ARGS__)
diff --git a/libs/gui/SurfaceControl.cpp b/libs/gui/SurfaceControl.cpp
index 16e533c..de182ee 100644
--- a/libs/gui/SurfaceControl.cpp
+++ b/libs/gui/SurfaceControl.cpp
@@ -23,7 +23,6 @@
#include <android/native_window.h>
-#include <utils/CallStack.h>
#include <utils/Errors.h>
#include <utils/Log.h>
#include <utils/threads.h>
diff --git a/libs/gui/tests/SurfaceTexture_test.cpp b/libs/gui/tests/SurfaceTexture_test.cpp
index e4fba15..28c05a7 100644
--- a/libs/gui/tests/SurfaceTexture_test.cpp
+++ b/libs/gui/tests/SurfaceTexture_test.cpp
@@ -35,6 +35,7 @@
#include <GLES2/gl2ext.h>
#include <ui/FramebufferNativeWindow.h>
+#include <UniquePtr.h>
#include <android/native_window.h>
namespace android {
diff --git a/libs/input/Android.mk b/libs/input/Android.mk
index f1921a4..944ac7f 100644
--- a/libs/input/Android.mk
+++ b/libs/input/Android.mk
@@ -27,6 +27,7 @@
deviceSources := \
$(commonSources) \
+ IInputFlinger.cpp \
InputTransport.cpp \
VelocityControl.cpp \
VelocityTracker.cpp
diff --git a/libs/input/IInputFlinger.cpp b/libs/input/IInputFlinger.cpp
new file mode 100644
index 0000000..e009731
--- /dev/null
+++ b/libs/input/IInputFlinger.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <binder/Parcel.h>
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+
+#include <input/IInputFlinger.h>
+
+
+namespace android {
+
+class BpInputFlinger : public BpInterface<IInputFlinger> {
+public:
+ BpInputFlinger(const sp<IBinder>& impl) :
+ BpInterface<IInputFlinger>(impl) { }
+
+ virtual status_t doSomething() {
+ Parcel data, reply;
+ data.writeInterfaceToken(IInputFlinger::getInterfaceDescriptor());
+ remote()->transact(BnInputFlinger::DO_SOMETHING_TRANSACTION, data, &reply);
+ return reply.readInt32();
+ }
+};
+
+IMPLEMENT_META_INTERFACE(InputFlinger, "android.input.IInputFlinger");
+
+
+status_t BnInputFlinger::onTransact(
+ uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) {
+ switch(code) {
+ case DO_SOMETHING_TRANSACTION: {
+ CHECK_INTERFACE(IInputFlinger, data, reply);
+ reply->writeInt32(0);
+ break;
+ }
+ default:
+ return BBinder::onTransact(code, data, reply, flags);
+ }
+ return NO_ERROR;
+}
+
+};
diff --git a/services/batteryservice/Android.mk b/services/batteryservice/Android.mk
index 0a29c36..9354b99 100644
--- a/services/batteryservice/Android.mk
+++ b/services/batteryservice/Android.mk
@@ -3,6 +3,7 @@
LOCAL_SRC_FILES:= \
BatteryProperties.cpp \
+ BatteryProperty.cpp \
IBatteryPropertiesListener.cpp \
IBatteryPropertiesRegistrar.cpp
diff --git a/services/batteryservice/BatteryProperty.cpp b/services/batteryservice/BatteryProperty.cpp
new file mode 100644
index 0000000..6cbc896
--- /dev/null
+++ b/services/batteryservice/BatteryProperty.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#include <stdint.h>
+#include <sys/types.h>
+#include <batteryservice/BatteryService.h>
+#include <binder/Parcel.h>
+#include <utils/Errors.h>
+
+namespace android {
+
+/*
+ * Parcel read/write code must be kept in sync with
+ * frameworks/base/core/java/android/os/BatteryProperty.java
+ */
+
+status_t BatteryProperty::readFromParcel(Parcel* p) {
+ valueInt = p->readInt32();
+ return OK;
+}
+
+status_t BatteryProperty::writeToParcel(Parcel* p) const {
+ p->writeInt32(valueInt);
+ return OK;
+}
+
+}; // namespace android
diff --git a/services/batteryservice/IBatteryPropertiesRegistrar.cpp b/services/batteryservice/IBatteryPropertiesRegistrar.cpp
index 6c2d2a5..6647122 100644
--- a/services/batteryservice/IBatteryPropertiesRegistrar.cpp
+++ b/services/batteryservice/IBatteryPropertiesRegistrar.cpp
@@ -44,6 +44,18 @@
data.writeStrongBinder(listener->asBinder());
remote()->transact(UNREGISTER_LISTENER, data, NULL);
}
+
+ status_t getProperty(int id, struct BatteryProperty *val) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IBatteryPropertiesRegistrar::getInterfaceDescriptor());
+ data.writeInt32(id);
+ remote()->transact(GET_PROPERTY, data, &reply);
+ status_t ret = reply.readInt32();
+ int parcelpresent = reply.readInt32();
+ if (parcelpresent)
+ val->readFromParcel(&reply);
+ return ret;
+ }
};
IMPLEMENT_META_INTERFACE(BatteryPropertiesRegistrar, "android.os.IBatteryPropertiesRegistrar");
@@ -69,6 +81,18 @@
unregisterListener(listener);
return OK;
}
+
+ case GET_PROPERTY: {
+ CHECK_INTERFACE(IBatteryPropertiesRegistrar, data, reply);
+ int id = data.readInt32();
+ struct BatteryProperty val;
+ status_t result = getProperty(id, &val);
+ reply->writeNoException();
+ reply->writeInt32(result);
+ reply->writeInt32(1);
+ val.writeToParcel(reply);
+ return OK;
+ }
}
return BBinder::onTransact(code, data, reply, flags);
};
diff --git a/services/inputflinger/Android.mk b/services/inputflinger/Android.mk
new file mode 100644
index 0000000..e32d38a
--- /dev/null
+++ b/services/inputflinger/Android.mk
@@ -0,0 +1,50 @@
+# Copyright (C) 2013 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.
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+ InputFlinger.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+ libbinder \
+ libcutils \
+ libinput \
+ liblog \
+ libutils
+
+LOCAL_CFLAGS += -fvisibility=hidden
+
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)
+
+LOCAL_MODULE := libinputflinger
+
+include $(BUILD_SHARED_LIBRARY)
+
+########################################################################
+# build input flinger executable
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+ main.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+ libbinder \
+ libinputflinger \
+ libutils
+
+LOCAL_MODULE := inputflinger
+
+include $(BUILD_EXECUTABLE)
diff --git a/services/inputflinger/InputFlinger.cpp b/services/inputflinger/InputFlinger.cpp
new file mode 100644
index 0000000..9ea6ce5
--- /dev/null
+++ b/services/inputflinger/InputFlinger.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#define LOG_TAG "InputFlinger"
+
+#include "InputFlinger.h"
+
+#include <stdint.h>
+#include <unistd.h>
+
+#include <sys/types.h>
+
+#include <binder/IPCThreadState.h>
+#include <binder/PermissionCache.h>
+#include <cutils/log.h>
+#include <private/android_filesystem_config.h>
+
+namespace android {
+
+const String16 sAccessInputFlingerPermission("android.permission.ACCESS_INPUT_FLINGER");
+const String16 sDumpPermission("android.permission.DUMP");
+
+
+InputFlinger::InputFlinger() :
+ BnInputFlinger() {
+ ALOGI("InputFlinger is starting");
+}
+
+InputFlinger::~InputFlinger() {
+}
+
+status_t InputFlinger::onTransact(
+ uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) {
+ switch (code) {
+ case DO_SOMETHING_TRANSACTION:
+ const IPCThreadState* ipc = IPCThreadState::self();
+ const int pid = ipc->getCallingPid();
+ const int uid = ipc->getCallingUid();
+ if (!PermissionCache::checkPermission(sAccessInputFlingerPermission, pid, uid)) {
+ ALOGE("Permission Denial: "
+ "can't access InputFlinger from pid=%d, uid=%d", pid, uid);
+ return PERMISSION_DENIED;
+ }
+ break;
+ }
+
+ return BnInputFlinger::onTransact(code, data, reply, flags);
+}
+
+status_t InputFlinger::dump(int fd, const Vector<String16>& args) {
+ String8 result;
+ const IPCThreadState* ipc = IPCThreadState::self();
+ const int pid = ipc->getCallingPid();
+ const int uid = ipc->getCallingUid();
+ if ((uid != AID_SHELL)
+ && !PermissionCache::checkPermission(sDumpPermission, pid, uid)) {
+ result.appendFormat("Permission Denial: "
+ "can't dump SurfaceFlinger from pid=%d, uid=%d\n", pid, uid);
+ } else {
+ dumpInternal(result);
+ }
+ write(fd, result.string(), result.size());
+ return OK;
+}
+
+void InputFlinger::dumpInternal(String8& result) {
+ result.append("INPUT FLINGER (dumpsys inputflinger)\n");
+ result.append("... nothing here yet...\n");
+}
+
+status_t InputFlinger::doSomething() {
+ ALOGI("Did something...");
+ return OK;
+}
+
+}; // namespace android
diff --git a/services/inputflinger/InputFlinger.h b/services/inputflinger/InputFlinger.h
new file mode 100644
index 0000000..731ab17
--- /dev/null
+++ b/services/inputflinger/InputFlinger.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#ifndef ANDROID_INPUT_FLINGER_H
+#define ANDROID_INPUT_FLINGER_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <cutils/compiler.h>
+#include <input/IInputFlinger.h>
+#include <utils/String8.h>
+#include <utils/String16.h>
+
+namespace android {
+
+class InputFlinger : public BnInputFlinger {
+public:
+ static char const* getServiceName() ANDROID_API {
+ return "inputflinger";
+ }
+
+ InputFlinger() ANDROID_API;
+
+ // IBinder interface
+ virtual status_t onTransact(uint32_t code,
+ const Parcel& data, Parcel* reply, uint32_t flags);
+ virtual status_t dump(int fd, const Vector<String16>& args);
+
+ // IInputFlinger interface
+ virtual status_t doSomething();
+
+private:
+ virtual ~InputFlinger();
+
+ void dumpInternal(String8& result);
+};
+
+} // namespace android
+
+#endif // ANDROID_INPUT_FLINGER_H
diff --git a/services/inputflinger/main.cpp b/services/inputflinger/main.cpp
new file mode 100644
index 0000000..3209a62
--- /dev/null
+++ b/services/inputflinger/main.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#include <binder/BinderService.h>
+#include "InputFlinger.h"
+
+using namespace android;
+
+int main(int argc, char** argv) {
+ ProcessState::self()->setThreadPoolMaxThreadCount(4);
+ BinderService<InputFlinger>::publishAndJoinThreadPool(true);
+ return 0;
+}
diff --git a/services/sensorservice/BatteryService.cpp b/services/sensorservice/BatteryService.cpp
index 38dc749..cb962a6 100644
--- a/services/sensorservice/BatteryService.cpp
+++ b/services/sensorservice/BatteryService.cpp
@@ -34,32 +34,10 @@
const sp<IServiceManager> sm(defaultServiceManager());
if (sm != NULL) {
const String16 name("batterystats");
- mBatteryStatService = sm->getService(name);
+ mBatteryStatService = interface_cast<IBatteryStats>(sm->getService(name));
}
}
-status_t BatteryService::noteStartSensor(int uid, int handle) {
- Parcel data, reply;
- data.writeInterfaceToken(DESCRIPTOR);
- data.writeInt32(uid);
- data.writeInt32(handle);
- status_t err = mBatteryStatService->transact(
- TRANSACTION_noteStartSensor, data, &reply, 0);
- err = reply.readExceptionCode();
- return err;
-}
-
-status_t BatteryService::noteStopSensor(int uid, int handle) {
- Parcel data, reply;
- data.writeInterfaceToken(DESCRIPTOR);
- data.writeInt32(uid);
- data.writeInt32(handle);
- status_t err = mBatteryStatService->transact(
- TRANSACTION_noteStopSensor, data, &reply, 0);
- err = reply.readExceptionCode();
- return err;
-}
-
bool BatteryService::addSensor(uid_t uid, int handle) {
Mutex::Autolock _l(mActivationsLock);
Info key(uid, handle);
@@ -86,7 +64,7 @@
if (mBatteryStatService != 0) {
if (addSensor(uid, handle)) {
int64_t identity = IPCThreadState::self()->clearCallingIdentity();
- noteStartSensor(uid, handle);
+ mBatteryStatService->noteStartSensor(uid, handle);
IPCThreadState::self()->restoreCallingIdentity(identity);
}
}
@@ -95,7 +73,7 @@
if (mBatteryStatService != 0) {
if (removeSensor(uid, handle)) {
int64_t identity = IPCThreadState::self()->clearCallingIdentity();
- noteStopSensor(uid, handle);
+ mBatteryStatService->noteStopSensor(uid, handle);
IPCThreadState::self()->restoreCallingIdentity(identity);
}
}
@@ -108,7 +86,7 @@
for (ssize_t i=0 ; i<mActivations.size() ; i++) {
const Info& info(mActivations[i]);
if (info.uid == uid) {
- noteStopSensor(info.uid, info.handle);
+ mBatteryStatService->noteStopSensor(info.uid, info.handle);
mActivations.removeAt(i);
i--;
}
@@ -117,8 +95,6 @@
}
}
-const String16 BatteryService::DESCRIPTOR("com.android.internal.app.IBatteryStats");
-
ANDROID_SINGLETON_STATIC_INSTANCE(BatteryService)
// ---------------------------------------------------------------------------
diff --git a/services/sensorservice/BatteryService.h b/services/sensorservice/BatteryService.h
index 86cc884..08ba857 100644
--- a/services/sensorservice/BatteryService.h
+++ b/services/sensorservice/BatteryService.h
@@ -17,22 +17,18 @@
#include <stdint.h>
#include <sys/types.h>
+#include <binder/IBatteryStats.h>
#include <utils/Singleton.h>
namespace android {
// ---------------------------------------------------------------------------
class BatteryService : public Singleton<BatteryService> {
- static const int TRANSACTION_noteStartSensor = IBinder::FIRST_CALL_TRANSACTION + 3;
- static const int TRANSACTION_noteStopSensor = IBinder::FIRST_CALL_TRANSACTION + 4;
- static const String16 DESCRIPTOR;
friend class Singleton<BatteryService>;
- sp<IBinder> mBatteryStatService;
+ sp<IBatteryStats> mBatteryStatService;
BatteryService();
- status_t noteStartSensor(int uid, int handle);
- status_t noteStopSensor(int uid, int handle);
void enableSensorImpl(uid_t uid, int handle);
void disableSensorImpl(uid_t uid, int handle);
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 2469f0c..6a35efa 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -23,7 +23,6 @@
#include <sys/types.h>
#include <math.h>
-#include <utils/CallStack.h>
#include <utils/Errors.h>
#include <utils/misc.h>
#include <utils/String8.h>
diff --git a/services/surfaceflinger/RenderEngine/ProgramCache.cpp b/services/surfaceflinger/RenderEngine/ProgramCache.cpp
index 09b0ddc..d130506 100644
--- a/services/surfaceflinger/RenderEngine/ProgramCache.cpp
+++ b/services/surfaceflinger/RenderEngine/ProgramCache.cpp
@@ -169,7 +169,8 @@
fs << "gl_FragColor.rgb = gl_FragColor.rgb/gl_FragColor.a;";
}
fs << "gl_FragColor.rgb = pow(gl_FragColor.rgb, vec3(2.2));";
- fs << "gl_FragColor = colorMatrix*gl_FragColor;";
+ fs << "vec4 transformed = colorMatrix * vec4(gl_FragColor.rgb, 1);";
+ fs << "gl_FragColor.rgb = transformed.rgb/transformed.a;";
fs << "gl_FragColor.rgb = pow(gl_FragColor.rgb, vec3(1.0 / 2.2));";
if (!needs.isOpaque() && needs.isPremultiplied()) {
// and re-premultiply if needed after gamma correction
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 9d94c87..189049c 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -152,7 +152,8 @@
mBootFinished(false),
mPrimaryHWVsyncEnabled(false),
mHWVsyncAvailable(false),
- mDaltonize(false)
+ mDaltonize(false),
+ mHasColorMatrix(false)
{
ALOGI("SurfaceFlinger is starting");
@@ -1062,7 +1063,7 @@
for (size_t i=0 ; cur!=end && i<count ; ++i, ++cur) {
const sp<Layer>& layer(currentLayers[i]);
layer->setGeometry(hw, *cur);
- if (mDebugDisableHWC || mDebugRegion || mDaltonize) {
+ if (mDebugDisableHWC || mDebugRegion || mDaltonize || mHasColorMatrix) {
cur->setSkip(true);
}
}
@@ -1682,11 +1683,15 @@
}
}
- if (CC_LIKELY(!mDaltonize)) {
+ if (CC_LIKELY(!mDaltonize && !mHasColorMatrix)) {
doComposeSurfaces(hw, dirtyRegion);
} else {
RenderEngine& engine(getRenderEngine());
- engine.beginGroup(mDaltonizer());
+ mat4 colorMatrix = mColorMatrix;
+ if (mDaltonize) {
+ colorMatrix = colorMatrix * mDaltonizer();
+ }
+ engine.beginGroup(colorMatrix);
doComposeSurfaces(hw, dirtyRegion);
engine.endGroup();
}
@@ -2575,7 +2580,8 @@
colorizer.reset(result);
result.appendFormat(" h/w composer %s and %s\n",
hwc.initCheck()==NO_ERROR ? "present" : "not present",
- (mDebugDisableHWC || mDebugRegion || mDaltonize) ? "disabled" : "enabled");
+ (mDebugDisableHWC || mDebugRegion || mDaltonize
+ || mHasColorMatrix) ? "disabled" : "enabled");
hwc.dump(result);
/*
@@ -2738,8 +2744,28 @@
mDaltonize = n > 0;
invalidateHwcGeometry();
repaintEverything();
+ return NO_ERROR;
}
- return NO_ERROR;
+ case 1015: {
+ // apply a color matrix
+ n = data.readInt32();
+ mHasColorMatrix = n ? 1 : 0;
+ if (n) {
+ // color matrix is sent as mat3 matrix followed by vec3
+ // offset, then packed into a mat4 where the last row is
+ // the offset and extra values are 0
+ for (size_t i = 0 ; i < 4; i++) {
+ for (size_t j = 0; j < 4; j++) {
+ mColorMatrix[i][j] = data.readFloat();
+ }
+ }
+ } else {
+ mColorMatrix = mat4();
+ }
+ invalidateHwcGeometry();
+ repaintEverything();
+ return NO_ERROR;
+ }
}
}
return err;
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index f08e66a..2e75f1c 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -38,6 +38,7 @@
#include <binder/IMemory.h>
#include <ui/PixelFormat.h>
+#include <ui/mat4.h>
#include <gui/ISurfaceComposer.h>
#include <gui/ISurfaceComposerClient.h>
@@ -478,6 +479,9 @@
Daltonizer mDaltonizer;
bool mDaltonize;
+
+ mat4 mColorMatrix;
+ bool mHasColorMatrix;
};
}; // namespace android