diff options
32 files changed, 2039 insertions, 3815 deletions
diff --git a/include/utils/backup_helpers.h b/include/utils/BackupHelpers.h index 24b6c9e60b..24b6c9e60b 100644 --- a/include/utils/backup_helpers.h +++ b/include/utils/BackupHelpers.h diff --git a/include/utils/LogSocket.h b/include/utils/LogSocket.h deleted file mode 100644 index 01fbfb50e3..0000000000 --- a/include/utils/LogSocket.h +++ /dev/null @@ -1,20 +0,0 @@ -/* utils/LogSocket.h -** -** Copyright 2008, The Android Open Source Project -** -** This file is dual licensed. It may be redistributed and/or modified -** under the terms of the Apache 2.0 License OR version 2 of the GNU -** General Public License. -*/ - -#ifndef _UTILS_LOGSOCKET_H -#define _UTILS_LOGSOCKET_H - -#define SOCKET_CLOSE_LOCAL 0 - -void add_send_stats(int fd, int send); -void add_recv_stats(int fd, int recv); -void log_socket_close(int fd, short reason); -void log_socket_connect(int fd, unsigned int ip, unsigned short port); - -#endif /* _UTILS_LOGSOCKET_H */ diff --git a/include/utils/Socket.h b/include/utils/Socket.h deleted file mode 100644 index 8b7f406179..0000000000 --- a/include/utils/Socket.h +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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. - */ - -// -// Socket class. Modeled after Java classes. -// -#ifndef _RUNTIME_SOCKET_H -#define _RUNTIME_SOCKET_H - -#include <utils/inet_address.h> -#include <sys/types.h> - -namespace android { - -/* - * Basic socket class, needed to abstract away the differences between - * BSD sockets and WinSock. This establishes a streaming network - * connection (TCP/IP) to somebody. - */ -class Socket { -public: - Socket(void); - ~Socket(void); - - // Create a connection to somewhere. - // Return 0 on success. - int connect(const char* host, int port); - int connect(const InetAddress* addr, int port); - - - // Close the socket. Don't try to use this object again after - // calling this. Returns false on failure. - bool close(void); - - // If we created the socket without an address, we can use these - // to finish the connection. Returns 0 on success. - int bind(const SocketAddress& bindPoint); - int connect(const SocketAddress& endPoint); - - // Here we deviate from the traditional object-oriented fanciness - // and just provide read/write operators instead of getters for - // objects that abstract a stream. - // - // Standard read/write semantics. - int read(void* buf, ssize_t len) const; - int write(const void* buf, ssize_t len) const; - - // This must be called once, at program startup. - static bool bootInit(void); - static void finalShutdown(void); - -private: - // Internal function that establishes a connection. - int doConnect(const InetSocketAddress& addr); - - unsigned long mSock; // holds SOCKET or int - - static bool mBootInitialized; -}; - - -// debug -- unit tests -void TestSockets(void); - -}; // namespace android - -#endif // _RUNTIME_SOCKET_H diff --git a/include/utils/StringArray.h b/include/utils/StringArray.h new file mode 100644 index 0000000000..c244587181 --- /dev/null +++ b/include/utils/StringArray.h @@ -0,0 +1,83 @@ +/* + * 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. + */ + +// +// Sortable array of strings. STL-ish, but STL-free. +// +#ifndef _LIBS_UTILS_STRING_ARRAY_H +#define _LIBS_UTILS_STRING_ARRAY_H + +#include <stdlib.h> +#include <string.h> + +namespace android { + +// +// An expanding array of strings. Add, get, sort, delete. +// +class StringArray { +public: + StringArray(); + virtual ~StringArray(); + + // + // Add a string. A copy of the string is made. + // + bool push_back(const char* str); + + // + // Delete an entry. + // + void erase(int idx); + + // + // Sort the array. + // + void sort(int (*compare)(const void*, const void*)); + + // + // Pass this to the sort routine to do an ascending alphabetical sort. + // + static int cmpAscendingAlpha(const void* pstr1, const void* pstr2); + + // + // Get the #of items in the array. + // + inline int size(void) const { return mCurrent; } + + // + // Return entry N. + // [should use operator[] here] + // + const char* getEntry(int idx) const { + return (unsigned(idx) >= unsigned(mCurrent)) ? NULL : mArray[idx]; + } + + // + // Set entry N to specified string. + // [should use operator[] here] + // + void setEntry(int idx, const char* str); + +private: + int mMax; + int mCurrent; + char** mArray; +}; + +}; // namespace android + +#endif // _LIBS_UTILS_STRING_ARRAY_H diff --git a/include/utils/ZipEntry.h b/include/utils/ZipEntry.h deleted file mode 100644 index e4698dfbbc..0000000000 --- a/include/utils/ZipEntry.h +++ /dev/null @@ -1,345 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// -// Zip archive entries. -// -// The ZipEntry class is tightly meshed with the ZipFile class. -// -#ifndef __LIBS_ZIPENTRY_H -#define __LIBS_ZIPENTRY_H - -#include "Errors.h" - -#include <stdlib.h> -#include <stdio.h> - -namespace android { - -class ZipFile; - -/* - * ZipEntry objects represent a single entry in a Zip archive. - * - * You can use one of these to get or set information about an entry, but - * there are no functions here for accessing the data itself. (We could - * tuck a pointer to the ZipFile in here for convenience, but that raises - * the likelihood of using ZipEntry objects after discarding the ZipFile.) - * - * File information is stored in two places: next to the file data (the Local - * File Header, and possibly a Data Descriptor), and at the end of the file - * (the Central Directory Entry). The two must be kept in sync. - */ -class ZipEntry { -public: - friend class ZipFile; - - ZipEntry(void) - : mDeleted(false), mMarked(false) - {} - ~ZipEntry(void) {} - - /* - * Returns "true" if the data is compressed. - */ - bool isCompressed(void) const { - return mCDE.mCompressionMethod != kCompressStored; - } - int getCompressionMethod(void) const { return mCDE.mCompressionMethod; } - - /* - * Return the uncompressed length. - */ - off_t getUncompressedLen(void) const { return mCDE.mUncompressedSize; } - - /* - * Return the compressed length. For uncompressed data, this returns - * the same thing as getUncompresesdLen(). - */ - off_t getCompressedLen(void) const { return mCDE.mCompressedSize; } - - /* - * Return the absolute file offset of the start of the compressed or - * uncompressed data. - */ - off_t getFileOffset(void) const { - return mCDE.mLocalHeaderRelOffset + - LocalFileHeader::kLFHLen + - mLFH.mFileNameLength + - mLFH.mExtraFieldLength; - } - - /* - * Return the data CRC. - */ - unsigned long getCRC32(void) const { return mCDE.mCRC32; } - - /* - * Return file modification time in UNIX seconds-since-epoch. - */ - time_t getModWhen(void) const; - - /* - * Return the archived file name. - */ - const char* getFileName(void) const { return (const char*) mCDE.mFileName; } - - /* - * Application-defined "mark". Can be useful when synchronizing the - * contents of an archive with contents on disk. - */ - bool getMarked(void) const { return mMarked; } - void setMarked(bool val) { mMarked = val; } - - /* - * Some basic functions for raw data manipulation. "LE" means - * Little Endian. - */ - static inline unsigned short getShortLE(const unsigned char* buf) { - return buf[0] | (buf[1] << 8); - } - static inline unsigned long getLongLE(const unsigned char* buf) { - return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); - } - static inline void putShortLE(unsigned char* buf, short val) { - buf[0] = (unsigned char) val; - buf[1] = (unsigned char) (val >> 8); - } - static inline void putLongLE(unsigned char* buf, long val) { - buf[0] = (unsigned char) val; - buf[1] = (unsigned char) (val >> 8); - buf[2] = (unsigned char) (val >> 16); - buf[3] = (unsigned char) (val >> 24); - } - - /* defined for Zip archives */ - enum { - kCompressStored = 0, // no compression - // shrunk = 1, - // reduced 1 = 2, - // reduced 2 = 3, - // reduced 3 = 4, - // reduced 4 = 5, - // imploded = 6, - // tokenized = 7, - kCompressDeflated = 8, // standard deflate - // Deflate64 = 9, - // lib imploded = 10, - // reserved = 11, - // bzip2 = 12, - }; - - /* - * Deletion flag. If set, the entry will be removed on the next - * call to "flush". - */ - bool getDeleted(void) const { return mDeleted; } - -protected: - /* - * Initialize the structure from the file, which is pointing at - * our Central Directory entry. - */ - status_t initFromCDE(FILE* fp); - - /* - * Initialize the structure for a new file. We need the filename - * and comment so that we can properly size the LFH area. The - * filename is mandatory, the comment is optional. - */ - void initNew(const char* fileName, const char* comment); - - /* - * Initialize the structure with the contents of a ZipEntry from - * another file. - */ - status_t initFromExternal(const ZipFile* pZipFile, const ZipEntry* pEntry); - - /* - * Add some pad bytes to the LFH. We do this by adding or resizing - * the "extra" field. - */ - status_t addPadding(int padding); - - /* - * Set information about the data for this entry. - */ - void setDataInfo(long uncompLen, long compLen, unsigned long crc32, - int compressionMethod); - - /* - * Set the modification date. - */ - void setModWhen(time_t when); - - /* - * Return the offset of the local file header. - */ - off_t getLFHOffset(void) const { return mCDE.mLocalHeaderRelOffset; } - - /* - * Set the offset of the local file header, relative to the start of - * the current file. - */ - void setLFHOffset(off_t offset) { - mCDE.mLocalHeaderRelOffset = (long) offset; - } - - /* mark for deletion; used by ZipFile::remove() */ - void setDeleted(void) { mDeleted = true; } - -private: - /* these are private and not defined */ - ZipEntry(const ZipEntry& src); - ZipEntry& operator=(const ZipEntry& src); - - /* returns "true" if the CDE and the LFH agree */ - bool compareHeaders(void) const; - void copyCDEtoLFH(void); - - bool mDeleted; // set if entry is pending deletion - bool mMarked; // app-defined marker - - /* - * Every entry in the Zip archive starts off with one of these. - */ - class LocalFileHeader { - public: - LocalFileHeader(void) : - mVersionToExtract(0), - mGPBitFlag(0), - mCompressionMethod(0), - mLastModFileTime(0), - mLastModFileDate(0), - mCRC32(0), - mCompressedSize(0), - mUncompressedSize(0), - mFileNameLength(0), - mExtraFieldLength(0), - mFileName(NULL), - mExtraField(NULL) - {} - virtual ~LocalFileHeader(void) { - delete[] mFileName; - delete[] mExtraField; - } - - status_t read(FILE* fp); - status_t write(FILE* fp); - - // unsigned long mSignature; - unsigned short mVersionToExtract; - unsigned short mGPBitFlag; - unsigned short mCompressionMethod; - unsigned short mLastModFileTime; - unsigned short mLastModFileDate; - unsigned long mCRC32; - unsigned long mCompressedSize; - unsigned long mUncompressedSize; - unsigned short mFileNameLength; - unsigned short mExtraFieldLength; - unsigned char* mFileName; - unsigned char* mExtraField; - - enum { - kSignature = 0x04034b50, - kLFHLen = 30, // LocalFileHdr len, excl. var fields - }; - - void dump(void) const; - }; - - /* - * Every entry in the Zip archive has one of these in the "central - * directory" at the end of the file. - */ - class CentralDirEntry { - public: - CentralDirEntry(void) : - mVersionMadeBy(0), - mVersionToExtract(0), - mGPBitFlag(0), - mCompressionMethod(0), - mLastModFileTime(0), - mLastModFileDate(0), - mCRC32(0), - mCompressedSize(0), - mUncompressedSize(0), - mFileNameLength(0), - mExtraFieldLength(0), - mFileCommentLength(0), - mDiskNumberStart(0), - mInternalAttrs(0), - mExternalAttrs(0), - mLocalHeaderRelOffset(0), - mFileName(NULL), - mExtraField(NULL), - mFileComment(NULL) - {} - virtual ~CentralDirEntry(void) { - delete[] mFileName; - delete[] mExtraField; - delete[] mFileComment; - } - - status_t read(FILE* fp); - status_t write(FILE* fp); - - // unsigned long mSignature; - unsigned short mVersionMadeBy; - unsigned short mVersionToExtract; - unsigned short mGPBitFlag; - unsigned short mCompressionMethod; - unsigned short mLastModFileTime; - unsigned short mLastModFileDate; - unsigned long mCRC32; - unsigned long mCompressedSize; - unsigned long mUncompressedSize; - unsigned short mFileNameLength; - unsigned short mExtraFieldLength; - unsigned short mFileCommentLength; - unsigned short mDiskNumberStart; - unsigned short mInternalAttrs; - unsigned long mExternalAttrs; - unsigned long mLocalHeaderRelOffset; - unsigned char* mFileName; - unsigned char* mExtraField; - unsigned char* mFileComment; - - void dump(void) const; - - enum { - kSignature = 0x02014b50, - kCDELen = 46, // CentralDirEnt len, excl. var fields - }; - }; - - enum { - //kDataDescriptorSignature = 0x08074b50, // currently unused - kDataDescriptorLen = 16, // four 32-bit fields - - kDefaultVersion = 20, // need deflate, nothing much else - kDefaultMadeBy = 0x0317, // 03=UNIX, 17=spec v2.3 - kUsesDataDescr = 0x0008, // GPBitFlag bit 3 - }; - - LocalFileHeader mLFH; - CentralDirEntry mCDE; -}; - -}; // namespace android - -#endif // __LIBS_ZIPENTRY_H diff --git a/include/utils/ZipFile.h b/include/utils/ZipFile.h deleted file mode 100644 index 44df5bbaa4..0000000000 --- a/include/utils/ZipFile.h +++ /dev/null @@ -1,269 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// -// General-purpose Zip archive access. This class allows both reading and -// writing to Zip archives, including deletion of existing entries. -// -#ifndef __LIBS_ZIPFILE_H -#define __LIBS_ZIPFILE_H - -#include "ZipEntry.h" -#include "Vector.h" -#include "Errors.h" -#include <stdio.h> - -namespace android { - -/* - * Manipulate a Zip archive. - * - * Some changes will not be visible in the until until "flush" is called. - * - * The correct way to update a file archive is to make all changes to a - * copy of the archive in a temporary file, and then unlink/rename over - * the original after everything completes. Because we're only interested - * in using this for packaging, we don't worry about such things. Crashing - * after making changes and before flush() completes could leave us with - * an unusable Zip archive. - */ -class ZipFile { -public: - ZipFile(void) - : mZipFp(NULL), mReadOnly(false), mNeedCDRewrite(false) - {} - ~ZipFile(void) { - if (!mReadOnly) - flush(); - if (mZipFp != NULL) - fclose(mZipFp); - discardEntries(); - } - - /* - * Open a new or existing archive. - */ - typedef enum { - kOpenReadOnly = 0x01, - kOpenReadWrite = 0x02, - kOpenCreate = 0x04, // create if it doesn't exist - kOpenTruncate = 0x08, // if it exists, empty it - }; - status_t open(const char* zipFileName, int flags); - - /* - * Add a file to the end of the archive. Specify whether you want the - * library to try to store it compressed. - * - * If "storageName" is specified, the archive will use that instead - * of "fileName". - * - * If there is already an entry with the same name, the call fails. - * Existing entries with the same name must be removed first. - * - * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. - */ - status_t add(const char* fileName, int compressionMethod, - ZipEntry** ppEntry) - { - return add(fileName, fileName, compressionMethod, ppEntry); - } - status_t add(const char* fileName, const char* storageName, - int compressionMethod, ZipEntry** ppEntry) - { - return addCommon(fileName, NULL, 0, storageName, - ZipEntry::kCompressStored, - compressionMethod, ppEntry); - } - - /* - * Add a file that is already compressed with gzip. - * - * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. - */ - status_t addGzip(const char* fileName, const char* storageName, - ZipEntry** ppEntry) - { - return addCommon(fileName, NULL, 0, storageName, - ZipEntry::kCompressDeflated, - ZipEntry::kCompressDeflated, ppEntry); - } - - /* - * Add a file from an in-memory data buffer. - * - * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. - */ - status_t add(const void* data, size_t size, const char* storageName, - int compressionMethod, ZipEntry** ppEntry) - { - return addCommon(NULL, data, size, storageName, - ZipEntry::kCompressStored, - compressionMethod, ppEntry); - } - - /* - * Add an entry by copying it from another zip file. If "padding" is - * nonzero, the specified number of bytes will be added to the "extra" - * field in the header. - * - * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. - */ - status_t add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry, - int padding, ZipEntry** ppEntry); - - /* - * Mark an entry as having been removed. It is not actually deleted - * from the archive or our internal data structures until flush() is - * called. - */ - status_t remove(ZipEntry* pEntry); - - /* - * Flush changes. If mNeedCDRewrite is set, this writes the central dir. - */ - status_t flush(void); - - /* - * Expand the data into the buffer provided. The buffer must hold - * at least <uncompressed len> bytes. Variation expands directly - * to a file. - * - * Returns "false" if an error was encountered in the compressed data. - */ - //bool uncompress(const ZipEntry* pEntry, void* buf) const; - //bool uncompress(const ZipEntry* pEntry, FILE* fp) const; - void* uncompress(const ZipEntry* pEntry); - - /* - * Get an entry, by name. Returns NULL if not found. - * - * Does not return entries pending deletion. - */ - ZipEntry* getEntryByName(const char* fileName) const; - - /* - * Get the Nth entry in the archive. - * - * This will return an entry that is pending deletion. - */ - int getNumEntries(void) const { return mEntries.size(); } - ZipEntry* getEntryByIndex(int idx) const; - -private: - /* these are private and not defined */ - ZipFile(const ZipFile& src); - ZipFile& operator=(const ZipFile& src); - - class EndOfCentralDir { - public: - EndOfCentralDir(void) : - mDiskNumber(0), - mDiskWithCentralDir(0), - mNumEntries(0), - mTotalNumEntries(0), - mCentralDirSize(0), - mCentralDirOffset(0), - mCommentLen(0), - mComment(NULL) - {} - virtual ~EndOfCentralDir(void) { - delete[] mComment; - } - - status_t readBuf(const unsigned char* buf, int len); - status_t write(FILE* fp); - - //unsigned long mSignature; - unsigned short mDiskNumber; - unsigned short mDiskWithCentralDir; - unsigned short mNumEntries; - unsigned short mTotalNumEntries; - unsigned long mCentralDirSize; - unsigned long mCentralDirOffset; // offset from first disk - unsigned short mCommentLen; - unsigned char* mComment; - - enum { - kSignature = 0x06054b50, - kEOCDLen = 22, // EndOfCentralDir len, excl. comment - - kMaxCommentLen = 65535, // longest possible in ushort - kMaxEOCDSearch = kMaxCommentLen + EndOfCentralDir::kEOCDLen, - - }; - - void dump(void) const; - }; - - - /* read all entries in the central dir */ - status_t readCentralDir(void); - - /* crunch deleted entries out */ - status_t crunchArchive(void); - - /* clean up mEntries */ - void discardEntries(void); - - /* common handler for all "add" functions */ - status_t addCommon(const char* fileName, const void* data, size_t size, - const char* storageName, int sourceType, int compressionMethod, - ZipEntry** ppEntry); - - /* copy all of "srcFp" into "dstFp" */ - status_t copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32); - /* copy all of "data" into "dstFp" */ - status_t copyDataToFp(FILE* dstFp, - const void* data, size_t size, unsigned long* pCRC32); - /* copy some of "srcFp" into "dstFp" */ - status_t copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length, - unsigned long* pCRC32); - /* like memmove(), but on parts of a single file */ - status_t filemove(FILE* fp, off_t dest, off_t src, size_t n); - /* compress all of "srcFp" into "dstFp", using Deflate */ - status_t compressFpToFp(FILE* dstFp, FILE* srcFp, - const void* data, size_t size, unsigned long* pCRC32); - - /* get modification date from a file descriptor */ - time_t getModTime(int fd); - - /* - * We use stdio FILE*, which gives us buffering but makes dealing - * with files >2GB awkward. Until we support Zip64, we're fine. - */ - FILE* mZipFp; // Zip file pointer - - /* one of these per file */ - EndOfCentralDir mEOCD; - - /* did we open this read-only? */ - bool mReadOnly; - - /* set this when we trash the central dir */ - bool mNeedCDRewrite; - - /* - * One ZipEntry per entry in the zip file. I'm using pointers instead - * of objects because it's easier than making operator= work for the - * classes and sub-classes. - */ - Vector<ZipEntry*> mEntries; -}; - -}; // namespace android - -#endif // __LIBS_ZIPFILE_H diff --git a/include/utils/inet_address.h b/include/utils/inet_address.h deleted file mode 100644 index dbd8672e0b..0000000000 --- a/include/utils/inet_address.h +++ /dev/null @@ -1,103 +0,0 @@ -/* - * 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. - */ - -// -// Internet address classes. Modeled after Java classes. -// -#ifndef _RUNTIME_INET_ADDRESS_H -#define _RUNTIME_INET_ADDRESS_H - -#ifdef HAVE_ANDROID_OS -#error DO NOT USE THIS FILE IN THE DEVICE BUILD -#endif - - -namespace android { - -/* - * This class holds Internet addresses. Perhaps more useful is its - * ability to look up addresses by name. - * - * Invoke one of the static factory methods to create a new object. - */ -class InetAddress { -public: - virtual ~InetAddress(void); - - // create from w.x.y.z or foo.bar.com notation - static InetAddress* getByName(const char* host); - - // copy-construction - InetAddress(const InetAddress& orig); - - const void* getAddress(void) const { return mAddress; } - int getAddressLength(void) const { return mLength; } - const char* getHostName(void) const { return mName; } - -private: - InetAddress(void); - // assignment (private) - InetAddress& operator=(const InetAddress& addr); - - // use a void* here so we don't have to expose actual socket headers - void* mAddress; // this is really a ptr to sockaddr_in - int mLength; - char* mName; -}; - - -/* - * Base class for socket addresses. - */ -class SocketAddress { -public: - SocketAddress() {} - virtual ~SocketAddress() {} -}; - - -/* - * Internet address class. This combines an InetAddress with a port. - */ -class InetSocketAddress : public SocketAddress { -public: - InetSocketAddress() : - mAddress(0), mPort(-1) - {} - ~InetSocketAddress(void) { - delete mAddress; - } - - // Create an address with a host wildcard (useful for servers). - bool create(int port); - // Create an address with the specified host and port. - bool create(const InetAddress* addr, int port); - // Create an address with the specified host and port. Does the - // hostname lookup. - bool create(const char* host, int port); - - const InetAddress* getAddress(void) const { return mAddress; } - const int getPort(void) const { return mPort; } - const char* getHostName(void) const { return mAddress->getHostName(); } - -private: - InetAddress* mAddress; - int mPort; -}; - -}; // namespace android - -#endif // _RUNTIME_INET_ADDRESS_H diff --git a/include/utils/string_array.h b/include/utils/string_array.h deleted file mode 100644 index 064dda2248..0000000000 --- a/include/utils/string_array.h +++ /dev/null @@ -1,135 +0,0 @@ -/* - * 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. - */ - -// -// Sortable array of strings. STL-ish, but STL-free. -// -#ifndef _LIBS_UTILS_STRING_ARRAY_H -#define _LIBS_UTILS_STRING_ARRAY_H - -#include <stdlib.h> -#include <string.h> - -namespace android { - -// -// An expanding array of strings. Add, get, sort, delete. -// -class StringArray { -public: - StringArray() - : mMax(0), mCurrent(0), mArray(NULL) - {} - virtual ~StringArray() { - for (int i = 0; i < mCurrent; i++) - delete[] mArray[i]; - delete[] mArray; - } - - // - // Add a string. A copy of the string is made. - // - bool push_back(const char* str) { - if (mCurrent >= mMax) { - char** tmp; - - if (mMax == 0) - mMax = 16; // initial storage - else - mMax *= 2; - - tmp = new char*[mMax]; - if (tmp == NULL) - return false; - - memcpy(tmp, mArray, mCurrent * sizeof(char*)); - delete[] mArray; - mArray = tmp; - } - - int len = strlen(str); - mArray[mCurrent] = new char[len+1]; - memcpy(mArray[mCurrent], str, len+1); - mCurrent++; - - return true; - } - - // - // Delete an entry. - // - void erase(int idx) { - if (idx < 0 || idx >= mCurrent) - return; - delete[] mArray[idx]; - if (idx < mCurrent-1) { - memmove(&mArray[idx], &mArray[idx+1], - (mCurrent-1 - idx) * sizeof(char*)); - } - mCurrent--; - } - - // - // Sort the array. - // - void sort(int (*compare)(const void*, const void*)) { - qsort(mArray, mCurrent, sizeof(char*), compare); - } - - // - // Pass this to the sort routine to do an ascending alphabetical sort. - // - static int cmpAscendingAlpha(const void* pstr1, const void* pstr2) { - return strcmp(*(const char**)pstr1, *(const char**)pstr2); - } - - // - // Get the #of items in the array. - // - inline int size(void) const { return mCurrent; } - - // - // Return entry N. - // [should use operator[] here] - // - const char* getEntry(int idx) const { - if (idx < 0 || idx >= mCurrent) - return NULL; - return mArray[idx]; - } - - // - // Set entry N to specified string. - // [should use operator[] here] - // - void setEntry(int idx, const char* str) { - if (idx < 0 || idx >= mCurrent) - return; - delete[] mArray[idx]; - int len = strlen(str); - mArray[idx] = new char[len+1]; - memcpy(mArray[idx], str, len+1); - } - -private: - int mMax; - int mCurrent; - char** mArray; -}; - -}; // namespace android - -#endif // _LIBS_UTILS_STRING_ARRAY_H diff --git a/libs/utils/Android.mk b/libs/utils/Android.mk index 1acc1a43aa..3f5cb85ac8 100644 --- a/libs/utils/Android.mk +++ b/libs/utils/Android.mk @@ -32,6 +32,7 @@ commonSources:= \ StopWatch.cpp \ String8.cpp \ String16.cpp \ + StringArray.cpp \ SystemClock.cpp \ TextOutput.cpp \ Threads.cpp \ @@ -40,29 +41,15 @@ commonSources:= \ ZipFileCRO.cpp \ ZipFileRO.cpp \ ZipUtils.cpp \ - misc.cpp \ - LogSocket.cpp + misc.cpp -# -# The cpp files listed here do not belong in the device -# build. Consult with the swetland before even thinking about -# putting them in commonSources. -# -# They're used by the simulator runtime and by host-side tools like -# aapt and the simulator front-end. -# -hostSources:= \ - InetAddress.cpp \ - Socket.cpp \ - ZipEntry.cpp \ - ZipFile.cpp # For the host # ===================================================== include $(CLEAR_VARS) -LOCAL_SRC_FILES:= $(commonSources) $(hostSources) +LOCAL_SRC_FILES:= $(commonSources) ifeq ($(HOST_OS),linux) # Use the futex based mutex and condition variable @@ -96,12 +83,8 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ $(commonSources) \ Unicode.cpp \ - backup_data.cpp \ - backup_helper_file.cpp - -ifeq ($(TARGET_SIMULATOR),true) -LOCAL_SRC_FILES += $(hostSources) -endif + BackupData.cpp \ + BackupHelpers.cpp ifeq ($(TARGET_OS),linux) # Use the futex based mutex and condition variable @@ -129,9 +112,5 @@ endif # linux-x86 endif # sim LOCAL_MODULE:= libutils - -#LOCAL_CFLAGS+= -#LOCAL_LDFLAGS:= - include $(BUILD_SHARED_LIBRARY) diff --git a/libs/utils/backup_data.cpp b/libs/utils/BackupData.cpp index 95c05b7edb..120f23d362 100644 --- a/libs/utils/backup_data.cpp +++ b/libs/utils/BackupData.cpp @@ -16,7 +16,7 @@ #define LOG_TAG "backup_data" -#include <utils/backup_helpers.h> +#include <utils/BackupHelpers.h> #include <utils/ByteOrder.h> #include <stdio.h> diff --git a/libs/utils/backup_helper_file.cpp b/libs/utils/BackupHelpers.cpp index 7ec2ce8653..7f423a8e8e 100644 --- a/libs/utils/backup_helper_file.cpp +++ b/libs/utils/BackupHelpers.cpp @@ -16,7 +16,7 @@ #define LOG_TAG "file_backup_helper" -#include <utils/backup_helpers.h> +#include <utils/BackupHelpers.h> #include <utils/KeyedVector.h> #include <utils/ByteOrder.h> @@ -350,10 +350,11 @@ back_up_files(int oldSnapshotFD, BackupDataWriter* dataStream, int newSnapshotFD FileState& g = newSnapshot.editValueAt(m); int fd = open(realFilename.string(), O_RDONLY); - if (fd != -1) { + 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", realFilename.string()); } else { g.crc32 = compute_crc32(fd); diff --git a/libs/utils/characterData.h b/libs/utils/CharacterData.h index e931d995e5..e931d995e5 100644 --- a/libs/utils/characterData.h +++ b/libs/utils/CharacterData.h diff --git a/libs/utils/InetAddress.cpp b/libs/utils/InetAddress.cpp deleted file mode 100644 index 39a0a6839a..0000000000 --- a/libs/utils/InetAddress.cpp +++ /dev/null @@ -1,236 +0,0 @@ -/* - * 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. - */ - -// -// Internet address class. -// -#ifdef HAVE_WINSOCK -# include <winsock2.h> -#else -# include <sys/types.h> -# include <sys/socket.h> -# include <netinet/in.h> -//# include <arpa/inet.h> -# include <netdb.h> -#endif - -#include <utils/inet_address.h> -#include <utils/threads.h> -#include <utils/Log.h> - -#include <errno.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <assert.h> - -using namespace android; - - -/* - * =========================================================================== - * InetAddress - * =========================================================================== - */ - -// lock for the next couple of functions; could tuck into InetAddress -static Mutex* gGHBNLock; - -/* - * Lock/unlock access to the hostent struct returned by gethostbyname(). - */ -static inline void lock_gethostbyname(void) -{ - if (gGHBNLock == NULL) - gGHBNLock = new Mutex; - gGHBNLock->lock(); -} -static inline void unlock_gethostbyname(void) -{ - assert(gGHBNLock != NULL); - gGHBNLock->unlock(); -} - - -/* - * Constructor -- just init members. This is private so that callers - * are required to use getByName(). - */ -InetAddress::InetAddress(void) - : mAddress(NULL), mLength(-1), mName(NULL) -{ -} - -/* - * Destructor -- free address storage. - */ -InetAddress::~InetAddress(void) -{ - delete[] (char*) mAddress; - delete[] mName; -} - -/* - * Copy constructor. - */ -InetAddress::InetAddress(const InetAddress& orig) -{ - *this = orig; // use assignment code -} - -/* - * Assignment operator. - */ -InetAddress& InetAddress::operator=(const InetAddress& addr) -{ - // handle self-assignment - if (this == &addr) - return *this; - // copy mLength and mAddress - mLength = addr.mLength; - if (mLength > 0) { - mAddress = new char[mLength]; - memcpy(mAddress, addr.mAddress, mLength); - LOG(LOG_DEBUG, "socket", - "HEY: copied %d bytes in assignment operator\n", mLength); - } else { - mAddress = NULL; - } - // copy mName - mName = new char[strlen(addr.mName)+1]; - strcpy(mName, addr.mName); - - return *this; -} - -/* - * Create a new object from a name or a dotted-number IP notation. - * - * Returns NULL on failure. - */ -InetAddress* -InetAddress::getByName(const char* host) -{ - InetAddress* newAddr = NULL; - struct sockaddr_in addr; - struct hostent* he; - DurationTimer hostTimer, lockTimer; - - // gethostbyname() isn't reentrant, so we need to lock things until - // we can copy the data out. - lockTimer.start(); - lock_gethostbyname(); - hostTimer.start(); - - he = gethostbyname(host); - if (he == NULL) { - LOG(LOG_WARN, "socket", "WARNING: cannot resolve host %s\n", host); - unlock_gethostbyname(); - return NULL; - } - - memcpy(&addr.sin_addr, he->h_addr, he->h_length); - addr.sin_family = he->h_addrtype; - addr.sin_port = 0; - - // got it, unlock us - hostTimer.stop(); - he = NULL; - unlock_gethostbyname(); - - lockTimer.stop(); - if ((long) lockTimer.durationUsecs() > 100000) { - long lockTime = (long) lockTimer.durationUsecs(); - long hostTime = (long) hostTimer.durationUsecs(); - LOG(LOG_DEBUG, "socket", - "Lookup of %s took %.3fs (gethostbyname=%.3fs lock=%.3fs)\n", - host, lockTime / 1000000.0, hostTime / 1000000.0, - (lockTime - hostTime) / 1000000.0); - } - - // Alloc storage and copy it over. - newAddr = new InetAddress(); - if (newAddr == NULL) - return NULL; - - newAddr->mLength = sizeof(struct sockaddr_in); - newAddr->mAddress = new char[sizeof(struct sockaddr_in)]; - if (newAddr->mAddress == NULL) { - delete newAddr; - return NULL; - } - memcpy(newAddr->mAddress, &addr, newAddr->mLength); - - // Keep this for debug messages. - newAddr->mName = new char[strlen(host)+1]; - if (newAddr->mName == NULL) { - delete newAddr; - return NULL; - } - strcpy(newAddr->mName, host); - - return newAddr; -} - - -/* - * =========================================================================== - * InetSocketAddress - * =========================================================================== - */ - -/* - * Create an address with the host wildcard (INADDR_ANY). - */ -bool InetSocketAddress::create(int port) -{ - assert(mAddress == NULL); - - mAddress = InetAddress::getByName("0.0.0.0"); - if (mAddress == NULL) - return false; - mPort = port; - return true; -} - -/* - * Create address with host and port specified. - */ -bool InetSocketAddress::create(const InetAddress* addr, int port) -{ - assert(mAddress == NULL); - - mAddress = new InetAddress(*addr); // make a copy - if (mAddress == NULL) - return false; - mPort = port; - return true; -} - -/* - * Create address with host and port specified. - */ -bool InetSocketAddress::create(const char* host, int port) -{ - assert(mAddress == NULL); - - mAddress = InetAddress::getByName(host); - if (mAddress == NULL) - return false; - mPort = port; - return true; -} - diff --git a/libs/utils/LogSocket.cpp b/libs/utils/LogSocket.cpp deleted file mode 100644 index 55c1b99aff..0000000000 --- a/libs/utils/LogSocket.cpp +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - -#ifndef HAVE_WINSOCK -//#define SOCKETLOG -#endif - -#ifdef SOCKETLOG - -#define LOG_TAG "SOCKETLOG" - -#include <string.h> -#include <cutils/log.h> -#include "utils/LogSocket.h" -#include "utils/logger.h" -#include "cutils/hashmap.h" - -// defined in //device/data/etc/event-log-tags -#define SOCKET_CLOSE_LOG 51000 - -static Hashmap* statsMap = NULL; - -#define LOG_LIST_NUMBER 5 - -typedef struct SocketStats { - int fd; - unsigned int send; - unsigned int recv; - unsigned int ip; - unsigned short port; - short reason; -}SocketStats; - -SocketStats *get_socket_stats(int fd) { - if (statsMap == NULL) { - statsMap = hashmapCreate(8, &hashmapIntHash, &hashmapIntEquals); - } - - SocketStats *s = (SocketStats*) hashmapGet(statsMap, &fd); - if (s == NULL) { - // LOGD("create SocketStats for fd %d", fd); - s = (SocketStats*) malloc(sizeof(SocketStats)); - memset(s, 0, sizeof(SocketStats)); - s->fd = fd; - hashmapPut(statsMap, &s->fd, s); - } - return s; -} - -void log_socket_connect(int fd, unsigned int ip, unsigned short port) { - // LOGD("log_socket_connect for fd %d ip %d port%d", fd, ip, port); - SocketStats *s = get_socket_stats(fd); - s->ip = ip; - s->port = port; -} - -void add_send_stats(int fd, int send) { - if (send <=0) { - LOGE("add_send_stats send %d", send); - return; - } - SocketStats *s = get_socket_stats(fd); - s->send += send; - // LOGD("add_send_stats for fd %d ip %d port%d", fd, s->ip, s->port); -} - -void add_recv_stats(int fd, int recv) { - if (recv <=0) { - LOGE("add_recv_stats recv %d", recv); - return; - } - SocketStats *s = get_socket_stats(fd); - s->recv += recv; - // LOGD("add_recv_stats for fd %d ip %d port%d", fd, s->ip, s->port); -} - -char* put_int(char* buf, int value) { - *buf = EVENT_TYPE_INT; - buf++; - memcpy(buf, &value, sizeof(int)); - return buf + sizeof(int); -} - -void log_socket_close(int fd, short reason) { - if (statsMap) { - SocketStats *s = (SocketStats*) hashmapGet(statsMap, &fd); - if (s != NULL) { - if (s->send != 0 || s->recv != 0) { - s->reason = reason; - // 5 int + list type need 2 bytes - char buf[LOG_LIST_NUMBER * 5 + 2]; - buf[0] = EVENT_TYPE_LIST; - buf[1] = LOG_LIST_NUMBER; - char* writePos = buf + 2; - writePos = put_int(writePos, s->send); - writePos = put_int(writePos, s->recv); - writePos = put_int(writePos, s->ip); - writePos = put_int(writePos, s->port); - writePos = put_int(writePos, s->reason); - - android_bWriteLog(SOCKET_CLOSE_LOG, buf, sizeof(buf)); - // LOGD("send %d recv %d reason %d", s->send, s->recv, s->reason); - } - hashmapRemove(statsMap, &s->fd); - free(s); - } - } -} - -#else -void add_send_stats(int fd, int send) {} -void add_recv_stats(int fd, int recv) {} -void log_socket_close(int fd, short reason) {} -void log_socket_connect(int fd, unsigned int ip, unsigned short port) {} -#endif diff --git a/libs/utils/Socket.cpp b/libs/utils/Socket.cpp deleted file mode 100644 index 51509a3048..0000000000 --- a/libs/utils/Socket.cpp +++ /dev/null @@ -1,388 +0,0 @@ -/* - * 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. - */ - -// -// Internet address class. -// - -#ifdef HAVE_WINSOCK -// This needs to come first, or Cygwin gets concerned about a potential -// clash between WinSock and <sys/types.h>. -# include <winsock2.h> -#endif - -#include <utils/Socket.h> -#include <utils/inet_address.h> -#include <utils/Log.h> -#include <utils/Timers.h> - -#ifndef HAVE_WINSOCK -# include <sys/types.h> -# include <sys/socket.h> -# include <netinet/in.h> -# include <arpa/inet.h> -#endif - -#include <stdlib.h> -#include <stdio.h> -#include <unistd.h> -#include <string.h> -#include <errno.h> -#include <assert.h> - -using namespace android; - - -/* - * =========================================================================== - * Socket - * =========================================================================== - */ - -#ifndef INVALID_SOCKET -# define INVALID_SOCKET (-1) -#endif -#define UNDEF_SOCKET ((unsigned long) INVALID_SOCKET) - -/*static*/ bool Socket::mBootInitialized = false; - -/* - * Extract system-dependent error code. - */ -static inline int getSocketError(void) { -#ifdef HAVE_WINSOCK - return WSAGetLastError(); -#else - return errno; -#endif -} - -/* - * One-time initialization for socket code. - */ -/*static*/ bool Socket::bootInit(void) -{ -#ifdef HAVE_WINSOCK - WSADATA wsaData; - int err; - - err = WSAStartup(MAKEWORD(2, 0), &wsaData); - if (err != 0) { - LOG(LOG_ERROR, "socket", "Unable to start WinSock\n"); - return false; - } - - LOG(LOG_INFO, "socket", "Using WinSock v%d.%d\n", - LOBYTE(wsaData.wVersion), HIBYTE(wsaData.wVersion)); -#endif - - mBootInitialized = true; - return true; -} - -/* - * One-time shutdown for socket code. - */ -/*static*/ void Socket::finalShutdown(void) -{ -#ifdef HAVE_WINSOCK - WSACleanup(); -#endif - mBootInitialized = false; -} - - -/* - * Simple constructor. Allow the application to create us and then make - * bind/connect calls. - */ -Socket::Socket(void) - : mSock(UNDEF_SOCKET) -{ - if (!mBootInitialized) - LOG(LOG_WARN, "socket", "WARNING: sockets not initialized\n"); -} - -/* - * Destructor. Closes the socket and resets our storage. - */ -Socket::~Socket(void) -{ - close(); -} - - -/* - * Create a socket and connect to the specified host and port. - */ -int Socket::connect(const char* host, int port) -{ - if (mSock != UNDEF_SOCKET) { - LOG(LOG_WARN, "socket", "Socket already connected\n"); - return -1; - } - - InetSocketAddress sockAddr; - if (!sockAddr.create(host, port)) - return -1; - - //return doConnect(sockAddr); - int foo; - foo = doConnect(sockAddr); - return foo; -} - -/* - * Create a socket and connect to the specified host and port. - */ -int Socket::connect(const InetAddress* addr, int port) -{ - if (mSock != UNDEF_SOCKET) { - LOG(LOG_WARN, "socket", "Socket already connected\n"); - return -1; - } - - InetSocketAddress sockAddr; - if (!sockAddr.create(addr, port)) - return -1; - - return doConnect(sockAddr); -} - -/* - * Finish creating a socket by connecting to the remote host. - * - * Returns 0 on success. - */ -int Socket::doConnect(const InetSocketAddress& sockAddr) -{ -#ifdef HAVE_WINSOCK - SOCKET sock; -#else - int sock; -#endif - const InetAddress* addr = sockAddr.getAddress(); - int port = sockAddr.getPort(); - struct sockaddr_in inaddr; - DurationTimer connectTimer; - - assert(sizeof(struct sockaddr_in) == addr->getAddressLength()); - memcpy(&inaddr, addr->getAddress(), addr->getAddressLength()); - inaddr.sin_port = htons(port); - - //fprintf(stderr, "--- connecting to %s:%d\n", - // sockAddr.getHostName(), port); - - sock = ::socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); - if (sock == INVALID_SOCKET) { - int err = getSocketError(); - LOG(LOG_ERROR, "socket", "Unable to create socket (err=%d)\n", err); - return (err != 0) ? err : -1; - } - - connectTimer.start(); - - if (::connect(sock, (struct sockaddr*) &inaddr, sizeof(inaddr)) != 0) { - int err = getSocketError(); - LOG(LOG_WARN, "socket", "Connect to %s:%d failed: %d\n", - sockAddr.getHostName(), port, err); - return (err != 0) ? err : -1; - } - - connectTimer.stop(); - if ((long) connectTimer.durationUsecs() > 100000) { - LOG(LOG_INFO, "socket", - "Connect to %s:%d took %.3fs\n", sockAddr.getHostName(), - port, ((long) connectTimer.durationUsecs()) / 1000000.0); - } - - mSock = (unsigned long) sock; - LOG(LOG_VERBOSE, "socket", - "--- connected to %s:%d\n", sockAddr.getHostName(), port); - return 0; -} - - -/* - * Close the socket if it needs closing. - */ -bool Socket::close(void) -{ - if (mSock != UNDEF_SOCKET) { - //fprintf(stderr, "--- closing socket %lu\n", mSock); -#ifdef HAVE_WINSOCK - if (::closesocket((SOCKET) mSock) != 0) - return false; -#else - if (::close((int) mSock) != 0) - return false; -#endif - } - - mSock = UNDEF_SOCKET; - - return true; -} - -/* - * Read data from socket. - * - * Standard semantics: read up to "len" bytes into "buf". Returns the - * number of bytes read, or less than zero on error. - */ -int Socket::read(void* buf, ssize_t len) const -{ - if (mSock == UNDEF_SOCKET) { - LOG(LOG_ERROR, "socket", "ERROR: read on invalid socket\n"); - return -500; - } - -#ifdef HAVE_WINSOCK - SOCKET sock = (SOCKET) mSock; -#else - int sock = (int) mSock; -#endif - int cc; - - cc = recv(sock, (char*)buf, len, 0); - if (cc < 0) { - int err = getSocketError(); - return (err > 0) ? -err : -1; - } - - return cc; -} - -/* - * Write data to a socket. - * - * Standard semantics: write up to "len" bytes into "buf". Returns the - * number of bytes written, or less than zero on error. - */ -int Socket::write(const void* buf, ssize_t len) const -{ - if (mSock == UNDEF_SOCKET) { - LOG(LOG_ERROR, "socket", "ERROR: write on invalid socket\n"); - return -500; - } - -#ifdef HAVE_WINSOCK - SOCKET sock = (SOCKET) mSock; -#else - int sock = (int) mSock; -#endif - int cc; - - cc = send(sock, (const char*)buf, len, 0); - if (cc < 0) { - int err = getSocketError(); - return (err > 0) ? -err : -1; - } - - return cc; -} - - -/* - * =========================================================================== - * Socket tests - * =========================================================================== - */ - -/* - * Read all data from the socket. The data is read into a buffer that - * expands as needed. - * - * On exit, the buffer is returned, and the length of the data is stored - * in "*sz". A null byte is added to the end, but is not included in - * the length. - */ -static char* socketReadAll(const Socket& s, int *sz) -{ - int max, r; - char *data, *ptr, *tmp; - - data = (char*) malloc(max = 32768); - if (data == NULL) - return NULL; - - ptr = data; - - for (;;) { - if ((ptr - data) == max) { - tmp = (char*) realloc(data, max *= 2); - if(tmp == 0) { - free(data); - return 0; - } - } - r = s.read(ptr, max - (ptr - data)); - if (r == 0) - break; - if (r < 0) { - LOG(LOG_WARN, "socket", "WARNING: socket read failed (res=%d)\n",r); - break; - } - ptr += r; - } - - if ((ptr - data) == max) { - tmp = (char*) realloc(data, max + 1); - if (tmp == NULL) { - free(data); - return NULL; - } - } - *ptr = '\0'; - *sz = (ptr - data); - return data; -} - -/* - * Exercise the Socket class. - */ -void android::TestSockets(void) -{ - printf("----- SOCKET TEST ------\n"); - Socket::bootInit(); - - char* buf = NULL; - int len, cc; - const char* kTestStr = - "GET / HTTP/1.0\n" - "Connection: close\n" - "\n"; - - Socket sock; - if (sock.connect("www.google.com", 80) != 0) { - fprintf(stderr, "socket connected failed\n"); - goto bail; - } - - cc = sock.write(kTestStr, strlen(kTestStr)); - if (cc != (int) strlen(kTestStr)) { - fprintf(stderr, "write failed, res=%d\n", cc); - goto bail; - } - buf = socketReadAll(sock, &len); - - printf("GOT '%s'\n", buf); - -bail: - sock.close(); - free(buf); -} - diff --git a/libs/utils/StringArray.cpp b/libs/utils/StringArray.cpp new file mode 100644 index 0000000000..aa42d6837a --- /dev/null +++ b/libs/utils/StringArray.cpp @@ -0,0 +1,113 @@ +/* + * 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. + */ + +// +// Sortable array of strings. STL-ish, but STL-free. +// + +#include <stdlib.h> +#include <string.h> + +#include <utils/StringArray.h> + +namespace android { + +// +// An expanding array of strings. Add, get, sort, delete. +// +StringArray::StringArray() + : mMax(0), mCurrent(0), mArray(NULL) +{ +} + +StringArray:: ~StringArray() { + for (int i = 0; i < mCurrent; i++) + delete[] mArray[i]; + delete[] mArray; +} + +// +// Add a string. A copy of the string is made. +// +bool StringArray::push_back(const char* str) { + if (mCurrent >= mMax) { + char** tmp; + + if (mMax == 0) + mMax = 16; // initial storage + else + mMax *= 2; + + tmp = new char*[mMax]; + if (tmp == NULL) + return false; + + memcpy(tmp, mArray, mCurrent * sizeof(char*)); + delete[] mArray; + mArray = tmp; + } + + int len = strlen(str); + mArray[mCurrent] = new char[len+1]; + memcpy(mArray[mCurrent], str, len+1); + mCurrent++; + + return true; +} + +// +// Delete an entry. +// +void StringArray::erase(int idx) { + if (idx < 0 || idx >= mCurrent) + return; + delete[] mArray[idx]; + if (idx < mCurrent-1) { + memmove(&mArray[idx], &mArray[idx+1], + (mCurrent-1 - idx) * sizeof(char*)); + } + mCurrent--; +} + +// +// Sort the array. +// +void StringArray::sort(int (*compare)(const void*, const void*)) { + qsort(mArray, mCurrent, sizeof(char*), compare); +} + +// +// Pass this to the sort routine to do an ascending alphabetical sort. +// +int StringArray::cmpAscendingAlpha(const void* pstr1, const void* pstr2) { + return strcmp(*(const char**)pstr1, *(const char**)pstr2); +} + +// +// Set entry N to specified string. +// [should use operator[] here] +// +void StringArray::setEntry(int idx, const char* str) { + if (idx < 0 || idx >= mCurrent) + return; + delete[] mArray[idx]; + int len = strlen(str); + mArray[idx] = new char[len+1]; + memcpy(mArray[idx], str, len+1); +} + + +}; // namespace android diff --git a/libs/utils/Unicode.cpp b/libs/utils/Unicode.cpp index 33f535fd1a..f92703e7f0 100644 --- a/libs/utils/Unicode.cpp +++ b/libs/utils/Unicode.cpp @@ -14,11 +14,11 @@ * limitations under the License. */ -#include "utils/AndroidUnicode.h" -#include "characterData.h" +#include <utils/AndroidUnicode.h> +#include "CharacterData.h" #define LOG_TAG "Unicode" -#include "utils/Log.h" +#include <utils/Log.h> // ICU headers for using macros #include <unicode/utf16.h> diff --git a/libs/utils/ZipEntry.cpp b/libs/utils/ZipEntry.cpp deleted file mode 100644 index fbc9e67843..0000000000 --- a/libs/utils/ZipEntry.cpp +++ /dev/null @@ -1,696 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// -// Access to entries in a Zip archive. -// - -#define LOG_TAG "zip" - -#include "utils/ZipEntry.h" -#include "utils/Log.h" - -#include <stdio.h> -#include <string.h> -#include <assert.h> - -using namespace android; - -/* - * Initialize a new ZipEntry structure from a FILE* positioned at a - * CentralDirectoryEntry. - * - * On exit, the file pointer will be at the start of the next CDE or - * at the EOCD. - */ -status_t ZipEntry::initFromCDE(FILE* fp) -{ - status_t result; - long posn; - bool hasDD; - - //LOGV("initFromCDE ---\n"); - - /* read the CDE */ - result = mCDE.read(fp); - if (result != NO_ERROR) { - LOGD("mCDE.read failed\n"); - return result; - } - - //mCDE.dump(); - - /* using the info in the CDE, go load up the LFH */ - posn = ftell(fp); - if (fseek(fp, mCDE.mLocalHeaderRelOffset, SEEK_SET) != 0) { - LOGD("local header seek failed (%ld)\n", - mCDE.mLocalHeaderRelOffset); - return UNKNOWN_ERROR; - } - - result = mLFH.read(fp); - if (result != NO_ERROR) { - LOGD("mLFH.read failed\n"); - return result; - } - - if (fseek(fp, posn, SEEK_SET) != 0) - return UNKNOWN_ERROR; - - //mLFH.dump(); - - /* - * We *might* need to read the Data Descriptor at this point and - * integrate it into the LFH. If this bit is set, the CRC-32, - * compressed size, and uncompressed size will be zero. In practice - * these seem to be rare. - */ - hasDD = (mLFH.mGPBitFlag & kUsesDataDescr) != 0; - if (hasDD) { - // do something clever - //LOGD("+++ has data descriptor\n"); - } - - /* - * Sanity-check the LFH. Note that this will fail if the "kUsesDataDescr" - * flag is set, because the LFH is incomplete. (Not a problem, since we - * prefer the CDE values.) - */ - if (!hasDD && !compareHeaders()) { - LOGW("WARNING: header mismatch\n"); - // keep going? - } - - /* - * If the mVersionToExtract is greater than 20, we may have an - * issue unpacking the record -- could be encrypted, compressed - * with something we don't support, or use Zip64 extensions. We - * can defer worrying about that to when we're extracting data. - */ - - return NO_ERROR; -} - -/* - * Initialize a new entry. Pass in the file name and an optional comment. - * - * Initializes the CDE and the LFH. - */ -void ZipEntry::initNew(const char* fileName, const char* comment) -{ - assert(fileName != NULL && *fileName != '\0'); // name required - - /* most fields are properly initialized by constructor */ - mCDE.mVersionMadeBy = kDefaultMadeBy; - mCDE.mVersionToExtract = kDefaultVersion; - mCDE.mCompressionMethod = kCompressStored; - mCDE.mFileNameLength = strlen(fileName); - if (comment != NULL) - mCDE.mFileCommentLength = strlen(comment); - mCDE.mExternalAttrs = 0x81b60020; // matches what WinZip does - - if (mCDE.mFileNameLength > 0) { - mCDE.mFileName = new unsigned char[mCDE.mFileNameLength+1]; - strcpy((char*) mCDE.mFileName, fileName); - } - if (mCDE.mFileCommentLength > 0) { - /* TODO: stop assuming null-terminated ASCII here? */ - mCDE.mFileComment = new unsigned char[mCDE.mFileCommentLength+1]; - strcpy((char*) mCDE.mFileComment, comment); - } - - copyCDEtoLFH(); -} - -/* - * Initialize a new entry, starting with the ZipEntry from a different - * archive. - * - * Initializes the CDE and the LFH. - */ -status_t ZipEntry::initFromExternal(const ZipFile* pZipFile, - const ZipEntry* pEntry) -{ - /* - * Copy everything in the CDE over, then fix up the hairy bits. - */ - memcpy(&mCDE, &pEntry->mCDE, sizeof(mCDE)); - - if (mCDE.mFileNameLength > 0) { - mCDE.mFileName = new unsigned char[mCDE.mFileNameLength+1]; - if (mCDE.mFileName == NULL) - return NO_MEMORY; - strcpy((char*) mCDE.mFileName, (char*)pEntry->mCDE.mFileName); - } - if (mCDE.mFileCommentLength > 0) { - mCDE.mFileComment = new unsigned char[mCDE.mFileCommentLength+1]; - if (mCDE.mFileComment == NULL) - return NO_MEMORY; - strcpy((char*) mCDE.mFileComment, (char*)pEntry->mCDE.mFileComment); - } - if (mCDE.mExtraFieldLength > 0) { - /* we null-terminate this, though it may not be a string */ - mCDE.mExtraField = new unsigned char[mCDE.mExtraFieldLength+1]; - if (mCDE.mExtraField == NULL) - return NO_MEMORY; - memcpy(mCDE.mExtraField, pEntry->mCDE.mExtraField, - mCDE.mExtraFieldLength+1); - } - - /* construct the LFH from the CDE */ - copyCDEtoLFH(); - - /* - * The LFH "extra" field is independent of the CDE "extra", so we - * handle it here. - */ - assert(mLFH.mExtraField == NULL); - mLFH.mExtraFieldLength = pEntry->mLFH.mExtraFieldLength; - if (mLFH.mExtraFieldLength > 0) { - mLFH.mExtraField = new unsigned char[mLFH.mExtraFieldLength+1]; - if (mLFH.mExtraField == NULL) - return NO_MEMORY; - memcpy(mLFH.mExtraField, pEntry->mLFH.mExtraField, - mLFH.mExtraFieldLength+1); - } - - return NO_ERROR; -} - -/* - * Insert pad bytes in the LFH by tweaking the "extra" field. This will - * potentially confuse something that put "extra" data in here earlier, - * but I can't find an actual problem. - */ -status_t ZipEntry::addPadding(int padding) -{ - if (padding <= 0) - return INVALID_OPERATION; - - //LOGI("HEY: adding %d pad bytes to existing %d in %s\n", - // padding, mLFH.mExtraFieldLength, mCDE.mFileName); - - if (mLFH.mExtraFieldLength > 0) { - /* extend existing field */ - unsigned char* newExtra; - - newExtra = new unsigned char[mLFH.mExtraFieldLength + padding]; - if (newExtra == NULL) - return NO_MEMORY; - memset(newExtra + mLFH.mExtraFieldLength, 0, padding); - memcpy(newExtra, mLFH.mExtraField, mLFH.mExtraFieldLength); - - delete[] mLFH.mExtraField; - mLFH.mExtraField = newExtra; - mLFH.mExtraFieldLength += padding; - } else { - /* create new field */ - mLFH.mExtraField = new unsigned char[padding]; - memset(mLFH.mExtraField, 0, padding); - mLFH.mExtraFieldLength = padding; - } - - return NO_ERROR; -} - -/* - * Set the fields in the LFH equal to the corresponding fields in the CDE. - * - * This does not touch the LFH "extra" field. - */ -void ZipEntry::copyCDEtoLFH(void) -{ - mLFH.mVersionToExtract = mCDE.mVersionToExtract; - mLFH.mGPBitFlag = mCDE.mGPBitFlag; - mLFH.mCompressionMethod = mCDE.mCompressionMethod; - mLFH.mLastModFileTime = mCDE.mLastModFileTime; - mLFH.mLastModFileDate = mCDE.mLastModFileDate; - mLFH.mCRC32 = mCDE.mCRC32; - mLFH.mCompressedSize = mCDE.mCompressedSize; - mLFH.mUncompressedSize = mCDE.mUncompressedSize; - mLFH.mFileNameLength = mCDE.mFileNameLength; - // the "extra field" is independent - - delete[] mLFH.mFileName; - if (mLFH.mFileNameLength > 0) { - mLFH.mFileName = new unsigned char[mLFH.mFileNameLength+1]; - strcpy((char*) mLFH.mFileName, (const char*) mCDE.mFileName); - } else { - mLFH.mFileName = NULL; - } -} - -/* - * Set some information about a file after we add it. - */ -void ZipEntry::setDataInfo(long uncompLen, long compLen, unsigned long crc32, - int compressionMethod) -{ - mCDE.mCompressionMethod = compressionMethod; - mCDE.mCRC32 = crc32; - mCDE.mCompressedSize = compLen; - mCDE.mUncompressedSize = uncompLen; - mCDE.mCompressionMethod = compressionMethod; - if (compressionMethod == kCompressDeflated) { - mCDE.mGPBitFlag |= 0x0002; // indicates maximum compression used - } - copyCDEtoLFH(); -} - -/* - * See if the data in mCDE and mLFH match up. This is mostly useful for - * debugging these classes, but it can be used to identify damaged - * archives. - * - * Returns "false" if they differ. - */ -bool ZipEntry::compareHeaders(void) const -{ - if (mCDE.mVersionToExtract != mLFH.mVersionToExtract) { - LOGV("cmp: VersionToExtract\n"); - return false; - } - if (mCDE.mGPBitFlag != mLFH.mGPBitFlag) { - LOGV("cmp: GPBitFlag\n"); - return false; - } - if (mCDE.mCompressionMethod != mLFH.mCompressionMethod) { - LOGV("cmp: CompressionMethod\n"); - return false; - } - if (mCDE.mLastModFileTime != mLFH.mLastModFileTime) { - LOGV("cmp: LastModFileTime\n"); - return false; - } - if (mCDE.mLastModFileDate != mLFH.mLastModFileDate) { - LOGV("cmp: LastModFileDate\n"); - return false; - } - if (mCDE.mCRC32 != mLFH.mCRC32) { - LOGV("cmp: CRC32\n"); - return false; - } - if (mCDE.mCompressedSize != mLFH.mCompressedSize) { - LOGV("cmp: CompressedSize\n"); - return false; - } - if (mCDE.mUncompressedSize != mLFH.mUncompressedSize) { - LOGV("cmp: UncompressedSize\n"); - return false; - } - if (mCDE.mFileNameLength != mLFH.mFileNameLength) { - LOGV("cmp: FileNameLength\n"); - return false; - } -#if 0 // this seems to be used for padding, not real data - if (mCDE.mExtraFieldLength != mLFH.mExtraFieldLength) { - LOGV("cmp: ExtraFieldLength\n"); - return false; - } -#endif - if (mCDE.mFileName != NULL) { - if (strcmp((char*) mCDE.mFileName, (char*) mLFH.mFileName) != 0) { - LOGV("cmp: FileName\n"); - return false; - } - } - - return true; -} - - -/* - * Convert the DOS date/time stamp into a UNIX time stamp. - */ -time_t ZipEntry::getModWhen(void) const -{ - struct tm parts; - - parts.tm_sec = (mCDE.mLastModFileTime & 0x001f) << 1; - parts.tm_min = (mCDE.mLastModFileTime & 0x07e0) >> 5; - parts.tm_hour = (mCDE.mLastModFileTime & 0xf800) >> 11; - parts.tm_mday = (mCDE.mLastModFileDate & 0x001f); - parts.tm_mon = ((mCDE.mLastModFileDate & 0x01e0) >> 5) -1; - parts.tm_year = ((mCDE.mLastModFileDate & 0xfe00) >> 9) + 80; - parts.tm_wday = parts.tm_yday = 0; - parts.tm_isdst = -1; // DST info "not available" - - return mktime(&parts); -} - -/* - * Set the CDE/LFH timestamp from UNIX time. - */ -void ZipEntry::setModWhen(time_t when) -{ -#ifdef HAVE_LOCALTIME_R - struct tm tmResult; -#endif - time_t even; - unsigned short zdate, ztime; - - struct tm* ptm; - - /* round up to an even number of seconds */ - even = (time_t)(((unsigned long)(when) + 1) & (~1)); - - /* expand */ -#ifdef HAVE_LOCALTIME_R - ptm = localtime_r(&even, &tmResult); -#else - ptm = localtime(&even); -#endif - - int year; - year = ptm->tm_year; - if (year < 80) - year = 80; - - zdate = (year - 80) << 9 | (ptm->tm_mon+1) << 5 | ptm->tm_mday; - ztime = ptm->tm_hour << 11 | ptm->tm_min << 5 | ptm->tm_sec >> 1; - - mCDE.mLastModFileTime = mLFH.mLastModFileTime = ztime; - mCDE.mLastModFileDate = mLFH.mLastModFileDate = zdate; -} - - -/* - * =========================================================================== - * ZipEntry::LocalFileHeader - * =========================================================================== - */ - -/* - * Read a local file header. - * - * On entry, "fp" points to the signature at the start of the header. - * On exit, "fp" points to the start of data. - */ -status_t ZipEntry::LocalFileHeader::read(FILE* fp) -{ - status_t result = NO_ERROR; - unsigned char buf[kLFHLen]; - - assert(mFileName == NULL); - assert(mExtraField == NULL); - - if (fread(buf, 1, kLFHLen, fp) != kLFHLen) { - result = UNKNOWN_ERROR; - goto bail; - } - - if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) { - LOGD("whoops: didn't find expected signature\n"); - result = UNKNOWN_ERROR; - goto bail; - } - - mVersionToExtract = ZipEntry::getShortLE(&buf[0x04]); - mGPBitFlag = ZipEntry::getShortLE(&buf[0x06]); - mCompressionMethod = ZipEntry::getShortLE(&buf[0x08]); - mLastModFileTime = ZipEntry::getShortLE(&buf[0x0a]); - mLastModFileDate = ZipEntry::getShortLE(&buf[0x0c]); - mCRC32 = ZipEntry::getLongLE(&buf[0x0e]); - mCompressedSize = ZipEntry::getLongLE(&buf[0x12]); - mUncompressedSize = ZipEntry::getLongLE(&buf[0x16]); - mFileNameLength = ZipEntry::getShortLE(&buf[0x1a]); - mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1c]); - - // TODO: validate sizes - - /* grab filename */ - if (mFileNameLength != 0) { - mFileName = new unsigned char[mFileNameLength+1]; - if (mFileName == NULL) { - result = NO_MEMORY; - goto bail; - } - if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) { - result = UNKNOWN_ERROR; - goto bail; - } - mFileName[mFileNameLength] = '\0'; - } - - /* grab extra field */ - if (mExtraFieldLength != 0) { - mExtraField = new unsigned char[mExtraFieldLength+1]; - if (mExtraField == NULL) { - result = NO_MEMORY; - goto bail; - } - if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) { - result = UNKNOWN_ERROR; - goto bail; - } - mExtraField[mExtraFieldLength] = '\0'; - } - -bail: - return result; -} - -/* - * Write a local file header. - */ -status_t ZipEntry::LocalFileHeader::write(FILE* fp) -{ - unsigned char buf[kLFHLen]; - - ZipEntry::putLongLE(&buf[0x00], kSignature); - ZipEntry::putShortLE(&buf[0x04], mVersionToExtract); - ZipEntry::putShortLE(&buf[0x06], mGPBitFlag); - ZipEntry::putShortLE(&buf[0x08], mCompressionMethod); - ZipEntry::putShortLE(&buf[0x0a], mLastModFileTime); - ZipEntry::putShortLE(&buf[0x0c], mLastModFileDate); - ZipEntry::putLongLE(&buf[0x0e], mCRC32); - ZipEntry::putLongLE(&buf[0x12], mCompressedSize); - ZipEntry::putLongLE(&buf[0x16], mUncompressedSize); - ZipEntry::putShortLE(&buf[0x1a], mFileNameLength); - ZipEntry::putShortLE(&buf[0x1c], mExtraFieldLength); - - if (fwrite(buf, 1, kLFHLen, fp) != kLFHLen) - return UNKNOWN_ERROR; - - /* write filename */ - if (mFileNameLength != 0) { - if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength) - return UNKNOWN_ERROR; - } - - /* write "extra field" */ - if (mExtraFieldLength != 0) { - if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) - return UNKNOWN_ERROR; - } - - return NO_ERROR; -} - - -/* - * Dump the contents of a LocalFileHeader object. - */ -void ZipEntry::LocalFileHeader::dump(void) const -{ - LOGD(" LocalFileHeader contents:\n"); - LOGD(" versToExt=%u gpBits=0x%04x compression=%u\n", - mVersionToExtract, mGPBitFlag, mCompressionMethod); - LOGD(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n", - mLastModFileTime, mLastModFileDate, mCRC32); - LOGD(" compressedSize=%lu uncompressedSize=%lu\n", - mCompressedSize, mUncompressedSize); - LOGD(" filenameLen=%u extraLen=%u\n", - mFileNameLength, mExtraFieldLength); - if (mFileName != NULL) - LOGD(" filename: '%s'\n", mFileName); -} - - -/* - * =========================================================================== - * ZipEntry::CentralDirEntry - * =========================================================================== - */ - -/* - * Read the central dir entry that appears next in the file. - * - * On entry, "fp" should be positioned on the signature bytes for the - * entry. On exit, "fp" will point at the signature word for the next - * entry or for the EOCD. - */ -status_t ZipEntry::CentralDirEntry::read(FILE* fp) -{ - status_t result = NO_ERROR; - unsigned char buf[kCDELen]; - - /* no re-use */ - assert(mFileName == NULL); - assert(mExtraField == NULL); - assert(mFileComment == NULL); - - if (fread(buf, 1, kCDELen, fp) != kCDELen) { - result = UNKNOWN_ERROR; - goto bail; - } - - if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) { - LOGD("Whoops: didn't find expected signature\n"); - result = UNKNOWN_ERROR; - goto bail; - } - - mVersionMadeBy = ZipEntry::getShortLE(&buf[0x04]); - mVersionToExtract = ZipEntry::getShortLE(&buf[0x06]); - mGPBitFlag = ZipEntry::getShortLE(&buf[0x08]); - mCompressionMethod = ZipEntry::getShortLE(&buf[0x0a]); - mLastModFileTime = ZipEntry::getShortLE(&buf[0x0c]); - mLastModFileDate = ZipEntry::getShortLE(&buf[0x0e]); - mCRC32 = ZipEntry::getLongLE(&buf[0x10]); - mCompressedSize = ZipEntry::getLongLE(&buf[0x14]); - mUncompressedSize = ZipEntry::getLongLE(&buf[0x18]); - mFileNameLength = ZipEntry::getShortLE(&buf[0x1c]); - mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1e]); - mFileCommentLength = ZipEntry::getShortLE(&buf[0x20]); - mDiskNumberStart = ZipEntry::getShortLE(&buf[0x22]); - mInternalAttrs = ZipEntry::getShortLE(&buf[0x24]); - mExternalAttrs = ZipEntry::getLongLE(&buf[0x26]); - mLocalHeaderRelOffset = ZipEntry::getLongLE(&buf[0x2a]); - - // TODO: validate sizes and offsets - - /* grab filename */ - if (mFileNameLength != 0) { - mFileName = new unsigned char[mFileNameLength+1]; - if (mFileName == NULL) { - result = NO_MEMORY; - goto bail; - } - if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) { - result = UNKNOWN_ERROR; - goto bail; - } - mFileName[mFileNameLength] = '\0'; - } - - /* read "extra field" */ - if (mExtraFieldLength != 0) { - mExtraField = new unsigned char[mExtraFieldLength+1]; - if (mExtraField == NULL) { - result = NO_MEMORY; - goto bail; - } - if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) { - result = UNKNOWN_ERROR; - goto bail; - } - mExtraField[mExtraFieldLength] = '\0'; - } - - - /* grab comment, if any */ - if (mFileCommentLength != 0) { - mFileComment = new unsigned char[mFileCommentLength+1]; - if (mFileComment == NULL) { - result = NO_MEMORY; - goto bail; - } - if (fread(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength) - { - result = UNKNOWN_ERROR; - goto bail; - } - mFileComment[mFileCommentLength] = '\0'; - } - -bail: - return result; -} - -/* - * Write a central dir entry. - */ -status_t ZipEntry::CentralDirEntry::write(FILE* fp) -{ - unsigned char buf[kCDELen]; - - ZipEntry::putLongLE(&buf[0x00], kSignature); - ZipEntry::putShortLE(&buf[0x04], mVersionMadeBy); - ZipEntry::putShortLE(&buf[0x06], mVersionToExtract); - ZipEntry::putShortLE(&buf[0x08], mGPBitFlag); - ZipEntry::putShortLE(&buf[0x0a], mCompressionMethod); - ZipEntry::putShortLE(&buf[0x0c], mLastModFileTime); - ZipEntry::putShortLE(&buf[0x0e], mLastModFileDate); - ZipEntry::putLongLE(&buf[0x10], mCRC32); - ZipEntry::putLongLE(&buf[0x14], mCompressedSize); - ZipEntry::putLongLE(&buf[0x18], mUncompressedSize); - ZipEntry::putShortLE(&buf[0x1c], mFileNameLength); - ZipEntry::putShortLE(&buf[0x1e], mExtraFieldLength); - ZipEntry::putShortLE(&buf[0x20], mFileCommentLength); - ZipEntry::putShortLE(&buf[0x22], mDiskNumberStart); - ZipEntry::putShortLE(&buf[0x24], mInternalAttrs); - ZipEntry::putLongLE(&buf[0x26], mExternalAttrs); - ZipEntry::putLongLE(&buf[0x2a], mLocalHeaderRelOffset); - - if (fwrite(buf, 1, kCDELen, fp) != kCDELen) - return UNKNOWN_ERROR; - - /* write filename */ - if (mFileNameLength != 0) { - if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength) - return UNKNOWN_ERROR; - } - - /* write "extra field" */ - if (mExtraFieldLength != 0) { - if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) - return UNKNOWN_ERROR; - } - - /* write comment */ - if (mFileCommentLength != 0) { - if (fwrite(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength) - return UNKNOWN_ERROR; - } - - return NO_ERROR; -} - -/* - * Dump the contents of a CentralDirEntry object. - */ -void ZipEntry::CentralDirEntry::dump(void) const -{ - LOGD(" CentralDirEntry contents:\n"); - LOGD(" versMadeBy=%u versToExt=%u gpBits=0x%04x compression=%u\n", - mVersionMadeBy, mVersionToExtract, mGPBitFlag, mCompressionMethod); - LOGD(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n", - mLastModFileTime, mLastModFileDate, mCRC32); - LOGD(" compressedSize=%lu uncompressedSize=%lu\n", - mCompressedSize, mUncompressedSize); - LOGD(" filenameLen=%u extraLen=%u commentLen=%u\n", - mFileNameLength, mExtraFieldLength, mFileCommentLength); - LOGD(" diskNumStart=%u intAttr=0x%04x extAttr=0x%08lx relOffset=%lu\n", - mDiskNumberStart, mInternalAttrs, mExternalAttrs, - mLocalHeaderRelOffset); - - if (mFileName != NULL) - LOGD(" filename: '%s'\n", mFileName); - if (mFileComment != NULL) - LOGD(" comment: '%s'\n", mFileComment); -} - diff --git a/libs/utils/ZipFile.cpp b/libs/utils/ZipFile.cpp deleted file mode 100644 index 2132d2224e..0000000000 --- a/libs/utils/ZipFile.cpp +++ /dev/null @@ -1,1296 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// -// Access to Zip archives. -// - -#define LOG_TAG "zip" - -#include "utils/ZipFile.h" -#include "utils/ZipUtils.h" -#include "utils/Log.h" - -#include <zlib.h> -#define DEF_MEM_LEVEL 8 // normally in zutil.h? - -#include <memory.h> -#include <sys/stat.h> -#include <errno.h> -#include <assert.h> - -using namespace android; - -/* - * Some environments require the "b", some choke on it. - */ -#define FILE_OPEN_RO "rb" -#define FILE_OPEN_RW "r+b" -#define FILE_OPEN_RW_CREATE "w+b" - -/* should live somewhere else? */ -static status_t errnoToStatus(int err) -{ - if (err == ENOENT) - return NAME_NOT_FOUND; - else if (err == EACCES) - return PERMISSION_DENIED; - else - return UNKNOWN_ERROR; -} - -/* - * Open a file and parse its guts. - */ -status_t ZipFile::open(const char* zipFileName, int flags) -{ - bool newArchive = false; - - assert(mZipFp == NULL); // no reopen - - if ((flags & kOpenTruncate)) - flags |= kOpenCreate; // trunc implies create - - if ((flags & kOpenReadOnly) && (flags & kOpenReadWrite)) - return INVALID_OPERATION; // not both - if (!((flags & kOpenReadOnly) || (flags & kOpenReadWrite))) - return INVALID_OPERATION; // not neither - if ((flags & kOpenCreate) && !(flags & kOpenReadWrite)) - return INVALID_OPERATION; // create requires write - - if (flags & kOpenTruncate) { - newArchive = true; - } else { - newArchive = (access(zipFileName, F_OK) != 0); - if (!(flags & kOpenCreate) && newArchive) { - /* not creating, must already exist */ - LOGD("File %s does not exist", zipFileName); - return NAME_NOT_FOUND; - } - } - - /* open the file */ - const char* openflags; - if (flags & kOpenReadWrite) { - if (newArchive) - openflags = FILE_OPEN_RW_CREATE; - else - openflags = FILE_OPEN_RW; - } else { - openflags = FILE_OPEN_RO; - } - mZipFp = fopen(zipFileName, openflags); - if (mZipFp == NULL) { - int err = errno; - LOGD("fopen failed: %d\n", err); - return errnoToStatus(err); - } - - status_t result; - if (!newArchive) { - /* - * Load the central directory. If that fails, then this probably - * isn't a Zip archive. - */ - result = readCentralDir(); - } else { - /* - * Newly-created. The EndOfCentralDir constructor actually - * sets everything to be the way we want it (all zeroes). We - * set mNeedCDRewrite so that we create *something* if the - * caller doesn't add any files. (We could also just unlink - * the file if it's brand new and nothing was added, but that's - * probably doing more than we really should -- the user might - * have a need for empty zip files.) - */ - mNeedCDRewrite = true; - result = NO_ERROR; - } - - if (flags & kOpenReadOnly) - mReadOnly = true; - else - assert(!mReadOnly); - - return result; -} - -/* - * Return the Nth entry in the archive. - */ -ZipEntry* ZipFile::getEntryByIndex(int idx) const -{ - if (idx < 0 || idx >= (int) mEntries.size()) - return NULL; - - return mEntries[idx]; -} - -/* - * Find an entry by name. - */ -ZipEntry* ZipFile::getEntryByName(const char* fileName) const -{ - /* - * Do a stupid linear string-compare search. - * - * There are various ways to speed this up, especially since it's rare - * to intermingle changes to the archive with "get by name" calls. We - * don't want to sort the mEntries vector itself, however, because - * it's used to recreate the Central Directory. - * - * (Hash table works, parallel list of pointers in sorted order is good.) - */ - int idx; - - for (idx = mEntries.size()-1; idx >= 0; idx--) { - ZipEntry* pEntry = mEntries[idx]; - if (!pEntry->getDeleted() && - strcmp(fileName, pEntry->getFileName()) == 0) - { - return pEntry; - } - } - - return NULL; -} - -/* - * Empty the mEntries vector. - */ -void ZipFile::discardEntries(void) -{ - int count = mEntries.size(); - - while (--count >= 0) - delete mEntries[count]; - - mEntries.clear(); -} - - -/* - * Find the central directory and read the contents. - * - * The fun thing about ZIP archives is that they may or may not be - * readable from start to end. In some cases, notably for archives - * that were written to stdout, the only length information is in the - * central directory at the end of the file. - * - * Of course, the central directory can be followed by a variable-length - * comment field, so we have to scan through it backwards. The comment - * is at most 64K, plus we have 18 bytes for the end-of-central-dir stuff - * itself, plus apparently sometimes people throw random junk on the end - * just for the fun of it. - * - * This is all a little wobbly. If the wrong value ends up in the EOCD - * area, we're hosed. This appears to be the way that everbody handles - * it though, so we're in pretty good company if this fails. - */ -status_t ZipFile::readCentralDir(void) -{ - status_t result = NO_ERROR; - unsigned char* buf = NULL; - off_t fileLength, seekStart; - long readAmount; - int i; - - fseek(mZipFp, 0, SEEK_END); - fileLength = ftell(mZipFp); - rewind(mZipFp); - - /* too small to be a ZIP archive? */ - if (fileLength < EndOfCentralDir::kEOCDLen) { - LOGD("Length is %ld -- too small\n", (long)fileLength); - result = INVALID_OPERATION; - goto bail; - } - - buf = new unsigned char[EndOfCentralDir::kMaxEOCDSearch]; - if (buf == NULL) { - LOGD("Failure allocating %d bytes for EOCD search", - EndOfCentralDir::kMaxEOCDSearch); - result = NO_MEMORY; - goto bail; - } - - if (fileLength > EndOfCentralDir::kMaxEOCDSearch) { - seekStart = fileLength - EndOfCentralDir::kMaxEOCDSearch; - readAmount = EndOfCentralDir::kMaxEOCDSearch; - } else { - seekStart = 0; - readAmount = (long) fileLength; - } - if (fseek(mZipFp, seekStart, SEEK_SET) != 0) { - LOGD("Failure seeking to end of zip at %ld", (long) seekStart); - result = UNKNOWN_ERROR; - goto bail; - } - - /* read the last part of the file into the buffer */ - if (fread(buf, 1, readAmount, mZipFp) != (size_t) readAmount) { - LOGD("short file? wanted %ld\n", readAmount); - result = UNKNOWN_ERROR; - goto bail; - } - - /* find the end-of-central-dir magic */ - for (i = readAmount - 4; i >= 0; i--) { - if (buf[i] == 0x50 && - ZipEntry::getLongLE(&buf[i]) == EndOfCentralDir::kSignature) - { - LOGV("+++ Found EOCD at buf+%d\n", i); - break; - } - } - if (i < 0) { - LOGD("EOCD not found, not Zip\n"); - result = INVALID_OPERATION; - goto bail; - } - - /* extract eocd values */ - result = mEOCD.readBuf(buf + i, readAmount - i); - if (result != NO_ERROR) { - LOGD("Failure reading %ld bytes of EOCD values", readAmount - i); - goto bail; - } - //mEOCD.dump(); - - if (mEOCD.mDiskNumber != 0 || mEOCD.mDiskWithCentralDir != 0 || - mEOCD.mNumEntries != mEOCD.mTotalNumEntries) - { - LOGD("Archive spanning not supported\n"); - result = INVALID_OPERATION; - goto bail; - } - - /* - * So far so good. "mCentralDirSize" is the size in bytes of the - * central directory, so we can just seek back that far to find it. - * We can also seek forward mCentralDirOffset bytes from the - * start of the file. - * - * We're not guaranteed to have the rest of the central dir in the - * buffer, nor are we guaranteed that the central dir will have any - * sort of convenient size. We need to skip to the start of it and - * read the header, then the other goodies. - * - * The only thing we really need right now is the file comment, which - * we're hoping to preserve. - */ - if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) { - LOGD("Failure seeking to central dir offset %ld\n", - mEOCD.mCentralDirOffset); - result = UNKNOWN_ERROR; - goto bail; - } - - /* - * Loop through and read the central dir entries. - */ - LOGV("Scanning %d entries...\n", mEOCD.mTotalNumEntries); - int entry; - for (entry = 0; entry < mEOCD.mTotalNumEntries; entry++) { - ZipEntry* pEntry = new ZipEntry; - - result = pEntry->initFromCDE(mZipFp); - if (result != NO_ERROR) { - LOGD("initFromCDE failed\n"); - delete pEntry; - goto bail; - } - - mEntries.add(pEntry); - } - - - /* - * If all went well, we should now be back at the EOCD. - */ - { - unsigned char checkBuf[4]; - if (fread(checkBuf, 1, 4, mZipFp) != 4) { - LOGD("EOCD check read failed\n"); - result = INVALID_OPERATION; - goto bail; - } - if (ZipEntry::getLongLE(checkBuf) != EndOfCentralDir::kSignature) { - LOGD("EOCD read check failed\n"); - result = UNKNOWN_ERROR; - goto bail; - } - LOGV("+++ EOCD read check passed\n"); - } - -bail: - delete[] buf; - return result; -} - - -/* - * Add a new file to the archive. - * - * This requires creating and populating a ZipEntry structure, and copying - * the data into the file at the appropriate position. The "appropriate - * position" is the current location of the central directory, which we - * casually overwrite (we can put it back later). - * - * If we were concerned about safety, we would want to make all changes - * in a temp file and then overwrite the original after everything was - * safely written. Not really a concern for us. - */ -status_t ZipFile::addCommon(const char* fileName, const void* data, size_t size, - const char* storageName, int sourceType, int compressionMethod, - ZipEntry** ppEntry) -{ - ZipEntry* pEntry = NULL; - status_t result = NO_ERROR; - long lfhPosn, startPosn, endPosn, uncompressedLen; - FILE* inputFp = NULL; - unsigned long crc; - time_t modWhen; - - if (mReadOnly) - return INVALID_OPERATION; - - assert(compressionMethod == ZipEntry::kCompressDeflated || - compressionMethod == ZipEntry::kCompressStored); - - /* make sure we're in a reasonable state */ - assert(mZipFp != NULL); - assert(mEntries.size() == mEOCD.mTotalNumEntries); - - /* make sure it doesn't already exist */ - if (getEntryByName(storageName) != NULL) - return ALREADY_EXISTS; - - if (!data) { - inputFp = fopen(fileName, FILE_OPEN_RO); - if (inputFp == NULL) - return errnoToStatus(errno); - } - - if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) { - result = UNKNOWN_ERROR; - goto bail; - } - - pEntry = new ZipEntry; - pEntry->initNew(storageName, NULL); - - /* - * From here on out, failures are more interesting. - */ - mNeedCDRewrite = true; - - /* - * Write the LFH, even though it's still mostly blank. We need it - * as a place-holder. In theory the LFH isn't necessary, but in - * practice some utilities demand it. - */ - lfhPosn = ftell(mZipFp); - pEntry->mLFH.write(mZipFp); - startPosn = ftell(mZipFp); - - /* - * Copy the data in, possibly compressing it as we go. - */ - if (sourceType == ZipEntry::kCompressStored) { - if (compressionMethod == ZipEntry::kCompressDeflated) { - bool failed = false; - result = compressFpToFp(mZipFp, inputFp, data, size, &crc); - if (result != NO_ERROR) { - LOGD("compression failed, storing\n"); - failed = true; - } else { - /* - * Make sure it has compressed "enough". This probably ought - * to be set through an API call, but I don't expect our - * criteria to change over time. - */ - long src = inputFp ? ftell(inputFp) : size; - long dst = ftell(mZipFp) - startPosn; - if (dst + (dst / 10) > src) { - LOGD("insufficient compression (src=%ld dst=%ld), storing\n", - src, dst); - failed = true; - } - } - - if (failed) { - compressionMethod = ZipEntry::kCompressStored; - if (inputFp) rewind(inputFp); - fseek(mZipFp, startPosn, SEEK_SET); - /* fall through to kCompressStored case */ - } - } - /* handle "no compression" request, or failed compression from above */ - if (compressionMethod == ZipEntry::kCompressStored) { - if (inputFp) { - result = copyFpToFp(mZipFp, inputFp, &crc); - } else { - result = copyDataToFp(mZipFp, data, size, &crc); - } - if (result != NO_ERROR) { - // don't need to truncate; happens in CDE rewrite - LOGD("failed copying data in\n"); - goto bail; - } - } - - // currently seeked to end of file - uncompressedLen = inputFp ? ftell(inputFp) : size; - } else if (sourceType == ZipEntry::kCompressDeflated) { - /* we should support uncompressed-from-compressed, but it's not - * important right now */ - assert(compressionMethod == ZipEntry::kCompressDeflated); - - bool scanResult; - int method; - long compressedLen; - - scanResult = ZipUtils::examineGzip(inputFp, &method, &uncompressedLen, - &compressedLen, &crc); - if (!scanResult || method != ZipEntry::kCompressDeflated) { - LOGD("this isn't a deflated gzip file?"); - result = UNKNOWN_ERROR; - goto bail; - } - - result = copyPartialFpToFp(mZipFp, inputFp, compressedLen, NULL); - if (result != NO_ERROR) { - LOGD("failed copying gzip data in\n"); - goto bail; - } - } else { - assert(false); - result = UNKNOWN_ERROR; - goto bail; - } - - /* - * We could write the "Data Descriptor", but there doesn't seem to - * be any point since we're going to go back and write the LFH. - * - * Update file offsets. - */ - endPosn = ftell(mZipFp); // seeked to end of compressed data - - /* - * Success! Fill out new values. - */ - pEntry->setDataInfo(uncompressedLen, endPosn - startPosn, crc, - compressionMethod); - modWhen = getModTime(inputFp ? fileno(inputFp) : fileno(mZipFp)); - pEntry->setModWhen(modWhen); - pEntry->setLFHOffset(lfhPosn); - mEOCD.mNumEntries++; - mEOCD.mTotalNumEntries++; - mEOCD.mCentralDirSize = 0; // mark invalid; set by flush() - mEOCD.mCentralDirOffset = endPosn; - - /* - * Go back and write the LFH. - */ - if (fseek(mZipFp, lfhPosn, SEEK_SET) != 0) { - result = UNKNOWN_ERROR; - goto bail; - } - pEntry->mLFH.write(mZipFp); - - /* - * Add pEntry to the list. - */ - mEntries.add(pEntry); - if (ppEntry != NULL) - *ppEntry = pEntry; - pEntry = NULL; - -bail: - if (inputFp != NULL) - fclose(inputFp); - delete pEntry; - return result; -} - -/* - * Add an entry by copying it from another zip file. If "padding" is - * nonzero, the specified number of bytes will be added to the "extra" - * field in the header. - * - * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. - */ -status_t ZipFile::add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry, - int padding, ZipEntry** ppEntry) -{ - ZipEntry* pEntry = NULL; - status_t result; - long lfhPosn, endPosn; - - if (mReadOnly) - return INVALID_OPERATION; - - /* make sure we're in a reasonable state */ - assert(mZipFp != NULL); - assert(mEntries.size() == mEOCD.mTotalNumEntries); - - if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) { - result = UNKNOWN_ERROR; - goto bail; - } - - pEntry = new ZipEntry; - if (pEntry == NULL) { - result = NO_MEMORY; - goto bail; - } - - result = pEntry->initFromExternal(pSourceZip, pSourceEntry); - if (result != NO_ERROR) - goto bail; - if (padding != 0) { - result = pEntry->addPadding(padding); - if (result != NO_ERROR) - goto bail; - } - - /* - * From here on out, failures are more interesting. - */ - mNeedCDRewrite = true; - - /* - * Write the LFH. Since we're not recompressing the data, we already - * have all of the fields filled out. - */ - lfhPosn = ftell(mZipFp); - pEntry->mLFH.write(mZipFp); - - /* - * Copy the data over. - * - * If the "has data descriptor" flag is set, we want to copy the DD - * fields as well. This is a fixed-size area immediately following - * the data. - */ - if (fseek(pSourceZip->mZipFp, pSourceEntry->getFileOffset(), SEEK_SET) != 0) - { - result = UNKNOWN_ERROR; - goto bail; - } - - off_t copyLen; - copyLen = pSourceEntry->getCompressedLen(); - if ((pSourceEntry->mLFH.mGPBitFlag & ZipEntry::kUsesDataDescr) != 0) - copyLen += ZipEntry::kDataDescriptorLen; - - if (copyPartialFpToFp(mZipFp, pSourceZip->mZipFp, copyLen, NULL) - != NO_ERROR) - { - LOGW("copy of '%s' failed\n", pEntry->mCDE.mFileName); - result = UNKNOWN_ERROR; - goto bail; - } - - /* - * Update file offsets. - */ - endPosn = ftell(mZipFp); - - /* - * Success! Fill out new values. - */ - pEntry->setLFHOffset(lfhPosn); // sets mCDE.mLocalHeaderRelOffset - mEOCD.mNumEntries++; - mEOCD.mTotalNumEntries++; - mEOCD.mCentralDirSize = 0; // mark invalid; set by flush() - mEOCD.mCentralDirOffset = endPosn; - - /* - * Add pEntry to the list. - */ - mEntries.add(pEntry); - if (ppEntry != NULL) - *ppEntry = pEntry; - pEntry = NULL; - - result = NO_ERROR; - -bail: - delete pEntry; - return result; -} - -/* - * Copy all of the bytes in "src" to "dst". - * - * On exit, "srcFp" will be seeked to the end of the file, and "dstFp" - * will be seeked immediately past the data. - */ -status_t ZipFile::copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32) -{ - unsigned char tmpBuf[32768]; - size_t count; - - *pCRC32 = crc32(0L, Z_NULL, 0); - - while (1) { - count = fread(tmpBuf, 1, sizeof(tmpBuf), srcFp); - if (ferror(srcFp) || ferror(dstFp)) - return errnoToStatus(errno); - if (count == 0) - break; - - *pCRC32 = crc32(*pCRC32, tmpBuf, count); - - if (fwrite(tmpBuf, 1, count, dstFp) != count) { - LOGD("fwrite %d bytes failed\n", (int) count); - return UNKNOWN_ERROR; - } - } - - return NO_ERROR; -} - -/* - * Copy all of the bytes in "src" to "dst". - * - * On exit, "dstFp" will be seeked immediately past the data. - */ -status_t ZipFile::copyDataToFp(FILE* dstFp, - const void* data, size_t size, unsigned long* pCRC32) -{ - size_t count; - - *pCRC32 = crc32(0L, Z_NULL, 0); - if (size > 0) { - *pCRC32 = crc32(*pCRC32, (const unsigned char*)data, size); - if (fwrite(data, 1, size, dstFp) != size) { - LOGD("fwrite %d bytes failed\n", (int) size); - return UNKNOWN_ERROR; - } - } - - return NO_ERROR; -} - -/* - * Copy some of the bytes in "src" to "dst". - * - * If "pCRC32" is NULL, the CRC will not be computed. - * - * On exit, "srcFp" will be seeked to the end of the file, and "dstFp" - * will be seeked immediately past the data just written. - */ -status_t ZipFile::copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length, - unsigned long* pCRC32) -{ - unsigned char tmpBuf[32768]; - size_t count; - - if (pCRC32 != NULL) - *pCRC32 = crc32(0L, Z_NULL, 0); - - while (length) { - long readSize; - - readSize = sizeof(tmpBuf); - if (readSize > length) - readSize = length; - - count = fread(tmpBuf, 1, readSize, srcFp); - if ((long) count != readSize) { // error or unexpected EOF - LOGD("fread %d bytes failed\n", (int) readSize); - return UNKNOWN_ERROR; - } - - if (pCRC32 != NULL) - *pCRC32 = crc32(*pCRC32, tmpBuf, count); - - if (fwrite(tmpBuf, 1, count, dstFp) != count) { - LOGD("fwrite %d bytes failed\n", (int) count); - return UNKNOWN_ERROR; - } - - length -= readSize; - } - - return NO_ERROR; -} - -/* - * Compress all of the data in "srcFp" and write it to "dstFp". - * - * On exit, "srcFp" will be seeked to the end of the file, and "dstFp" - * will be seeked immediately past the compressed data. - */ -status_t ZipFile::compressFpToFp(FILE* dstFp, FILE* srcFp, - const void* data, size_t size, unsigned long* pCRC32) -{ - status_t result = NO_ERROR; - const size_t kBufSize = 32768; - unsigned char* inBuf = NULL; - unsigned char* outBuf = NULL; - z_stream zstream; - bool atEof = false; // no feof() aviailable yet - unsigned long crc; - int zerr; - - /* - * Create an input buffer and an output buffer. - */ - inBuf = new unsigned char[kBufSize]; - outBuf = new unsigned char[kBufSize]; - if (inBuf == NULL || outBuf == NULL) { - result = NO_MEMORY; - goto bail; - } - - /* - * Initialize the zlib stream. - */ - memset(&zstream, 0, sizeof(zstream)); - zstream.zalloc = Z_NULL; - zstream.zfree = Z_NULL; - zstream.opaque = Z_NULL; - zstream.next_in = NULL; - zstream.avail_in = 0; - zstream.next_out = outBuf; - zstream.avail_out = kBufSize; - zstream.data_type = Z_UNKNOWN; - - zerr = deflateInit2(&zstream, Z_BEST_COMPRESSION, - Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY); - if (zerr != Z_OK) { - result = UNKNOWN_ERROR; - if (zerr == Z_VERSION_ERROR) { - LOGE("Installed zlib is not compatible with linked version (%s)\n", - ZLIB_VERSION); - } else { - LOGD("Call to deflateInit2 failed (zerr=%d)\n", zerr); - } - goto bail; - } - - crc = crc32(0L, Z_NULL, 0); - - /* - * Loop while we have data. - */ - do { - size_t getSize; - int flush; - - /* only read if the input buffer is empty */ - if (zstream.avail_in == 0 && !atEof) { - LOGV("+++ reading %d bytes\n", (int)kBufSize); - if (data) { - getSize = size > kBufSize ? kBufSize : size; - memcpy(inBuf, data, getSize); - data = ((const char*)data) + getSize; - size -= getSize; - } else { - getSize = fread(inBuf, 1, kBufSize, srcFp); - if (ferror(srcFp)) { - LOGD("deflate read failed (errno=%d)\n", errno); - goto z_bail; - } - } - if (getSize < kBufSize) { - LOGV("+++ got %d bytes, EOF reached\n", - (int)getSize); - atEof = true; - } - - crc = crc32(crc, inBuf, getSize); - - zstream.next_in = inBuf; - zstream.avail_in = getSize; - } - - if (atEof) - flush = Z_FINISH; /* tell zlib that we're done */ - else - flush = Z_NO_FLUSH; /* more to come! */ - - zerr = deflate(&zstream, flush); - if (zerr != Z_OK && zerr != Z_STREAM_END) { - LOGD("zlib deflate call failed (zerr=%d)\n", zerr); - result = UNKNOWN_ERROR; - goto z_bail; - } - - /* write when we're full or when we're done */ - if (zstream.avail_out == 0 || - (zerr == Z_STREAM_END && zstream.avail_out != (uInt) kBufSize)) - { - LOGV("+++ writing %d bytes\n", (int) (zstream.next_out - outBuf)); - if (fwrite(outBuf, 1, zstream.next_out - outBuf, dstFp) != - (size_t)(zstream.next_out - outBuf)) - { - LOGD("write %d failed in deflate\n", - (int) (zstream.next_out - outBuf)); - goto z_bail; - } - - zstream.next_out = outBuf; - zstream.avail_out = kBufSize; - } - } while (zerr == Z_OK); - - assert(zerr == Z_STREAM_END); /* other errors should've been caught */ - - *pCRC32 = crc; - -z_bail: - deflateEnd(&zstream); /* free up any allocated structures */ - -bail: - delete[] inBuf; - delete[] outBuf; - - return result; -} - -/* - * Mark an entry as deleted. - * - * We will eventually need to crunch the file down, but if several files - * are being removed (perhaps as part of an "update" process) we can make - * things considerably faster by deferring the removal to "flush" time. - */ -status_t ZipFile::remove(ZipEntry* pEntry) -{ - /* - * Should verify that pEntry is actually part of this archive, and - * not some stray ZipEntry from a different file. - */ - - /* mark entry as deleted, and mark archive as dirty */ - pEntry->setDeleted(); - mNeedCDRewrite = true; - return NO_ERROR; -} - -/* - * Flush any pending writes. - * - * In particular, this will crunch out deleted entries, and write the - * Central Directory and EOCD if we have stomped on them. - */ -status_t ZipFile::flush(void) -{ - status_t result = NO_ERROR; - long eocdPosn; - int i, count; - - if (mReadOnly) - return INVALID_OPERATION; - if (!mNeedCDRewrite) - return NO_ERROR; - - assert(mZipFp != NULL); - - result = crunchArchive(); - if (result != NO_ERROR) - return result; - - if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) - return UNKNOWN_ERROR; - - count = mEntries.size(); - for (i = 0; i < count; i++) { - ZipEntry* pEntry = mEntries[i]; - pEntry->mCDE.write(mZipFp); - } - - eocdPosn = ftell(mZipFp); - mEOCD.mCentralDirSize = eocdPosn - mEOCD.mCentralDirOffset; - - mEOCD.write(mZipFp); - - /* - * If we had some stuff bloat up during compression and get replaced - * with plain files, or if we deleted some entries, there's a lot - * of wasted space at the end of the file. Remove it now. - */ - if (ftruncate(fileno(mZipFp), ftell(mZipFp)) != 0) { - LOGW("ftruncate failed %ld: %s\n", ftell(mZipFp), strerror(errno)); - // not fatal - } - - /* should we clear the "newly added" flag in all entries now? */ - - mNeedCDRewrite = false; - return NO_ERROR; -} - -/* - * Crunch deleted files out of an archive by shifting the later files down. - * - * Because we're not using a temp file, we do the operation inside the - * current file. - */ -status_t ZipFile::crunchArchive(void) -{ - status_t result = NO_ERROR; - int i, count; - long delCount, adjust; - -#if 0 - printf("CONTENTS:\n"); - for (i = 0; i < (int) mEntries.size(); i++) { - printf(" %d: lfhOff=%ld del=%d\n", - i, mEntries[i]->getLFHOffset(), mEntries[i]->getDeleted()); - } - printf(" END is %ld\n", (long) mEOCD.mCentralDirOffset); -#endif - - /* - * Roll through the set of files, shifting them as appropriate. We - * could probably get a slight performance improvement by sliding - * multiple files down at once (because we could use larger reads - * when operating on batches of small files), but it's not that useful. - */ - count = mEntries.size(); - delCount = adjust = 0; - for (i = 0; i < count; i++) { - ZipEntry* pEntry = mEntries[i]; - long span; - - if (pEntry->getLFHOffset() != 0) { - long nextOffset; - - /* Get the length of this entry by finding the offset - * of the next entry. Directory entries don't have - * file offsets, so we need to find the next non-directory - * entry. - */ - nextOffset = 0; - for (int ii = i+1; nextOffset == 0 && ii < count; ii++) - nextOffset = mEntries[ii]->getLFHOffset(); - if (nextOffset == 0) - nextOffset = mEOCD.mCentralDirOffset; - span = nextOffset - pEntry->getLFHOffset(); - - assert(span >= ZipEntry::LocalFileHeader::kLFHLen); - } else { - /* This is a directory entry. It doesn't have - * any actual file contents, so there's no need to - * move anything. - */ - span = 0; - } - - //printf("+++ %d: off=%ld span=%ld del=%d [count=%d]\n", - // i, pEntry->getLFHOffset(), span, pEntry->getDeleted(), count); - - if (pEntry->getDeleted()) { - adjust += span; - delCount++; - - delete pEntry; - mEntries.removeAt(i); - - /* adjust loop control */ - count--; - i--; - } else if (span != 0 && adjust > 0) { - /* shuffle this entry back */ - //printf("+++ Shuffling '%s' back %ld\n", - // pEntry->getFileName(), adjust); - result = filemove(mZipFp, pEntry->getLFHOffset() - adjust, - pEntry->getLFHOffset(), span); - if (result != NO_ERROR) { - /* this is why you use a temp file */ - LOGE("error during crunch - archive is toast\n"); - return result; - } - - pEntry->setLFHOffset(pEntry->getLFHOffset() - adjust); - } - } - - /* - * Fix EOCD info. We have to wait until the end to do some of this - * because we use mCentralDirOffset to determine "span" for the - * last entry. - */ - mEOCD.mCentralDirOffset -= adjust; - mEOCD.mNumEntries -= delCount; - mEOCD.mTotalNumEntries -= delCount; - mEOCD.mCentralDirSize = 0; // mark invalid; set by flush() - - assert(mEOCD.mNumEntries == mEOCD.mTotalNumEntries); - assert(mEOCD.mNumEntries == count); - - return result; -} - -/* - * Works like memmove(), but on pieces of a file. - */ -status_t ZipFile::filemove(FILE* fp, off_t dst, off_t src, size_t n) -{ - if (dst == src || n <= 0) - return NO_ERROR; - - unsigned char readBuf[32768]; - - if (dst < src) { - /* shift stuff toward start of file; must read from start */ - while (n != 0) { - size_t getSize = sizeof(readBuf); - if (getSize > n) - getSize = n; - - if (fseek(fp, (long) src, SEEK_SET) != 0) { - LOGD("filemove src seek %ld failed\n", (long) src); - return UNKNOWN_ERROR; - } - - if (fread(readBuf, 1, getSize, fp) != getSize) { - LOGD("filemove read %ld off=%ld failed\n", - (long) getSize, (long) src); - return UNKNOWN_ERROR; - } - - if (fseek(fp, (long) dst, SEEK_SET) != 0) { - LOGD("filemove dst seek %ld failed\n", (long) dst); - return UNKNOWN_ERROR; - } - - if (fwrite(readBuf, 1, getSize, fp) != getSize) { - LOGD("filemove write %ld off=%ld failed\n", - (long) getSize, (long) dst); - return UNKNOWN_ERROR; - } - - src += getSize; - dst += getSize; - n -= getSize; - } - } else { - /* shift stuff toward end of file; must read from end */ - assert(false); // write this someday, maybe - return UNKNOWN_ERROR; - } - - return NO_ERROR; -} - - -/* - * Get the modification time from a file descriptor. - */ -time_t ZipFile::getModTime(int fd) -{ - struct stat sb; - - if (fstat(fd, &sb) < 0) { - LOGD("HEY: fstat on fd %d failed\n", fd); - return (time_t) -1; - } - - return sb.st_mtime; -} - - -#if 0 /* this is a bad idea */ -/* - * Get a copy of the Zip file descriptor. - * - * We don't allow this if the file was opened read-write because we tend - * to leave the file contents in an uncertain state between calls to - * flush(). The duplicated file descriptor should only be valid for reads. - */ -int ZipFile::getZipFd(void) const -{ - if (!mReadOnly) - return INVALID_OPERATION; - assert(mZipFp != NULL); - - int fd; - fd = dup(fileno(mZipFp)); - if (fd < 0) { - LOGD("didn't work, errno=%d\n", errno); - } - - return fd; -} -#endif - - -#if 0 -/* - * Expand data. - */ -bool ZipFile::uncompress(const ZipEntry* pEntry, void* buf) const -{ - return false; -} -#endif - -// free the memory when you're done -void* ZipFile::uncompress(const ZipEntry* entry) -{ - size_t unlen = entry->getUncompressedLen(); - size_t clen = entry->getCompressedLen(); - - void* buf = malloc(unlen); - if (buf == NULL) { - return NULL; - } - - fseek(mZipFp, 0, SEEK_SET); - - off_t offset = entry->getFileOffset(); - if (fseek(mZipFp, offset, SEEK_SET) != 0) { - goto bail; - } - - switch (entry->getCompressionMethod()) - { - case ZipEntry::kCompressStored: { - ssize_t amt = fread(buf, 1, unlen, mZipFp); - if (amt != (ssize_t)unlen) { - goto bail; - } -#if 0 - printf("data...\n"); - const unsigned char* p = (unsigned char*)buf; - const unsigned char* end = p+unlen; - for (int i=0; i<32 && p < end; i++) { - printf("0x%08x ", (int)(offset+(i*0x10))); - for (int j=0; j<0x10 && p < end; j++) { - printf(" %02x", *p); - p++; - } - printf("\n"); - } -#endif - - } - break; - case ZipEntry::kCompressDeflated: { - if (!ZipUtils::inflateToBuffer(mZipFp, buf, unlen, clen)) { - goto bail; - } - } - break; - default: - goto bail; - } - return buf; - -bail: - free(buf); - return NULL; -} - - -/* - * =========================================================================== - * ZipFile::EndOfCentralDir - * =========================================================================== - */ - -/* - * Read the end-of-central-dir fields. - * - * "buf" should be positioned at the EOCD signature, and should contain - * the entire EOCD area including the comment. - */ -status_t ZipFile::EndOfCentralDir::readBuf(const unsigned char* buf, int len) -{ - /* don't allow re-use */ - assert(mComment == NULL); - - if (len < kEOCDLen) { - /* looks like ZIP file got truncated */ - LOGD(" Zip EOCD: expected >= %d bytes, found %d\n", - kEOCDLen, len); - return INVALID_OPERATION; - } - - /* this should probably be an assert() */ - if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) - return UNKNOWN_ERROR; - - mDiskNumber = ZipEntry::getShortLE(&buf[0x04]); - mDiskWithCentralDir = ZipEntry::getShortLE(&buf[0x06]); - mNumEntries = ZipEntry::getShortLE(&buf[0x08]); - mTotalNumEntries = ZipEntry::getShortLE(&buf[0x0a]); - mCentralDirSize = ZipEntry::getLongLE(&buf[0x0c]); - mCentralDirOffset = ZipEntry::getLongLE(&buf[0x10]); - mCommentLen = ZipEntry::getShortLE(&buf[0x14]); - - // TODO: validate mCentralDirOffset - - if (mCommentLen > 0) { - if (kEOCDLen + mCommentLen > len) { - LOGD("EOCD(%d) + comment(%d) exceeds len (%d)\n", - kEOCDLen, mCommentLen, len); - return UNKNOWN_ERROR; - } - mComment = new unsigned char[mCommentLen]; - memcpy(mComment, buf + kEOCDLen, mCommentLen); - } - - return NO_ERROR; -} - -/* - * Write an end-of-central-directory section. - */ -status_t ZipFile::EndOfCentralDir::write(FILE* fp) -{ - unsigned char buf[kEOCDLen]; - - ZipEntry::putLongLE(&buf[0x00], kSignature); - ZipEntry::putShortLE(&buf[0x04], mDiskNumber); - ZipEntry::putShortLE(&buf[0x06], mDiskWithCentralDir); - ZipEntry::putShortLE(&buf[0x08], mNumEntries); - ZipEntry::putShortLE(&buf[0x0a], mTotalNumEntries); - ZipEntry::putLongLE(&buf[0x0c], mCentralDirSize); - ZipEntry::putLongLE(&buf[0x10], mCentralDirOffset); - ZipEntry::putShortLE(&buf[0x14], mCommentLen); - - if (fwrite(buf, 1, kEOCDLen, fp) != kEOCDLen) - return UNKNOWN_ERROR; - if (mCommentLen > 0) { - assert(mComment != NULL); - if (fwrite(mComment, mCommentLen, 1, fp) != mCommentLen) - return UNKNOWN_ERROR; - } - - return NO_ERROR; -} - -/* - * Dump the contents of an EndOfCentralDir object. - */ -void ZipFile::EndOfCentralDir::dump(void) const -{ - LOGD(" EndOfCentralDir contents:\n"); - LOGD(" diskNum=%u diskWCD=%u numEnt=%u totalNumEnt=%u\n", - mDiskNumber, mDiskWithCentralDir, mNumEntries, mTotalNumEntries); - LOGD(" centDirSize=%lu centDirOff=%lu commentLen=%u\n", - mCentralDirSize, mCentralDirOffset, mCommentLen); -} - diff --git a/libs/utils/ZipFileCRO.cpp b/libs/utils/ZipFileCRO.cpp index d312dafada..45f6c8baa7 100644 --- a/libs/utils/ZipFileCRO.cpp +++ b/libs/utils/ZipFileCRO.cpp @@ -14,8 +14,8 @@ * limitations under the License. */ -#include "utils/ZipFileCRO.h" -#include "utils/ZipFileRO.h" +#include <utils/ZipFileCRO.h> +#include <utils/ZipFileRO.h> using namespace android; diff --git a/libs/utils/ZipFileRO.cpp b/libs/utils/ZipFileRO.cpp index ae8c719726..6c701dd0b6 100644 --- a/libs/utils/ZipFileRO.cpp +++ b/libs/utils/ZipFileRO.cpp @@ -19,9 +19,9 @@ // #define LOG_TAG "zipro" //#define LOG_NDEBUG 0 -#include "utils/ZipFileRO.h" -#include "utils/Log.h" -#include "utils/misc.h" +#include <utils/ZipFileRO.h> +#include <utils/Log.h> +#include <utils/misc.h> #include <zlib.h> diff --git a/libs/utils/ZipUtils.cpp b/libs/utils/ZipUtils.cpp index bfbacfecd9..5df94cbbd9 100644 --- a/libs/utils/ZipUtils.cpp +++ b/libs/utils/ZipUtils.cpp @@ -20,9 +20,9 @@ #define LOG_TAG "ziputil" -#include "utils/ZipUtils.h" -#include "utils/ZipFileRO.h" -#include "utils/Log.h" +#include <utils/ZipUtils.h> +#include <utils/ZipFileRO.h> +#include <utils/Log.h> #include <stdlib.h> #include <string.h> diff --git a/opengl/libagl/array.cpp b/opengl/libagl/array.cpp index 6d2cc9111c..cffe880edd 100644 --- a/opengl/libagl/array.cpp +++ b/opengl/libagl/array.cpp @@ -960,6 +960,8 @@ void compileElement__generic(ogles_context_t* c, v->index = first; first &= vertex_cache_t::INDEX_MASK; const GLubyte* vp = c->arrays.vertex.element(first); + v->obj.z = 0; + v->obj.w = 0x10000; c->arrays.vertex.fetch(c, v->obj.v, vp); c->arrays.mvp_transform(&c->transforms.mvp, &v->clip, &v->obj); c->arrays.perspective(c, v); @@ -975,6 +977,8 @@ void compileElements__generic(ogles_context_t* c, do { v->flags = 0; v->index = first++; + v->obj.z = 0; + v->obj.w = 0x10000; c->arrays.vertex.fetch(c, v->obj.v, vp); c->arrays.mvp_transform(mvp, &v->clip, &v->obj); c->arrays.perspective(c, v); diff --git a/opengl/libagl/light.cpp b/opengl/libagl/light.cpp index 25c41d0c5b..8ae32cc0f0 100644 --- a/opengl/libagl/light.cpp +++ b/opengl/libagl/light.cpp @@ -38,13 +38,14 @@ static void lightVertex(ogles_context_t* c, vertex_t* v); static void lightVertexMaterial(ogles_context_t* c, vertex_t* v); static inline void vscale3(GLfixed* d, const GLfixed* m, GLfixed s); -static inline void vsub3w(GLfixed* d, const GLfixed* a, const GLfixed* b); static __attribute__((noinline)) void vnorm3(GLfixed* d, const GLfixed* a); static inline void vsa3(GLfixed* d, const GLfixed* m, GLfixed s, const GLfixed* a); +static inline void vss3(GLfixed* d, + const GLfixed* m, GLfixed s, const GLfixed* a); static inline void vmla3(GLfixed* d, const GLfixed* m0, const GLfixed* m1, const GLfixed* a); static inline void vmul3(GLfixed* d, @@ -151,18 +152,10 @@ void vsa3(GLfixed* d, const GLfixed* m, GLfixed s, const GLfixed* a) { } static inline -void vsub3w(GLfixed* d, const GLfixed* a, const GLfixed* b) { - const GLfixed wa = a[3]; - const GLfixed wb = b[3]; - if (ggl_likely(wa == wb)) { - d[0] = a[0] - b[0]; - d[1] = a[1] - b[1]; - d[2] = a[2] - b[2]; - } else { - d[0] = gglMulSubx(a[0], wb, gglMulx(b[0], wa)); - d[1] = gglMulSubx(a[1], wb, gglMulx(b[1], wa)); - d[2] = gglMulSubx(a[2], wb, gglMulx(b[2], wa)); - } +void vss3(GLfixed* d, const GLfixed* m, GLfixed s, const GLfixed* a) { + d[0] = gglMulSubx(m[0], s, a[0]); + d[1] = gglMulSubx(m[1], s, a[1]); + d[2] = gglMulSubx(m[2], s, a[2]); } static inline @@ -227,7 +220,7 @@ static inline void validate_light_mvi(ogles_context_t* c) const int i = 31 - gglClz(en); en &= ~(1<<i); light_t& l = c->lighting.lights[i]; - c->transforms.mvui.point3(&c->transforms.mvui, + c->transforms.mvui.point4(&c->transforms.mvui, &l.objPosition, &l.position); vnorm3(l.normalizedObjPosition.v, l.objPosition.v); } @@ -318,6 +311,11 @@ void lightVertexMaterial(ogles_context_t* c, vertex_t* v) vmul3(l.implicitAmbient.v, material.ambient.v, l.ambient.v); vmul3(l.implicitDiffuse.v, material.diffuse.v, l.diffuse.v); vmul3(l.implicitSpecular.v, material.specular.v, l.specular.v); + // this is just a flag to tell if we have a specular component + l.implicitSpecular.v[3] = + l.implicitSpecular.r | + l.implicitSpecular.g | + l.implicitSpecular.b; } // emission and ambient for the whole scene vmla3( c->lighting.implicitSceneEmissionAndAmbient.v, @@ -343,7 +341,11 @@ void lightVertex(ogles_context_t* c, vertex_t* v) vec4_t n; c->arrays.normal.fetch(c, n.v, c->arrays.normal.element(v->index & vertex_cache_t::INDEX_MASK)); - if (c->transforms.rescaleNormals == GL_NORMALIZE) + + // TODO: right now we handle GL_RESCALE_NORMALS as if ti were + // GL_NORMALIZE. We could optimize this by scaling mvui + // appropriately instead. + if (c->transforms.rescaleNormals) vnorm3(n.v, n.v); const material_t& material = c->lighting.front; @@ -360,7 +362,8 @@ void lightVertex(ogles_context_t* c, vertex_t* v) // compute vertex-to-light vector if (ggl_unlikely(l.position.w)) { - vsub3w(d.v, l.objPosition.v, v->obj.v); + // lightPos/1.0 - vertex/vertex.w == lightPos*vertex.w - vertex + vss3(d.v, l.objPosition.v, v->obj.w, v->obj.v); sqDist = dot3(d.v, d.v); vscale3(d.v, d.v, gglSqrtRecipx(sqDist)); } else { diff --git a/opengl/libagl/matrix.cpp b/opengl/libagl/matrix.cpp index f175cdad6e..0b68dc06f5 100644 --- a/opengl/libagl/matrix.cpp +++ b/opengl/libagl/matrix.cpp @@ -55,7 +55,7 @@ static void normal__nop(transform_t const*, vec4_t* c, vec4_t const* o); static void point2__generic(transform_t const*, vec4_t* c, vec4_t const* o); static void point3__generic(transform_t const*, vec4_t* c, vec4_t const* o); static void point4__generic(transform_t const*, vec4_t* c, vec4_t const* o); -static void normal__generic(transform_t const*, vec4_t* c, vec4_t const* o); +static void point4__mvui(transform_t const*, vec4_t* c, vec4_t const* o); // ---------------------------------------------------------------------------- #if 0 @@ -209,7 +209,8 @@ void mvui_transform_t::picker() { flags = 0; ops = OP_ALL; - point3 = normal__generic; + point3 = point4__mvui; + point4 = point4__mvui; } void transform_t::dump(const char* what) @@ -596,66 +597,19 @@ void transform_state_t::update_mvit() void transform_state_t::update_mvui() { + GLfloat r[16]; const GLfloat* const mv = modelview.top().elements(); - - /* - When transforming normals, we can use the upper 3x3 matrix, see: - http://www.opengl.org/documentation/specs/version1.1/glspec1.1/node26.html - */ - // Also note that: - // l(obj) = tr(M).l(eye) for infinite light - // l(obj) = inv(M).l(eye) for local light - - const uint32_t ops = modelview.top_ops() & ~OP_TRANSLATE; - if (ggl_likely((!(ops & ~OP_ROTATE)) || - (rescaleNormals && modelview.isRigidBody()))) { - // if the modelview matrix is a rigid body transformation - // (translation, rotation, uniform scaling), then we can bypass - // the inverse by transposing the matrix. - GLfloat rescale = 1.0f; - if (rescaleNormals == GL_RESCALE_NORMAL) { - if (!(ops & ~OP_UNIFORM_SCALE)) { - rescale = reciprocalf(mv[I(0,0)]); - } else { - rescale = rsqrtf( - sqrf(mv[I(2,0)]) + sqrf(mv[I(2,1)]) + sqrf(mv[I(2,2)])); - } - } - GLfixed* const x = mvui.matrix.m; - for (int i=0 ; i<3 ; i++) { - x[I(i,0)] = gglFloatToFixed(mv[I(0,i)] * rescale); - x[I(i,1)] = gglFloatToFixed(mv[I(1,i)] * rescale); - x[I(i,2)] = gglFloatToFixed(mv[I(2,i)] * rescale); - } - mvui.picker(); - return; - } - - GLfloat r[3][3]; - r[0][0] = det22(mv[I(1,1)], mv[I(2,1)], mv[I(1,2)], mv[I(2,2)]); - r[0][1] =ndet22(mv[I(0,1)], mv[I(2,1)], mv[I(0,2)], mv[I(2,2)]); - r[0][2] = det22(mv[I(0,1)], mv[I(1,1)], mv[I(0,2)], mv[I(1,2)]); - r[1][0] =ndet22(mv[I(1,0)], mv[I(2,0)], mv[I(1,2)], mv[I(2,2)]); - r[1][1] = det22(mv[I(0,0)], mv[I(2,0)], mv[I(0,2)], mv[I(2,2)]); - r[1][2] =ndet22(mv[I(0,0)], mv[I(1,0)], mv[I(0,2)], mv[I(1,2)]); - r[2][0] = det22(mv[I(1,0)], mv[I(2,0)], mv[I(1,1)], mv[I(2,1)]); - r[2][1] =ndet22(mv[I(0,0)], mv[I(2,0)], mv[I(0,1)], mv[I(2,1)]); - r[2][2] = det22(mv[I(0,0)], mv[I(1,0)], mv[I(0,1)], mv[I(1,1)]); - - GLfloat rdet; - if (rescaleNormals == GL_RESCALE_NORMAL) { - rdet = rsqrtf(sqrf(r[0][2]) + sqrf(r[1][2]) + sqrf(r[2][2])); - } else { - rdet = reciprocalf( - r[0][0]*mv[I(0,0)] + r[0][1]*mv[I(1,0)] + r[0][2]*mv[I(2,0)]); - } + // TODO: we need a faster invert, especially for when the modelview + // is a rigid-body matrix + invert(r, mv); GLfixed* const x = mvui.matrix.m; - for (int i=0 ; i<3 ; i++) { - x[I(i,0)] = gglFloatToFixed(r[i][0] * rdet); - x[I(i,1)] = gglFloatToFixed(r[i][1] * rdet); - x[I(i,2)] = gglFloatToFixed(r[i][2] * rdet); + for (int i=0 ; i<4 ; i++) { + x[I(i,0)] = gglFloatToFixed(r[I(i,0)]); + x[I(i,1)] = gglFloatToFixed(r[I(i,1)]); + x[I(i,2)] = gglFloatToFixed(r[I(i,2)]); + x[I(i,4)] = gglFloatToFixed(r[I(i,3)]); } mvui.picker(); } @@ -783,14 +737,19 @@ void point4__generic(transform_t const* mx, vec4_t* lhs, vec4_t const* rhs) { lhs->w = mla4(rx, m[ 3], ry, m[ 7], rz, m[11], rw, m[15]); } -void normal__generic(transform_t const* mx, vec4_t* lhs, vec4_t const* rhs) { +void point4__mvui(transform_t const* mx, vec4_t* lhs, vec4_t const* rhs) { + // this used for transforming light positions back to object space. + // Lights have 3 components positions, so w is always 1. + // however, it is used as a switch for directional lights, so we need + // to preserve it. const GLfixed* const m = mx->matrix.m; const GLfixed rx = rhs->x; const GLfixed ry = rhs->y; const GLfixed rz = rhs->z; - lhs->x = mla3(rx, m[ 0], ry, m[ 4], rz, m[ 8]); - lhs->y = mla3(rx, m[ 1], ry, m[ 5], rz, m[ 9]); - lhs->z = mla3(rx, m[ 2], ry, m[ 6], rz, m[10]); + lhs->x = mla3a(rx, m[ 0], ry, m[ 4], rz, m[ 8], m[12]); + lhs->y = mla3a(rx, m[ 1], ry, m[ 5], rz, m[ 9], m[13]); + lhs->z = mla3a(rx, m[ 2], ry, m[ 6], rz, m[10], m[14]); + lhs->w = rhs->w; } diff --git a/opengl/tests/lighting1709/Android.mk b/opengl/tests/lighting1709/Android.mk new file mode 100644 index 0000000000..9563e617f3 --- /dev/null +++ b/opengl/tests/lighting1709/Android.mk @@ -0,0 +1,11 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_PACKAGE_NAME := LightingTest +LOCAL_CERTIFICATE := platform + +include $(BUILD_PACKAGE) diff --git a/opengl/tests/lighting1709/AndroidManifest.xml b/opengl/tests/lighting1709/AndroidManifest.xml new file mode 100644 index 0000000000..6c23d422f5 --- /dev/null +++ b/opengl/tests/lighting1709/AndroidManifest.xml @@ -0,0 +1,13 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.lightingtest"> + + <application> + <activity android:name="ClearActivity" android:label="LightingTest"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/opengl/tests/lighting1709/src/com/android/lightingtest/ClearActivity.java b/opengl/tests/lighting1709/src/com/android/lightingtest/ClearActivity.java new file mode 100644 index 0000000000..3ae8c5ca29 --- /dev/null +++ b/opengl/tests/lighting1709/src/com/android/lightingtest/ClearActivity.java @@ -0,0 +1,174 @@ +/* + * 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. + */ + +package com.android.lightingtest; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; + +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; + +import android.app.Activity; +import android.content.Context; +import android.opengl.GLSurfaceView; +import android.os.Bundle; +import android.util.Log; +import android.view.MotionEvent; + +public class ClearActivity extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mGLView = new ClearGLSurfaceView(this); + setContentView(mGLView); + } + + @Override + protected void onPause() { + super.onPause(); + mGLView.onPause(); + } + + @Override + protected void onResume() { + super.onResume(); + mGLView.onResume(); + } + private GLSurfaceView mGLView; +} + +class ClearGLSurfaceView extends GLSurfaceView { + public ClearGLSurfaceView(Context context) { + super(context); + mRenderer = new ClearRenderer(); + setRenderer(mRenderer); + } + + ClearRenderer mRenderer; +} + +class ClearRenderer implements GLSurfaceView.Renderer { + public ClearRenderer() { + } + + public void onSurfaceCreated(GL10 gl, EGLConfig config) { + // Do nothing special. + } + + public void onSurfaceChanged(GL10 gl, int w, int h) { + // Compute the projection matrix + gl.glMatrixMode(GL10.GL_PROJECTION); + gl.glLoadIdentity(); + + // Compute the boundaries of the frustum + float fl = (float) (-(w / 2)) / 288; + float fr = (float) (w / 2) / 288; + float ft = (float) (h / 2) / 288; + float fb = (float) (-(h / 2)) / 288; + + // Set the view frustum + gl.glFrustumf(fl, fr, fb, ft, 1.0f, 2000.0f); + + // Set the viewport dimensions + gl.glMatrixMode(GL10.GL_MODELVIEW); + gl.glLoadIdentity(); + gl.glViewport(0, 0, w, h); + } + + public void onDrawFrame(GL10 gl) { + gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); + + final float lightOff[] = {0.0f, 0.0f, 0.0f, 1.0f}; + final float lightAmbient[] = {5.0f, 0.0f, 0.0f, 1.0f}; + final float lightDiffuse[] = {0.0f, 2.0f, 0.0f, 0.0f}; + final float lightPosSpot[] = {0.0f, 0.0f, -8.0f, 1.0f}; + + final float pos[] = { + -5.0f, -1.5f, 0.0f, + 0.0f, -1.5f, 0.0f, + 5.0f, -1.5f, 0.0f, + }; + + final float v[] = new float[9]; + ByteBuffer vbb = ByteBuffer.allocateDirect(v.length*4); + vbb.order(ByteOrder.nativeOrder()); + FloatBuffer vb = vbb.asFloatBuffer(); + + gl.glDisable(GL10.GL_DITHER); + + gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, lightAmbient, 0); + gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, lightDiffuse, 0); + gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SPECULAR, lightOff, 0); + gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, lightPosSpot, 0); + gl.glEnable(GL10.GL_LIGHT0); + + gl.glEnable(GL10.GL_LIGHTING); + + + gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); + gl.glNormal3f(0, 0, 1); + + + // draw first 3 triangles, without using transforms + for (int i=0 ; i<3 ; i++) { + v[0] = -1; v[1] =-1; v[2] = -10; + v[3] = 0; v[4] = 1; v[5] = -10; + v[6] = 1; v[7] =-1; v[8] = -10; + for (int j=0 ; j<3 ; j++) { + v[j*3+0] -= pos[i*3+0]; + v[j*3+1] -= pos[i*3+1]; + v[j*3+2] -= pos[i*3+2]; + } + vb.put(v).position(0); + gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vb); + gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 3); + } + + // draw the 2nd batch this time with transforms + v[0] = -1; v[1] =-1; v[2] = -10; + v[3] = 0; v[4] = 1; v[5] = -10; + v[6] = 1; v[7] =-1; v[8] = -10; + vb.put(v).position(0); + gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vb); + + // draw lower left triangle + gl.glPushMatrix(); + gl.glTranslatef(pos[0], pos[1], pos[2]); + gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 3); + gl.glPopMatrix(); + + // draw lower middle triangle + gl.glPushMatrix(); + gl.glTranslatef(pos[3], pos[4], pos[5]); + gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 3); + gl.glPopMatrix(); + + // draw lower right triangle + gl.glPushMatrix(); + gl.glTranslatef(pos[6], pos[7], pos[8]); + gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 3); + gl.glPopMatrix(); + } + + public int[] getConfigSpec() { + int[] configSpec = { EGL10.EGL_DEPTH_SIZE, 16, EGL10.EGL_NONE }; + return configSpec; + } +} + diff --git a/tts/java/android/tts/SynthProxy.java b/tts/java/android/tts/SynthProxy.java new file mode 100755 index 0000000000..e065f40759 --- /dev/null +++ b/tts/java/android/tts/SynthProxy.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package android.tts; + +import android.util.Log; +import java.lang.ref.WeakReference; + +/** + * @hide + * + * The SpeechSynthesis class provides a high-level api to create and play + * synthesized speech. This class is used internally to talk to a native + * TTS library that implements the interface defined in + * frameworks/base/include/tts/TtsEngine.h + * + */ +@SuppressWarnings("unused") +public class SynthProxy { + + // + // External API + // + + /** + * Constructor; pass the location of the native TTS .so to use. + */ + public SynthProxy(String nativeSoLib) { + Log.e("TTS is loading", nativeSoLib); + native_setup(new WeakReference<SynthProxy>(this), nativeSoLib); + } + + /** + * Stops and clears the AudioTrack. + */ + public void stop() { + native_stop(mJniData); + } + + /** + * Synthesize speech and speak it directly using AudioTrack. + */ + public void speak(String text) { + native_speak(mJniData, text); + } + + /** + * Synthesize speech to a file. The current implementation writes a valid + * WAV file to the given path, assuming it is writable. Something like + * "/sdcard/???.wav" is recommended. + */ + public void synthesizeToFile(String text, String filename) { + native_synthesizeToFile(mJniData, text, filename); + } + + // TODO add IPA methods + + /** + * Sets the language + */ + public void setLanguage(String language) { + native_setLanguage(mJniData, language); + } + + /** + * Sets the speech rate + */ + public final void setSpeechRate(int speechRate) { + native_setSpeechRate(mJniData, speechRate); + } + + + /** + * Plays the given audio buffer + */ + public void playAudioBuffer(int bufferPointer, int bufferSize) { + native_playAudioBuffer(mJniData, bufferPointer, bufferSize); + } + + /** + * Gets the currently set language + */ + public String getLanguage() { + return native_getLanguage(mJniData); + } + + /** + * Gets the currently set rate + */ + public int getRate() { + return native_getRate(mJniData); + } + + /** + * Shuts down the native synthesizer + */ + public void shutdown() { + native_shutdown(mJniData); + } + + // + // Internal + // + + protected void finalize() { + native_finalize(mJniData); + mJniData = 0; + } + + static { + System.loadLibrary("ttssynthproxy"); + } + + private final static String TAG = "SynthProxy"; + + /** + * Accessed by native methods + */ + private int mJniData = 0; + + private native final void native_setup(Object weak_this, + String nativeSoLib); + + private native final void native_finalize(int jniData); + + private native final void native_stop(int jniData); + + private native final void native_speak(int jniData, String text); + + private native final void native_synthesizeToFile(int jniData, String text, String filename); + + private native final void native_setLanguage(int jniData, String language); + + private native final void native_setSpeechRate(int jniData, int speechRate); + + // TODO add buffer format + private native final void native_playAudioBuffer(int jniData, int bufferPointer, int bufferSize); + + private native final String native_getLanguage(int jniData); + + private native final int native_getRate(int jniData); + + private native final void native_shutdown(int jniData); + + + /** + * Callback from the C layer + */ + @SuppressWarnings("unused") + private static void postNativeSpeechSynthesizedInJava(Object tts_ref, + int bufferPointer, int bufferSize) { + + Log.i("TTS plugin debug", "bufferPointer: " + bufferPointer + + " bufferSize: " + bufferSize); + + SynthProxy nativeTTS = (SynthProxy)((WeakReference)tts_ref).get(); + // TODO notify TTS service of synthesis/playback completion, + // method definition to be changed. + } +} diff --git a/tts/java/android/tts/TtsService.java b/tts/java/android/tts/TtsService.java new file mode 100755 index 0000000000..d31718181a --- /dev/null +++ b/tts/java/android/tts/TtsService.java @@ -0,0 +1,783 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package android.tts; + +import android.tts.ITts.Stub; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.media.MediaPlayer; +import android.media.MediaPlayer.OnCompletionListener; +import android.net.Uri; +import android.os.IBinder; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.preference.PreferenceManager; +import android.util.Log; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.concurrent.locks.ReentrantLock; + +/** + * @hide Synthesizes speech from text. This is implemented as a service so that + * other applications can call the TTS without needing to bundle the TTS + * in the build. + * + */ +public class TtsService extends Service implements OnCompletionListener { + + private static class SpeechItem { + public static final int SPEECH = 0; + public static final int EARCON = 1; + public static final int SILENCE = 2; + public String mText = null; + public ArrayList<String> mParams = null; + public int mType = SPEECH; + public long mDuration = 0; + + public SpeechItem(String text, ArrayList<String> params, int itemType) { + mText = text; + mParams = params; + mType = itemType; + } + + public SpeechItem(long silenceTime) { + mDuration = silenceTime; + } + } + + /** + * Contains the information needed to access a sound resource; the name of + * the package that contains the resource and the resID of the resource + * within that package. + */ + private static class SoundResource { + public String mSourcePackageName = null; + public int mResId = -1; + public String mFilename = null; + + public SoundResource(String packageName, int id) { + mSourcePackageName = packageName; + mResId = id; + mFilename = null; + } + + public SoundResource(String file) { + mSourcePackageName = null; + mResId = -1; + mFilename = file; + } + } + + private static final String ACTION = "android.intent.action.USE_TTS"; + private static final String CATEGORY = "android.intent.category.TTS"; + private static final String PKGNAME = "android.tts"; + + final RemoteCallbackList<ITtsCallback> mCallbacks = new RemoteCallbackList<ITtsCallback>(); + + private Boolean mIsSpeaking; + private ArrayList<SpeechItem> mSpeechQueue; + private HashMap<String, SoundResource> mEarcons; + private HashMap<String, SoundResource> mUtterances; + private MediaPlayer mPlayer; + private TtsService mSelf; + + private SharedPreferences prefs; + + private final ReentrantLock speechQueueLock = new ReentrantLock(); + private final ReentrantLock synthesizerLock = new ReentrantLock(); + + // TODO support multiple SpeechSynthesis objects + private SynthProxy nativeSynth; + + @Override + public void onCreate() { + super.onCreate(); + Log.i("TTS", "TTS starting"); + + + // TODO: Make this work when the settings are done in the main Settings + // app. + prefs = PreferenceManager.getDefaultSharedPreferences(this); + + // TODO: This should be changed to work by requesting the path + // from the default engine. + nativeSynth = new SynthProxy(prefs.getString("engine_pref", "")); + + + mSelf = this; + mIsSpeaking = false; + + mEarcons = new HashMap<String, SoundResource>(); + mUtterances = new HashMap<String, SoundResource>(); + + mSpeechQueue = new ArrayList<SpeechItem>(); + mPlayer = null; + + setLanguage(prefs.getString("lang_pref", "en-rUS")); + setSpeechRate(Integer.parseInt(prefs.getString("rate_pref", "140"))); + } + + @Override + public void onDestroy() { + super.onDestroy(); + // Don't hog the media player + cleanUpPlayer(); + + nativeSynth.shutdown(); + + // Unregister all callbacks. + mCallbacks.kill(); + } + + private void setSpeechRate(int rate) { + if (prefs.getBoolean("override_pref", false)) { + // This is set to the default here so that the preview in the prefs + // activity will show the change without a restart, even if apps are + // not allowed to change the defaults. + rate = Integer.parseInt(prefs.getString("rate_pref", "140")); + } + nativeSynth.setSpeechRate(rate); + } + + private void setLanguage(String lang) { + if (prefs.getBoolean("override_pref", false)) { + // This is set to the default here so that the preview in the prefs + // activity will show the change without a restart, even if apps are + // not + // allowed to change the defaults. + lang = prefs.getString("lang_pref", "en-rUS"); + } + nativeSynth.setLanguage(lang); + } + + private void setEngine(String engineName, String[] requestedLanguages, + int strictness) { + // TODO: Implement engine selection code here. + Intent engineIntent = new Intent( + "android.intent.action.START_TTS_ENGINE"); + if (engineName != null) { + engineIntent.addCategory("android.intent.action.tts_engine." + + engineName); + } + for (int i = 0; i < requestedLanguages.length; i++) { + engineIntent.addCategory("android.intent.action.tts_lang." + + requestedLanguages[i]); + } + ResolveInfo[] enginesArray = new ResolveInfo[0]; + PackageManager pm = getPackageManager(); + enginesArray = pm.queryIntentActivities(engineIntent, 0).toArray( + enginesArray); + } + + private void setEngine(Intent engineIntent) { + // TODO: Implement engine selection code here. + } + + private int getEngineStatus() { + // TODO: Proposal - add a sanity check method that + // TTS engine plugins must implement. + return 0; + } + + /** + * Adds a sound resource to the TTS. + * + * @param text + * The text that should be associated with the sound resource + * @param packageName + * The name of the package which has the sound resource + * @param resId + * The resource ID of the sound within its package + */ + private void addSpeech(String text, String packageName, int resId) { + mUtterances.put(text, new SoundResource(packageName, resId)); + } + + /** + * Adds a sound resource to the TTS. + * + * @param text + * The text that should be associated with the sound resource + * @param filename + * The filename of the sound resource. This must be a complete + * path like: (/sdcard/mysounds/mysoundbite.mp3). + */ + private void addSpeech(String text, String filename) { + mUtterances.put(text, new SoundResource(filename)); + } + + /** + * Adds a sound resource to the TTS as an earcon. + * + * @param earcon + * The text that should be associated with the sound resource + * @param packageName + * The name of the package which has the sound resource + * @param resId + * The resource ID of the sound within its package + */ + private void addEarcon(String earcon, String packageName, int resId) { + mEarcons.put(earcon, new SoundResource(packageName, resId)); + } + + /** + * Adds a sound resource to the TTS as an earcon. + * + * @param earcon + * The text that should be associated with the sound resource + * @param filename + * The filename of the sound resource. This must be a complete + * path like: (/sdcard/mysounds/mysoundbite.mp3). + */ + private void addEarcon(String earcon, String filename) { + mEarcons.put(earcon, new SoundResource(filename)); + } + + /** + * Speaks the given text using the specified queueing mode and parameters. + * + * @param text + * The text that should be spoken + * @param queueMode + * 0 for no queue (interrupts all previous utterances), 1 for + * queued + * @param params + * An ArrayList of parameters. This is not implemented for all + * engines. + */ + private void speak(String text, int queueMode, ArrayList<String> params) { + if (queueMode == 0) { + stop(); + } + mSpeechQueue.add(new SpeechItem(text, params, SpeechItem.SPEECH)); + if (!mIsSpeaking) { + processSpeechQueue(); + } + } + + /** + * Plays the earcon using the specified queueing mode and parameters. + * + * @param earcon + * The earcon that should be played + * @param queueMode + * 0 for no queue (interrupts all previous utterances), 1 for + * queued + * @param params + * An ArrayList of parameters. This is not implemented for all + * engines. + */ + private void playEarcon(String earcon, int queueMode, + ArrayList<String> params) { + if (queueMode == 0) { + stop(); + } + mSpeechQueue.add(new SpeechItem(earcon, params, SpeechItem.EARCON)); + if (!mIsSpeaking) { + processSpeechQueue(); + } + } + + /** + * Stops all speech output and removes any utterances still in the queue. + */ + private void stop() { + Log.i("TTS", "Stopping"); + mSpeechQueue.clear(); + + nativeSynth.stop(); + mIsSpeaking = false; + if (mPlayer != null) { + try { + mPlayer.stop(); + } catch (IllegalStateException e) { + // Do nothing, the player is already stopped. + } + } + Log.i("TTS", "Stopped"); + } + + public void onCompletion(MediaPlayer arg0) { + processSpeechQueue(); + } + + private void playSilence(long duration, int queueMode, + ArrayList<String> params) { + if (queueMode == 0) { + stop(); + } + mSpeechQueue.add(new SpeechItem(duration)); + if (!mIsSpeaking) { + processSpeechQueue(); + } + } + + private void silence(final long duration) { + class SilenceThread implements Runnable { + public void run() { + try { + Thread.sleep(duration); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + processSpeechQueue(); + } + } + } + Thread slnc = (new Thread(new SilenceThread())); + slnc.setPriority(Thread.MIN_PRIORITY); + slnc.start(); + } + + private void speakInternalOnly(final String text, + final ArrayList<String> params) { + class SynthThread implements Runnable { + public void run() { + boolean synthAvailable = false; + try { + synthAvailable = synthesizerLock.tryLock(); + if (!synthAvailable) { + Thread.sleep(100); + Thread synth = (new Thread(new SynthThread())); + synth.setPriority(Thread.MIN_PRIORITY); + synth.start(); + return; + } + nativeSynth.speak(text); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + // This check is needed because finally will always run; + // even if the + // method returns somewhere in the try block. + if (synthAvailable) { + synthesizerLock.unlock(); + } + } + } + } + Thread synth = (new Thread(new SynthThread())); + synth.setPriority(Thread.MIN_PRIORITY); + synth.start(); + } + + private SoundResource getSoundResource(SpeechItem speechItem) { + SoundResource sr = null; + String text = speechItem.mText; + if (speechItem.mType == SpeechItem.SILENCE) { + // Do nothing if this is just silence + } else if (speechItem.mType == SpeechItem.EARCON) { + sr = mEarcons.get(text); + } else { + sr = mUtterances.get(text); + } + return sr; + } + + private void dispatchSpeechCompletedCallbacks(String mark) { + Log.i("TTS callback", "dispatch started"); + // Broadcast to all clients the new value. + final int N = mCallbacks.beginBroadcast(); + for (int i = 0; i < N; i++) { + try { + mCallbacks.getBroadcastItem(i).markReached(mark); + } catch (RemoteException e) { + // The RemoteCallbackList will take care of removing + // the dead object for us. + } + } + mCallbacks.finishBroadcast(); + Log.i("TTS callback", "dispatch completed to " + N); + } + + private void processSpeechQueue() { + boolean speechQueueAvailable = false; + try { + speechQueueAvailable = speechQueueLock.tryLock(); + if (!speechQueueAvailable) { + return; + } + if (mSpeechQueue.size() < 1) { + mIsSpeaking = false; + // Dispatch a completion here as this is the + // only place where speech completes normally. + // Nothing left to say in the queue is a special case + // that is always a "mark" - associated text is null. + dispatchSpeechCompletedCallbacks(""); + return; + } + + SpeechItem currentSpeechItem = mSpeechQueue.get(0); + mIsSpeaking = true; + SoundResource sr = getSoundResource(currentSpeechItem); + // Synth speech as needed - synthesizer should call + // processSpeechQueue to continue running the queue + Log.i("TTS processing: ", currentSpeechItem.mText); + if (sr == null) { + if (currentSpeechItem.mType == SpeechItem.SPEECH) { + // TODO: Split text up into smaller chunks before accepting + // them + // for processing. + speakInternalOnly(currentSpeechItem.mText, + currentSpeechItem.mParams); + } else { + // This is either silence or an earcon that was missing + silence(currentSpeechItem.mDuration); + } + } else { + cleanUpPlayer(); + if (sr.mSourcePackageName == PKGNAME) { + // Utterance is part of the TTS library + mPlayer = MediaPlayer.create(this, sr.mResId); + } else if (sr.mSourcePackageName != null) { + // Utterance is part of the app calling the library + Context ctx; + try { + ctx = this.createPackageContext(sr.mSourcePackageName, + 0); + } catch (NameNotFoundException e) { + e.printStackTrace(); + mSpeechQueue.remove(0); // Remove it from the queue and + // move on + mIsSpeaking = false; + return; + } + mPlayer = MediaPlayer.create(ctx, sr.mResId); + } else { + // Utterance is coming from a file + mPlayer = MediaPlayer.create(this, Uri.parse(sr.mFilename)); + } + + // Check if Media Server is dead; if it is, clear the queue and + // give up for now - hopefully, it will recover itself. + if (mPlayer == null) { + mSpeechQueue.clear(); + mIsSpeaking = false; + return; + } + mPlayer.setOnCompletionListener(this); + try { + mPlayer.start(); + } catch (IllegalStateException e) { + mSpeechQueue.clear(); + mIsSpeaking = false; + cleanUpPlayer(); + return; + } + } + if (mSpeechQueue.size() > 0) { + mSpeechQueue.remove(0); + } + } finally { + // This check is needed because finally will always run; even if the + // method returns somewhere in the try block. + if (speechQueueAvailable) { + speechQueueLock.unlock(); + } + } + } + + private void cleanUpPlayer() { + if (mPlayer != null) { + mPlayer.release(); + mPlayer = null; + } + } + + /** + * Synthesizes the given text using the specified queuing mode and + * parameters. + * + * @param text + * The String of text that should be synthesized + * @param params + * An ArrayList of parameters. The first element of this array + * controls the type of voice to use. + * @param filename + * The string that gives the full output filename; it should be + * something like "/sdcard/myappsounds/mysound.wav". + * @return A boolean that indicates if the synthesis succeeded + */ + private boolean synthesizeToFile(String text, ArrayList<String> params, + String filename, boolean calledFromApi) { + // Only stop everything if this is a call made by an outside app trying + // to + // use the API. Do NOT stop if this is a call from within the service as + // clearing the speech queue here would be a mistake. + if (calledFromApi) { + stop(); + } + Log.i("TTS", "Synthesizing to " + filename); + boolean synthAvailable = false; + try { + synthAvailable = synthesizerLock.tryLock(); + if (!synthAvailable) { + return false; + } + // Don't allow a filename that is too long + // TODO use platform constant + if (filename.length() > 250) { + return false; + } + nativeSynth.synthesizeToFile(text, filename); + } finally { + // This check is needed because finally will always run; even if the + // method returns somewhere in the try block. + if (synthAvailable) { + synthesizerLock.unlock(); + } + } + Log.i("TTS", "Completed synthesis for " + filename); + return true; + } + + @Override + public IBinder onBind(Intent intent) { + if (ACTION.equals(intent.getAction())) { + for (String category : intent.getCategories()) { + if (category.equals(CATEGORY)) { + return mBinder; + } + } + } + return null; + } + + private final ITts.Stub mBinder = new Stub() { + + public void registerCallback(ITtsCallback cb) { + if (cb != null) + mCallbacks.register(cb); + } + + public void unregisterCallback(ITtsCallback cb) { + if (cb != null) + mCallbacks.unregister(cb); + } + + /** + * Gives a hint about the type of engine that is preferred. + * + * @param selectedEngine + * The TTS engine that should be used + */ + public void setEngine(String engineName, String[] supportedLanguages, + int strictness) { + mSelf.setEngine(engineName, supportedLanguages, strictness); + } + + /** + * Specifies exactly what the engine has to support. Will always be + * considered "strict"; can be used for implementing + * optional/experimental features that are not supported by all engines. + * + * @param engineIntent + * An intent that specifies exactly what the engine has to + * support. + */ + public void setEngineWithIntent(Intent engineIntent) { + mSelf.setEngine(engineIntent); + } + + /** + * Speaks the given text using the specified queueing mode and + * parameters. + * + * @param text + * The text that should be spoken + * @param queueMode + * 0 for no queue (interrupts all previous utterances), 1 for + * queued + * @param params + * An ArrayList of parameters. The first element of this + * array controls the type of voice to use. + */ + public void speak(String text, int queueMode, String[] params) { + ArrayList<String> speakingParams = new ArrayList<String>(); + if (params != null) { + speakingParams = new ArrayList<String>(Arrays.asList(params)); + } + mSelf.speak(text, queueMode, speakingParams); + } + + /** + * Plays the earcon using the specified queueing mode and parameters. + * + * @param earcon + * The earcon that should be played + * @param queueMode + * 0 for no queue (interrupts all previous utterances), 1 for + * queued + * @param params + * An ArrayList of parameters. + */ + public void playEarcon(String earcon, int queueMode, String[] params) { + ArrayList<String> speakingParams = new ArrayList<String>(); + if (params != null) { + speakingParams = new ArrayList<String>(Arrays.asList(params)); + } + mSelf.playEarcon(earcon, queueMode, speakingParams); + } + + /** + * Plays the silence using the specified queueing mode and parameters. + * + * @param duration + * The duration of the silence that should be played + * @param queueMode + * 0 for no queue (interrupts all previous utterances), 1 for + * queued + * @param params + * An ArrayList of parameters. + */ + public void playSilence(long duration, int queueMode, String[] params) { + ArrayList<String> speakingParams = new ArrayList<String>(); + if (params != null) { + speakingParams = new ArrayList<String>(Arrays.asList(params)); + } + mSelf.playSilence(duration, queueMode, speakingParams); + } + + + /** + * Stops all speech output and removes any utterances still in the + * queue. + */ + public void stop() { + mSelf.stop(); + } + + /** + * Returns whether or not the TTS is speaking. + * + * @return Boolean to indicate whether or not the TTS is speaking + */ + public boolean isSpeaking() { + return (mSelf.mIsSpeaking && (mSpeechQueue.size() < 1)); + } + + /** + * Adds a sound resource to the TTS. + * + * @param text + * The text that should be associated with the sound resource + * @param packageName + * The name of the package which has the sound resource + * @param resId + * The resource ID of the sound within its package + */ + public void addSpeech(String text, String packageName, int resId) { + mSelf.addSpeech(text, packageName, resId); + } + + /** + * Adds a sound resource to the TTS. + * + * @param text + * The text that should be associated with the sound resource + * @param filename + * The filename of the sound resource. This must be a + * complete path like: (/sdcard/mysounds/mysoundbite.mp3). + */ + public void addSpeechFile(String text, String filename) { + mSelf.addSpeech(text, filename); + } + + /** + * Adds a sound resource to the TTS as an earcon. + * + * @param earcon + * The text that should be associated with the sound resource + * @param packageName + * The name of the package which has the sound resource + * @param resId + * The resource ID of the sound within its package + */ + public void addEarcon(String earcon, String packageName, int resId) { + mSelf.addEarcon(earcon, packageName, resId); + } + + /** + * Adds a sound resource to the TTS as an earcon. + * + * @param earcon + * The text that should be associated with the sound resource + * @param filename + * The filename of the sound resource. This must be a + * complete path like: (/sdcard/mysounds/mysoundbite.mp3). + */ + public void addEarconFile(String earcon, String filename) { + mSelf.addEarcon(earcon, filename); + } + + /** + * Sets the speech rate for the TTS. Note that this will only have an + * effect on synthesized speech; it will not affect pre-recorded speech. + * + * @param speechRate + * The speech rate that should be used + */ + public void setSpeechRate(int speechRate) { + mSelf.setSpeechRate(speechRate); + } + + // TODO: Fix comment about language + /** + * Sets the speech rate for the TTS. Note that this will only have an + * effect on synthesized speech; it will not affect pre-recorded speech. + * + * @param language + * The language to be used. The languages are specified by + * their IETF language tags as defined by BCP 47. This is the + * same standard used for the lang attribute in HTML. See: + * http://en.wikipedia.org/wiki/IETF_language_tag + */ + public void setLanguage(String language) { + mSelf.setLanguage(language); + } + + /** + * Speaks the given text using the specified queueing mode and + * parameters. + * + * @param text + * The String of text that should be synthesized + * @param params + * An ArrayList of parameters. The first element of this + * array controls the type of voice to use. + * @param filename + * The string that gives the full output filename; it should + * be something like "/sdcard/myappsounds/mysound.wav". + * @return A boolean that indicates if the synthesis succeeded + */ + public boolean synthesizeToFile(String text, String[] params, + String filename) { + ArrayList<String> speakingParams = new ArrayList<String>(); + if (params != null) { + speakingParams = new ArrayList<String>(Arrays.asList(params)); + } + return mSelf.synthesizeToFile(text, speakingParams, filename, true); + } + }; + +} diff --git a/tts/jni/Android.mk b/tts/jni/Android.mk new file mode 100755 index 0000000000..665d6d20fc --- /dev/null +++ b/tts/jni/Android.mk @@ -0,0 +1,31 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + android_tts_SynthProxy.cpp + +LOCAL_C_INCLUDES += \ + $(JNI_H_INCLUDE) + +LOCAL_SHARED_LIBRARIES := \ + libandroid_runtime \ + libnativehelper \ + libmedia \ + libutils \ + libcutils + +ifeq ($(TARGET_SIMULATOR),true) + LOCAL_LDLIBS += -ldl +else + LOCAL_SHARED_LIBRARIES += libdl +endif + + +LOCAL_MODULE:= libttssynthproxy + +LOCAL_ARM_MODE := arm + +LOCAL_PRELINK_MODULE := false + +include $(BUILD_SHARED_LIBRARY) + diff --git a/tts/jni/android_tts_SynthProxy.cpp b/tts/jni/android_tts_SynthProxy.cpp new file mode 100755 index 0000000000..d8f1bf3607 --- /dev/null +++ b/tts/jni/android_tts_SynthProxy.cpp @@ -0,0 +1,595 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * 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 <stdio.h> +#include <unistd.h> + +#define LOG_TAG "SynthProxy" + +#include <utils/Log.h> +#include <nativehelper/jni.h> +#include <nativehelper/JNIHelp.h> +#include <android_runtime/AndroidRuntime.h> +#include <tts/TtsEngine.h> +#include <media/AudioTrack.h> + +#include <dlfcn.h> + +#define DEFAULT_TTS_RATE 16000 +#define DEFAULT_TTS_FORMAT AudioSystem::PCM_16_BIT +#define DEFAULT_TTS_NB_CHANNELS 1 + +#define USAGEMODE_PLAY_IMMEDIATELY 0 +#define USAGEMODE_WRITE_TO_FILE 1 + +using namespace android; + +// ---------------------------------------------------------------------------- +struct fields_t { + jfieldID synthProxyFieldJniData; + jclass synthProxyClass; + jmethodID synthProxyMethodPost; +}; + +struct afterSynthData_t { + jint jniStorage; + int usageMode; + FILE* outputFile; +}; + +// ---------------------------------------------------------------------------- +static fields_t javaTTSFields; + +// ---------------------------------------------------------------------------- +class SynthProxyJniStorage { + public : + //jclass tts_class; + jobject tts_ref; + TtsEngine* mNativeSynthInterface; + AudioTrack* mAudioOut; + uint32_t mSampleRate; + AudioSystem::audio_format mAudFormat; + int mNbChannels; + + SynthProxyJniStorage() { + //tts_class = NULL; + tts_ref = NULL; + mNativeSynthInterface = NULL; + mAudioOut = NULL; + mSampleRate = DEFAULT_TTS_RATE; + mAudFormat = DEFAULT_TTS_FORMAT; + mNbChannels = DEFAULT_TTS_NB_CHANNELS; + } + + ~SynthProxyJniStorage() { + killAudio(); + if (mNativeSynthInterface) { + mNativeSynthInterface->shutdown(); + mNativeSynthInterface = NULL; + } + } + + void killAudio() { + if (mAudioOut) { + mAudioOut->stop(); + delete mAudioOut; + mAudioOut = NULL; + } + } + + void createAudioOut(uint32_t rate, AudioSystem::audio_format format, + int channel) { + mSampleRate = rate; + mAudFormat = format; + mNbChannels = channel; + + // TODO use the TTS stream type + int streamType = AudioSystem::MUSIC; + + // retrieve system properties to ensure successful creation of the + // AudioTrack object for playback + int afSampleRate; + if (AudioSystem::getOutputSamplingRate(&afSampleRate, streamType) != NO_ERROR) { + afSampleRate = 44100; + } + int afFrameCount; + if (AudioSystem::getOutputFrameCount(&afFrameCount, streamType) != NO_ERROR) { + afFrameCount = 2048; + } + uint32_t afLatency; + if (AudioSystem::getOutputLatency(&afLatency, streamType) != NO_ERROR) { + afLatency = 500; + } + uint32_t minBufCount = afLatency / ((1000 * afFrameCount)/afSampleRate); + if (minBufCount < 2) minBufCount = 2; + int minFrameCount = (afFrameCount * rate * minBufCount)/afSampleRate; + + mAudioOut = new AudioTrack(streamType, rate, format, channel, + minFrameCount > 4096 ? minFrameCount : 4096, + 0, 0, 0, 0); // not using an AudioTrack callback + + if (mAudioOut->initCheck() != NO_ERROR) { + LOGI("AudioTrack error"); + delete mAudioOut; + mAudioOut = NULL; + } else { + LOGI("AudioTrack OK"); + mAudioOut->start(); + LOGI("AudioTrack started"); + } + } +}; + + +// ---------------------------------------------------------------------------- +void prepAudioTrack(SynthProxyJniStorage* pJniData, + uint32_t rate, AudioSystem::audio_format format, int channel) +{ + // Don't bother creating a new audiotrack object if the current + // object is already set. + if ( pJniData->mAudioOut && + (rate == pJniData->mSampleRate) && + (format == pJniData->mAudFormat) && + (channel == pJniData->mNbChannels) ){ + return; + } + if (pJniData->mAudioOut){ + pJniData->killAudio(); + } + pJniData->createAudioOut(rate, format, channel); +} + + +// ---------------------------------------------------------------------------- +/* + * Callback from TTS engine. + * Directly speaks using AudioTrack or write to file + */ +static void ttsSynthDoneCB(void * userdata, uint32_t rate, + AudioSystem::audio_format format, int channel, + int8_t *wav, size_t bufferSize) { + LOGI("ttsSynthDoneCallback: %d bytes", bufferSize); + + afterSynthData_t* pForAfter = (afterSynthData_t*)userdata; + + if (pForAfter->usageMode == USAGEMODE_PLAY_IMMEDIATELY){ + LOGI("Direct speech"); + + if (wav == NULL) { + LOGI("Null: speech has completed"); + } + + if (bufferSize > 0) { + SynthProxyJniStorage* pJniData = + (SynthProxyJniStorage*)(pForAfter->jniStorage); + prepAudioTrack(pJniData, rate, format, channel); + if (pJniData->mAudioOut) { + pJniData->mAudioOut->write(wav, bufferSize); + LOGI("AudioTrack wrote: %d bytes", bufferSize); + } else { + LOGI("Can't play, null audiotrack"); + } + } + } else if (pForAfter->usageMode == USAGEMODE_WRITE_TO_FILE) { + LOGI("Save to file"); + if (wav == NULL) { + LOGI("Null: speech has completed"); + } + if (bufferSize > 0){ + fwrite(wav, 1, bufferSize, pForAfter->outputFile); + } + } + // TODO update to call back into the SynthProxy class through the + // javaTTSFields.synthProxyMethodPost methode to notify + // playback has completed + + delete pForAfter; + return; +} + + +// ---------------------------------------------------------------------------- +static void +android_tts_SynthProxy_native_setup(JNIEnv *env, jobject thiz, + jobject weak_this, jstring nativeSoLib) +{ + SynthProxyJniStorage* pJniStorage = new SynthProxyJniStorage(); + + prepAudioTrack(pJniStorage, + DEFAULT_TTS_RATE, DEFAULT_TTS_FORMAT, DEFAULT_TTS_NB_CHANNELS); + + const char *nativeSoLibNativeString = + env->GetStringUTFChars(nativeSoLib, 0); + + void *engine_lib_handle = dlopen(nativeSoLibNativeString, + RTLD_NOW | RTLD_LOCAL); + if (engine_lib_handle==NULL) { + LOGI("engine_lib_handle==NULL"); + // TODO report error so the TTS can't be used + } else { + TtsEngine *(*get_TtsEngine)() = + reinterpret_cast<TtsEngine* (*)()>(dlsym(engine_lib_handle, "getTtsEngine")); + pJniStorage->mNativeSynthInterface = (*get_TtsEngine)(); + if (pJniStorage->mNativeSynthInterface) { + pJniStorage->mNativeSynthInterface->init(ttsSynthDoneCB); + } + } + + // we use a weak reference so the SynthProxy object can be garbage collected. + pJniStorage->tts_ref = env->NewGlobalRef(weak_this); + + // save the JNI resources so we can use them (and free them) later + env->SetIntField(thiz, javaTTSFields.synthProxyFieldJniData, + (int)pJniStorage); + + env->ReleaseStringUTFChars(nativeSoLib, nativeSoLibNativeString); +} + + +static void +android_tts_SynthProxy_native_finalize(JNIEnv *env, jobject thiz, jint jniData) +{ + if (jniData) { + SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; + delete pSynthData; + } +} + + +static void +android_tts_SynthProxy_setLanguage(JNIEnv *env, jobject thiz, jint jniData, + jstring language) +{ + if (jniData == 0) { + LOGE("android_tts_SynthProxy_setLanguage(): invalid JNI data"); + return; + } + + SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; + const char *langNativeString = env->GetStringUTFChars(language, 0); + // TODO check return codes + if (pSynthData->mNativeSynthInterface) { + pSynthData->mNativeSynthInterface->setLanguage(langNativeString, + strlen(langNativeString)); + } + env->ReleaseStringUTFChars(language, langNativeString); +} + + +static void +android_tts_SynthProxy_setSpeechRate(JNIEnv *env, jobject thiz, jint jniData, + int speechRate) +{ + if (jniData == 0) { + LOGE("android_tts_SynthProxy_setSpeechRate(): invalid JNI data"); + return; + } + + int bufSize = 10; + char buffer [bufSize]; + sprintf(buffer, "%d", speechRate); + + SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; + LOGI("setting speech rate to %d", speechRate); + // TODO check return codes + if (pSynthData->mNativeSynthInterface) { + pSynthData->mNativeSynthInterface->setProperty("rate", buffer, bufSize); + } +} + + +// TODO: Refactor this to get rid of any assumptions about sample rate, etc. +static void +android_tts_SynthProxy_synthesizeToFile(JNIEnv *env, jobject thiz, jint jniData, + jstring textJavaString, jstring filenameJavaString) +{ + if (jniData == 0) { + LOGE("android_tts_SynthProxy_synthesizeToFile(): invalid JNI data"); + return; + } + + SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; + + const char *filenameNativeString = + env->GetStringUTFChars(filenameJavaString, 0); + const char *textNativeString = env->GetStringUTFChars(textJavaString, 0); + + afterSynthData_t* pForAfter = new (afterSynthData_t); + pForAfter->jniStorage = jniData; + pForAfter->usageMode = USAGEMODE_WRITE_TO_FILE; + + pForAfter->outputFile = fopen(filenameNativeString, "wb"); + + // Write 44 blank bytes for WAV header, then come back and fill them in + // after we've written the audio data + char header[44]; + fwrite(header, 1, 44, pForAfter->outputFile); + + unsigned int unique_identifier; + + // TODO check return codes + if (pSynthData->mNativeSynthInterface) { + pSynthData->mNativeSynthInterface->synthesizeText(textNativeString, + (void *)pForAfter); + } + + long filelen = ftell(pForAfter->outputFile); + + int samples = (((int)filelen) - 44) / 2; + header[0] = 'R'; + header[1] = 'I'; + header[2] = 'F'; + header[3] = 'F'; + ((uint32_t *)(&header[4]))[0] = filelen - 8; + header[8] = 'W'; + header[9] = 'A'; + header[10] = 'V'; + header[11] = 'E'; + + header[12] = 'f'; + header[13] = 'm'; + header[14] = 't'; + header[15] = ' '; + + ((uint32_t *)(&header[16]))[0] = 16; // size of fmt + + ((unsigned short *)(&header[20]))[0] = 1; // format + ((unsigned short *)(&header[22]))[0] = 1; // channels + ((uint32_t *)(&header[24]))[0] = 22050; // samplerate + ((uint32_t *)(&header[28]))[0] = 44100; // byterate + ((unsigned short *)(&header[32]))[0] = 2; // block align + ((unsigned short *)(&header[34]))[0] = 16; // bits per sample + + header[36] = 'd'; + header[37] = 'a'; + header[38] = 't'; + header[39] = 'a'; + + ((uint32_t *)(&header[40]))[0] = samples * 2; // size of data + + // Skip back to the beginning and rewrite the header + fseek(pForAfter->outputFile, 0, SEEK_SET); + fwrite(header, 1, 44, pForAfter->outputFile); + + fflush(pForAfter->outputFile); + fclose(pForAfter->outputFile); + + env->ReleaseStringUTFChars(textJavaString, textNativeString); + env->ReleaseStringUTFChars(filenameJavaString, filenameNativeString); +} + + +static void +android_tts_SynthProxy_speak(JNIEnv *env, jobject thiz, jint jniData, + jstring textJavaString) +{ + if (jniData == 0) { + LOGE("android_tts_SynthProxy_speak(): invalid JNI data"); + return; + } + + SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; + + if (pSynthData->mAudioOut) { + pSynthData->mAudioOut->stop(); + pSynthData->mAudioOut->start(); + } + + afterSynthData_t* pForAfter = new (afterSynthData_t); + pForAfter->jniStorage = jniData; + pForAfter->usageMode = USAGEMODE_PLAY_IMMEDIATELY; + + if (pSynthData->mNativeSynthInterface) { + const char *textNativeString = env->GetStringUTFChars(textJavaString, 0); + pSynthData->mNativeSynthInterface->synthesizeText(textNativeString, + (void *)pForAfter); + env->ReleaseStringUTFChars(textJavaString, textNativeString); + } +} + + +static void +android_tts_SynthProxy_stop(JNIEnv *env, jobject thiz, jint jniData) +{ + if (jniData == 0) { + LOGE("android_tts_SynthProxy_stop(): invalid JNI data"); + return; + } + + SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; + + if (pSynthData->mNativeSynthInterface) { + pSynthData->mNativeSynthInterface->stop(); + } + if (pSynthData->mAudioOut) { + pSynthData->mAudioOut->stop(); + } +} + + +static void +android_tts_SynthProxy_shutdown(JNIEnv *env, jobject thiz, jint jniData) +{ + if (jniData == 0) { + LOGE("android_tts_SynthProxy_shutdown(): invalid JNI data"); + return; + } + + SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; + if (pSynthData->mNativeSynthInterface) { + pSynthData->mNativeSynthInterface->shutdown(); + pSynthData->mNativeSynthInterface = NULL; + } +} + + +// TODO add buffer format +static void +android_tts_SynthProxy_playAudioBuffer(JNIEnv *env, jobject thiz, jint jniData, + int bufferPointer, int bufferSize) +{ + if (jniData == 0) { + LOGE("android_tts_SynthProxy_playAudioBuffer(): invalid JNI data"); + return; + } + + SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; + short* wav = (short*) bufferPointer; + pSynthData->mAudioOut->write(wav, bufferSize); + LOGI("AudioTrack wrote: %d bytes", bufferSize); +} + + +JNIEXPORT jstring JNICALL +android_tts_SynthProxy_getLanguage(JNIEnv *env, jobject thiz, jint jniData) +{ + if (jniData == 0) { + LOGE("android_tts_SynthProxy_getLanguage(): invalid JNI data"); + return env->NewStringUTF(""); + } + + SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; + size_t bufSize = 100; + char buf[bufSize]; + memset(buf, 0, bufSize); + // TODO check return codes + if (pSynthData->mNativeSynthInterface) { + pSynthData->mNativeSynthInterface->getLanguage(buf, &bufSize); + } + return env->NewStringUTF(buf); +} + +JNIEXPORT int JNICALL +android_tts_SynthProxy_getRate(JNIEnv *env, jobject thiz, jint jniData) +{ + if (jniData == 0) { + LOGE("android_tts_SynthProxy_getRate(): invalid JNI data"); + return 0; + } + + SynthProxyJniStorage* pSynthData = (SynthProxyJniStorage*)jniData; + size_t bufSize = 100; + + char buf[bufSize]; + memset(buf, 0, bufSize); + // TODO check return codes + if (pSynthData->mNativeSynthInterface) { + pSynthData->mNativeSynthInterface->getProperty("rate", buf, &bufSize); + } + return atoi(buf); +} + +// Dalvik VM type signatures +static JNINativeMethod gMethods[] = { + { "native_stop", + "(I)V", + (void*)android_tts_SynthProxy_stop + }, + { "native_speak", + "(ILjava/lang/String;)V", + (void*)android_tts_SynthProxy_speak + }, + { "native_synthesizeToFile", + "(ILjava/lang/String;Ljava/lang/String;)V", + (void*)android_tts_SynthProxy_synthesizeToFile + }, + { "native_setLanguage", + "(ILjava/lang/String;)V", + (void*)android_tts_SynthProxy_setLanguage + }, + { "native_setSpeechRate", + "(II)V", + (void*)android_tts_SynthProxy_setSpeechRate + }, + { "native_playAudioBuffer", + "(III)V", + (void*)android_tts_SynthProxy_playAudioBuffer + }, + { "native_getLanguage", + "(I)Ljava/lang/String;", + (void*)android_tts_SynthProxy_getLanguage + }, + { "native_getRate", + "(I)I", + (void*)android_tts_SynthProxy_getRate + }, + { "native_shutdown", + "(I)V", + (void*)android_tts_SynthProxy_shutdown + }, + { "native_setup", + "(Ljava/lang/Object;Ljava/lang/String;)V", + (void*)android_tts_SynthProxy_native_setup + }, + { "native_finalize", + "(I)V", + (void*)android_tts_SynthProxy_native_finalize + } +}; + +#define SP_JNIDATA_FIELD_NAME "mJniData" +#define SP_POSTSPEECHSYNTHESIZED_METHOD_NAME "postNativeSpeechSynthesizedInJava" + +// TODO: verify this is the correct path +static const char* const kClassPathName = "android/tts/SynthProxy"; + +jint JNI_OnLoad(JavaVM* vm, void* reserved) +{ + JNIEnv* env = NULL; + jint result = -1; + jclass clazz; + + if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { + LOGE("ERROR: GetEnv failed\n"); + goto bail; + } + assert(env != NULL); + + clazz = env->FindClass(kClassPathName); + if (clazz == NULL) { + LOGE("Can't find %s", kClassPathName); + goto bail; + } + + javaTTSFields.synthProxyClass = clazz; + javaTTSFields.synthProxyFieldJniData = NULL; + javaTTSFields.synthProxyMethodPost = NULL; + + javaTTSFields.synthProxyFieldJniData = env->GetFieldID(clazz, + SP_JNIDATA_FIELD_NAME, "I"); + if (javaTTSFields.synthProxyFieldJniData == NULL) { + LOGE("Can't find %s.%s field", kClassPathName, SP_JNIDATA_FIELD_NAME); + goto bail; + } + + javaTTSFields.synthProxyMethodPost = env->GetStaticMethodID(clazz, + SP_POSTSPEECHSYNTHESIZED_METHOD_NAME, "(Ljava/lang/Object;II)V"); + if (javaTTSFields.synthProxyMethodPost == NULL) { + LOGE("Can't find %s.%s method", kClassPathName, SP_POSTSPEECHSYNTHESIZED_METHOD_NAME); + goto bail; + } + + if (jniRegisterNativeMethods( + env, kClassPathName, gMethods, NELEM(gMethods)) < 0) + goto bail; + + /* success -- return valid version number */ + result = JNI_VERSION_1_4; + + bail: + return result; +} |