diff options
Diffstat (limited to 'libutils')
40 files changed, 10207 insertions, 0 deletions
diff --git a/libutils/Android.mk b/libutils/Android.mk new file mode 100644 index 0000000..7e6b1be --- /dev/null +++ b/libutils/Android.mk @@ -0,0 +1,144 @@ +# 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. + +LOCAL_PATH:= $(call my-dir) + +# libutils is a little unique: It's built twice, once for the host +# and once for the device. + +commonSources:= \ + BasicHashtable.cpp \ + BlobCache.cpp \ + CallStack.cpp \ + FileMap.cpp \ + JenkinsHash.cpp \ + LinearAllocator.cpp \ + LinearTransform.cpp \ + Log.cpp \ + PropertyMap.cpp \ + RefBase.cpp \ + SharedBuffer.cpp \ + Static.cpp \ + StopWatch.cpp \ + String8.cpp \ + String16.cpp \ + SystemClock.cpp \ + Threads.cpp \ + Timers.cpp \ + Tokenizer.cpp \ + Unicode.cpp \ + VectorImpl.cpp \ + misc.cpp + +host_commonCflags := -DLIBUTILS_NATIVE=1 $(TOOL_CFLAGS) + +ifeq ($(HOST_OS),windows) +ifeq ($(strip $(USE_CYGWIN),),) +# Under MinGW, ctype.h doesn't need multi-byte support +host_commonCflags += -DMB_CUR_MAX=1 +endif +endif + +host_commonLdlibs := + +ifeq ($(TARGET_OS),linux) +host_commonLdlibs += -lrt -ldl +endif + + +# For the host +# ===================================================== +include $(CLEAR_VARS) +LOCAL_SRC_FILES:= $(commonSources) +ifeq ($(HOST_OS), linux) +LOCAL_SRC_FILES += Looper.cpp +endif +LOCAL_MODULE:= libutils +LOCAL_STATIC_LIBRARIES := liblog +LOCAL_CFLAGS += $(host_commonCflags) +LOCAL_LDLIBS += $(host_commonLdlibs) +include $(BUILD_HOST_STATIC_LIBRARY) + + +# For the host, 64-bit +# ===================================================== +include $(CLEAR_VARS) +LOCAL_SRC_FILES:= $(commonSources) +ifeq ($(HOST_OS), linux) +LOCAL_SRC_FILES += Looper.cpp +endif +LOCAL_MODULE:= lib64utils +LOCAL_STATIC_LIBRARIES := liblog +LOCAL_CFLAGS += $(host_commonCflags) -m64 +LOCAL_LDLIBS += $(host_commonLdlibs) +include $(BUILD_HOST_STATIC_LIBRARY) + + +# For the device, static +# ===================================================== +include $(CLEAR_VARS) + + +# we have the common sources, plus some device-specific stuff +LOCAL_SRC_FILES:= \ + $(commonSources) \ + Looper.cpp \ + Trace.cpp + +ifeq ($(TARGET_OS),linux) +LOCAL_LDLIBS += -lrt -ldl +endif + +ifeq ($(TARGET_ARCH),mips) +LOCAL_CFLAGS += -DALIGN_DOUBLE +endif + +LOCAL_C_INCLUDES += \ + bionic/libc/private \ + external/zlib + +LOCAL_LDLIBS += -lpthread + +LOCAL_STATIC_LIBRARIES := \ + libcutils + +LOCAL_SHARED_LIBRARIES := \ + libcorkscrew \ + liblog \ + libdl + +LOCAL_MODULE:= libutils +include $(BUILD_STATIC_LIBRARY) + +# For the device, shared +# ===================================================== +include $(CLEAR_VARS) +LOCAL_MODULE:= libutils +LOCAL_WHOLE_STATIC_LIBRARIES := libutils +LOCAL_SHARED_LIBRARIES := \ + liblog \ + libcutils \ + libdl \ + libcorkscrew + +include $(BUILD_SHARED_LIBRARY) + +# Include subdirectory makefiles +# ============================================================ + +# If we're building with ONE_SHOT_MAKEFILE (mm, mmm), then what the framework +# team really wants is to build the stuff defined by this makefile. +ifeq (,$(ONE_SHOT_MAKEFILE)) +include $(call first-makefiles-under,$(LOCAL_PATH)) +endif diff --git a/libutils/BasicHashtable.cpp b/libutils/BasicHashtable.cpp new file mode 100644 index 0000000..491d9e9 --- /dev/null +++ b/libutils/BasicHashtable.cpp @@ -0,0 +1,342 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "BasicHashtable" + +#include <math.h> + +#include <utils/Log.h> +#include <utils/BasicHashtable.h> +#include <utils/misc.h> + +namespace android { + +BasicHashtableImpl::BasicHashtableImpl(size_t entrySize, bool hasTrivialDestructor, + size_t minimumInitialCapacity, float loadFactor) : + mBucketSize(entrySize + sizeof(Bucket)), mHasTrivialDestructor(hasTrivialDestructor), + mLoadFactor(loadFactor), mSize(0), + mFilledBuckets(0), mBuckets(NULL) { + determineCapacity(minimumInitialCapacity, mLoadFactor, &mBucketCount, &mCapacity); +} + +BasicHashtableImpl::BasicHashtableImpl(const BasicHashtableImpl& other) : + mBucketSize(other.mBucketSize), mHasTrivialDestructor(other.mHasTrivialDestructor), + mCapacity(other.mCapacity), mLoadFactor(other.mLoadFactor), + mSize(other.mSize), mFilledBuckets(other.mFilledBuckets), + mBucketCount(other.mBucketCount), mBuckets(other.mBuckets) { + if (mBuckets) { + SharedBuffer::bufferFromData(mBuckets)->acquire(); + } +} + +BasicHashtableImpl::~BasicHashtableImpl() +{ +} + +void BasicHashtableImpl::dispose() { + if (mBuckets) { + releaseBuckets(mBuckets, mBucketCount); + } +} + +void BasicHashtableImpl::clone() { + if (mBuckets) { + void* newBuckets = allocateBuckets(mBucketCount); + copyBuckets(mBuckets, newBuckets, mBucketCount); + releaseBuckets(mBuckets, mBucketCount); + mBuckets = newBuckets; + } +} + +void BasicHashtableImpl::setTo(const BasicHashtableImpl& other) { + if (mBuckets) { + releaseBuckets(mBuckets, mBucketCount); + } + + mCapacity = other.mCapacity; + mLoadFactor = other.mLoadFactor; + mSize = other.mSize; + mFilledBuckets = other.mFilledBuckets; + mBucketCount = other.mBucketCount; + mBuckets = other.mBuckets; + + if (mBuckets) { + SharedBuffer::bufferFromData(mBuckets)->acquire(); + } +} + +void BasicHashtableImpl::clear() { + if (mBuckets) { + if (mFilledBuckets) { + SharedBuffer* sb = SharedBuffer::bufferFromData(mBuckets); + if (sb->onlyOwner()) { + destroyBuckets(mBuckets, mBucketCount); + for (size_t i = 0; i < mBucketCount; i++) { + Bucket& bucket = bucketAt(mBuckets, i); + bucket.cookie = 0; + } + } else { + releaseBuckets(mBuckets, mBucketCount); + mBuckets = NULL; + } + mFilledBuckets = 0; + } + mSize = 0; + } +} + +ssize_t BasicHashtableImpl::next(ssize_t index) const { + if (mSize) { + while (size_t(++index) < mBucketCount) { + const Bucket& bucket = bucketAt(mBuckets, index); + if (bucket.cookie & Bucket::PRESENT) { + return index; + } + } + } + return -1; +} + +ssize_t BasicHashtableImpl::find(ssize_t index, hash_t hash, + const void* __restrict__ key) const { + if (!mSize) { + return -1; + } + + hash = trimHash(hash); + if (index < 0) { + index = chainStart(hash, mBucketCount); + + const Bucket& bucket = bucketAt(mBuckets, size_t(index)); + if (bucket.cookie & Bucket::PRESENT) { + if (compareBucketKey(bucket, key)) { + return index; + } + } else { + if (!(bucket.cookie & Bucket::COLLISION)) { + return -1; + } + } + } + + size_t inc = chainIncrement(hash, mBucketCount); + for (;;) { + index = chainSeek(index, inc, mBucketCount); + + const Bucket& bucket = bucketAt(mBuckets, size_t(index)); + if (bucket.cookie & Bucket::PRESENT) { + if ((bucket.cookie & Bucket::HASH_MASK) == hash + && compareBucketKey(bucket, key)) { + return index; + } + } + if (!(bucket.cookie & Bucket::COLLISION)) { + return -1; + } + } +} + +size_t BasicHashtableImpl::add(hash_t hash, const void* entry) { + if (!mBuckets) { + mBuckets = allocateBuckets(mBucketCount); + } else { + edit(); + } + + hash = trimHash(hash); + for (;;) { + size_t index = chainStart(hash, mBucketCount); + Bucket* bucket = &bucketAt(mBuckets, size_t(index)); + if (bucket->cookie & Bucket::PRESENT) { + size_t inc = chainIncrement(hash, mBucketCount); + do { + bucket->cookie |= Bucket::COLLISION; + index = chainSeek(index, inc, mBucketCount); + bucket = &bucketAt(mBuckets, size_t(index)); + } while (bucket->cookie & Bucket::PRESENT); + } + + uint32_t collision = bucket->cookie & Bucket::COLLISION; + if (!collision) { + if (mFilledBuckets >= mCapacity) { + rehash(mCapacity * 2, mLoadFactor); + continue; + } + mFilledBuckets += 1; + } + + bucket->cookie = collision | Bucket::PRESENT | hash; + mSize += 1; + initializeBucketEntry(*bucket, entry); + return index; + } +} + +void BasicHashtableImpl::removeAt(size_t index) { + edit(); + + Bucket& bucket = bucketAt(mBuckets, index); + bucket.cookie &= ~Bucket::PRESENT; + if (!(bucket.cookie & Bucket::COLLISION)) { + mFilledBuckets -= 1; + } + mSize -= 1; + if (!mHasTrivialDestructor) { + destroyBucketEntry(bucket); + } +} + +void BasicHashtableImpl::rehash(size_t minimumCapacity, float loadFactor) { + if (minimumCapacity < mSize) { + minimumCapacity = mSize; + } + size_t newBucketCount, newCapacity; + determineCapacity(minimumCapacity, loadFactor, &newBucketCount, &newCapacity); + + if (newBucketCount != mBucketCount || newCapacity != mCapacity) { + if (mBuckets) { + void* newBuckets; + if (mSize) { + newBuckets = allocateBuckets(newBucketCount); + for (size_t i = 0; i < mBucketCount; i++) { + const Bucket& fromBucket = bucketAt(mBuckets, i); + if (fromBucket.cookie & Bucket::PRESENT) { + hash_t hash = fromBucket.cookie & Bucket::HASH_MASK; + size_t index = chainStart(hash, newBucketCount); + Bucket* toBucket = &bucketAt(newBuckets, size_t(index)); + if (toBucket->cookie & Bucket::PRESENT) { + size_t inc = chainIncrement(hash, newBucketCount); + do { + toBucket->cookie |= Bucket::COLLISION; + index = chainSeek(index, inc, newBucketCount); + toBucket = &bucketAt(newBuckets, size_t(index)); + } while (toBucket->cookie & Bucket::PRESENT); + } + toBucket->cookie = Bucket::PRESENT | hash; + initializeBucketEntry(*toBucket, fromBucket.entry); + } + } + } else { + newBuckets = NULL; + } + releaseBuckets(mBuckets, mBucketCount); + mBuckets = newBuckets; + mFilledBuckets = mSize; + } + mBucketCount = newBucketCount; + mCapacity = newCapacity; + } + mLoadFactor = loadFactor; +} + +void* BasicHashtableImpl::allocateBuckets(size_t count) const { + size_t bytes = count * mBucketSize; + SharedBuffer* sb = SharedBuffer::alloc(bytes); + LOG_ALWAYS_FATAL_IF(!sb, "Could not allocate %u bytes for hashtable with %u buckets.", + uint32_t(bytes), uint32_t(count)); + void* buckets = sb->data(); + for (size_t i = 0; i < count; i++) { + Bucket& bucket = bucketAt(buckets, i); + bucket.cookie = 0; + } + return buckets; +} + +void BasicHashtableImpl::releaseBuckets(void* __restrict__ buckets, size_t count) const { + SharedBuffer* sb = SharedBuffer::bufferFromData(buckets); + if (sb->release(SharedBuffer::eKeepStorage) == 1) { + destroyBuckets(buckets, count); + SharedBuffer::dealloc(sb); + } +} + +void BasicHashtableImpl::destroyBuckets(void* __restrict__ buckets, size_t count) const { + if (!mHasTrivialDestructor) { + for (size_t i = 0; i < count; i++) { + Bucket& bucket = bucketAt(buckets, i); + if (bucket.cookie & Bucket::PRESENT) { + destroyBucketEntry(bucket); + } + } + } +} + +void BasicHashtableImpl::copyBuckets(const void* __restrict__ fromBuckets, + void* __restrict__ toBuckets, size_t count) const { + for (size_t i = 0; i < count; i++) { + const Bucket& fromBucket = bucketAt(fromBuckets, i); + Bucket& toBucket = bucketAt(toBuckets, i); + toBucket.cookie = fromBucket.cookie; + if (fromBucket.cookie & Bucket::PRESENT) { + initializeBucketEntry(toBucket, fromBucket.entry); + } + } +} + +// Table of 31-bit primes where each prime is no less than twice as large +// as the previous one. Generated by "primes.py". +static size_t PRIMES[] = { + 5, + 11, + 23, + 47, + 97, + 197, + 397, + 797, + 1597, + 3203, + 6421, + 12853, + 25717, + 51437, + 102877, + 205759, + 411527, + 823117, + 1646237, + 3292489, + 6584983, + 13169977, + 26339969, + 52679969, + 105359939, + 210719881, + 421439783, + 842879579, + 1685759167, + 0, +}; + +void BasicHashtableImpl::determineCapacity(size_t minimumCapacity, float loadFactor, + size_t* __restrict__ outBucketCount, size_t* __restrict__ outCapacity) { + LOG_ALWAYS_FATAL_IF(loadFactor <= 0.0f || loadFactor > 1.0f, + "Invalid load factor %0.3f. Must be in the range (0, 1].", loadFactor); + + size_t count = ceilf(minimumCapacity / loadFactor) + 1; + size_t i = 0; + while (count > PRIMES[i] && i < NELEM(PRIMES)) { + i++; + } + count = PRIMES[i]; + LOG_ALWAYS_FATAL_IF(!count, "Could not determine required number of buckets for " + "hashtable with minimum capacity %u and load factor %0.3f.", + uint32_t(minimumCapacity), loadFactor); + *outBucketCount = count; + *outCapacity = ceilf((count - 1) * loadFactor); +} + +}; // namespace android diff --git a/libutils/BlobCache.cpp b/libutils/BlobCache.cpp new file mode 100644 index 0000000..0fb1d8e --- /dev/null +++ b/libutils/BlobCache.cpp @@ -0,0 +1,362 @@ +/* + ** Copyright 2011, The Android Open Source Project + ** + ** Licensed under the Apache License, Version 2.0 (the "License"); + ** you may not use this file except in compliance with the License. + ** You may obtain a copy of the License at + ** + ** http://www.apache.org/licenses/LICENSE-2.0 + ** + ** Unless required by applicable law or agreed to in writing, software + ** distributed under the License is distributed on an "AS IS" BASIS, + ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ** See the License for the specific language governing permissions and + ** limitations under the License. + */ + +#define LOG_TAG "BlobCache" +//#define LOG_NDEBUG 0 + +#include <stdlib.h> +#include <string.h> + +#include <utils/BlobCache.h> +#include <utils/Errors.h> +#include <utils/Log.h> + +namespace android { + +// BlobCache::Header::mMagicNumber value +static const uint32_t blobCacheMagic = '_Bb$'; + +// BlobCache::Header::mBlobCacheVersion value +static const uint32_t blobCacheVersion = 1; + +// BlobCache::Header::mDeviceVersion value +static const uint32_t blobCacheDeviceVersion = 1; + +BlobCache::BlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxTotalSize): + mMaxKeySize(maxKeySize), + mMaxValueSize(maxValueSize), + mMaxTotalSize(maxTotalSize), + mTotalSize(0) { + nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); +#ifdef _WIN32 + srand(now); +#else + mRandState[0] = (now >> 0) & 0xFFFF; + mRandState[1] = (now >> 16) & 0xFFFF; + mRandState[2] = (now >> 32) & 0xFFFF; +#endif + ALOGV("initializing random seed using %lld", now); +} + +void BlobCache::set(const void* key, size_t keySize, const void* value, + size_t valueSize) { + if (mMaxKeySize < keySize) { + ALOGV("set: not caching because the key is too large: %d (limit: %d)", + keySize, mMaxKeySize); + return; + } + if (mMaxValueSize < valueSize) { + ALOGV("set: not caching because the value is too large: %d (limit: %d)", + valueSize, mMaxValueSize); + return; + } + if (mMaxTotalSize < keySize + valueSize) { + ALOGV("set: not caching because the combined key/value size is too " + "large: %d (limit: %d)", keySize + valueSize, mMaxTotalSize); + return; + } + if (keySize == 0) { + ALOGW("set: not caching because keySize is 0"); + return; + } + if (valueSize <= 0) { + ALOGW("set: not caching because valueSize is 0"); + return; + } + + sp<Blob> dummyKey(new Blob(key, keySize, false)); + CacheEntry dummyEntry(dummyKey, NULL); + + while (true) { + ssize_t index = mCacheEntries.indexOf(dummyEntry); + if (index < 0) { + // Create a new cache entry. + sp<Blob> keyBlob(new Blob(key, keySize, true)); + sp<Blob> valueBlob(new Blob(value, valueSize, true)); + size_t newTotalSize = mTotalSize + keySize + valueSize; + if (mMaxTotalSize < newTotalSize) { + if (isCleanable()) { + // Clean the cache and try again. + clean(); + continue; + } else { + ALOGV("set: not caching new key/value pair because the " + "total cache size limit would be exceeded: %d " + "(limit: %d)", + keySize + valueSize, mMaxTotalSize); + break; + } + } + mCacheEntries.add(CacheEntry(keyBlob, valueBlob)); + mTotalSize = newTotalSize; + ALOGV("set: created new cache entry with %d byte key and %d byte value", + keySize, valueSize); + } else { + // Update the existing cache entry. + sp<Blob> valueBlob(new Blob(value, valueSize, true)); + sp<Blob> oldValueBlob(mCacheEntries[index].getValue()); + size_t newTotalSize = mTotalSize + valueSize - oldValueBlob->getSize(); + if (mMaxTotalSize < newTotalSize) { + if (isCleanable()) { + // Clean the cache and try again. + clean(); + continue; + } else { + ALOGV("set: not caching new value because the total cache " + "size limit would be exceeded: %d (limit: %d)", + keySize + valueSize, mMaxTotalSize); + break; + } + } + mCacheEntries.editItemAt(index).setValue(valueBlob); + mTotalSize = newTotalSize; + ALOGV("set: updated existing cache entry with %d byte key and %d byte " + "value", keySize, valueSize); + } + break; + } +} + +size_t BlobCache::get(const void* key, size_t keySize, void* value, + size_t valueSize) { + if (mMaxKeySize < keySize) { + ALOGV("get: not searching because the key is too large: %d (limit %d)", + keySize, mMaxKeySize); + return 0; + } + sp<Blob> dummyKey(new Blob(key, keySize, false)); + CacheEntry dummyEntry(dummyKey, NULL); + ssize_t index = mCacheEntries.indexOf(dummyEntry); + if (index < 0) { + ALOGV("get: no cache entry found for key of size %d", keySize); + return 0; + } + + // The key was found. Return the value if the caller's buffer is large + // enough. + sp<Blob> valueBlob(mCacheEntries[index].getValue()); + size_t valueBlobSize = valueBlob->getSize(); + if (valueBlobSize <= valueSize) { + ALOGV("get: copying %d bytes to caller's buffer", valueBlobSize); + memcpy(value, valueBlob->getData(), valueBlobSize); + } else { + ALOGV("get: caller's buffer is too small for value: %d (needs %d)", + valueSize, valueBlobSize); + } + return valueBlobSize; +} + +static inline size_t align4(size_t size) { + return (size + 3) & ~3; +} + +size_t BlobCache::getFlattenedSize() const { + size_t size = sizeof(Header); + for (size_t i = 0; i < mCacheEntries.size(); i++) { + const CacheEntry& e(mCacheEntries[i]); + sp<Blob> keyBlob = e.getKey(); + sp<Blob> valueBlob = e.getValue(); + size = align4(size); + size += sizeof(EntryHeader) + keyBlob->getSize() + + valueBlob->getSize(); + } + return size; +} + +status_t BlobCache::flatten(void* buffer, size_t size) const { + // Write the cache header + if (size < sizeof(Header)) { + ALOGE("flatten: not enough room for cache header"); + return BAD_VALUE; + } + Header* header = reinterpret_cast<Header*>(buffer); + header->mMagicNumber = blobCacheMagic; + header->mBlobCacheVersion = blobCacheVersion; + header->mDeviceVersion = blobCacheDeviceVersion; + header->mNumEntries = mCacheEntries.size(); + + // Write cache entries + uint8_t* byteBuffer = reinterpret_cast<uint8_t*>(buffer); + off_t byteOffset = align4(sizeof(Header)); + for (size_t i = 0; i < mCacheEntries.size(); i++) { + const CacheEntry& e(mCacheEntries[i]); + sp<Blob> keyBlob = e.getKey(); + sp<Blob> valueBlob = e.getValue(); + size_t keySize = keyBlob->getSize(); + size_t valueSize = valueBlob->getSize(); + + size_t entrySize = sizeof(EntryHeader) + keySize + valueSize; + if (byteOffset + entrySize > size) { + ALOGE("flatten: not enough room for cache entries"); + return BAD_VALUE; + } + + EntryHeader* eheader = reinterpret_cast<EntryHeader*>( + &byteBuffer[byteOffset]); + eheader->mKeySize = keySize; + eheader->mValueSize = valueSize; + + memcpy(eheader->mData, keyBlob->getData(), keySize); + memcpy(eheader->mData + keySize, valueBlob->getData(), valueSize); + + byteOffset += align4(entrySize); + } + + return OK; +} + +status_t BlobCache::unflatten(void const* buffer, size_t size) { + // All errors should result in the BlobCache being in an empty state. + mCacheEntries.clear(); + + // Read the cache header + if (size < sizeof(Header)) { + ALOGE("unflatten: not enough room for cache header"); + return BAD_VALUE; + } + const Header* header = reinterpret_cast<const Header*>(buffer); + if (header->mMagicNumber != blobCacheMagic) { + ALOGE("unflatten: bad magic number: %d", header->mMagicNumber); + return BAD_VALUE; + } + if (header->mBlobCacheVersion != blobCacheVersion || + header->mDeviceVersion != blobCacheDeviceVersion) { + // We treat version mismatches as an empty cache. + return OK; + } + + // Read cache entries + const uint8_t* byteBuffer = reinterpret_cast<const uint8_t*>(buffer); + off_t byteOffset = align4(sizeof(Header)); + size_t numEntries = header->mNumEntries; + for (size_t i = 0; i < numEntries; i++) { + if (byteOffset + sizeof(EntryHeader) > size) { + mCacheEntries.clear(); + ALOGE("unflatten: not enough room for cache entry headers"); + return BAD_VALUE; + } + + const EntryHeader* eheader = reinterpret_cast<const EntryHeader*>( + &byteBuffer[byteOffset]); + size_t keySize = eheader->mKeySize; + size_t valueSize = eheader->mValueSize; + size_t entrySize = sizeof(EntryHeader) + keySize + valueSize; + + if (byteOffset + entrySize > size) { + mCacheEntries.clear(); + ALOGE("unflatten: not enough room for cache entry headers"); + return BAD_VALUE; + } + + const uint8_t* data = eheader->mData; + set(data, keySize, data + keySize, valueSize); + + byteOffset += align4(entrySize); + } + + return OK; +} + +long int BlobCache::blob_random() { +#ifdef _WIN32 + return rand(); +#else + return nrand48(mRandState); +#endif +} + +void BlobCache::clean() { + // Remove a random cache entry until the total cache size gets below half + // the maximum total cache size. + while (mTotalSize > mMaxTotalSize / 2) { + size_t i = size_t(blob_random() % (mCacheEntries.size())); + const CacheEntry& entry(mCacheEntries[i]); + mTotalSize -= entry.getKey()->getSize() + entry.getValue()->getSize(); + mCacheEntries.removeAt(i); + } +} + +bool BlobCache::isCleanable() const { + return mTotalSize > mMaxTotalSize / 2; +} + +BlobCache::Blob::Blob(const void* data, size_t size, bool copyData): + mData(copyData ? malloc(size) : data), + mSize(size), + mOwnsData(copyData) { + if (data != NULL && copyData) { + memcpy(const_cast<void*>(mData), data, size); + } +} + +BlobCache::Blob::~Blob() { + if (mOwnsData) { + free(const_cast<void*>(mData)); + } +} + +bool BlobCache::Blob::operator<(const Blob& rhs) const { + if (mSize == rhs.mSize) { + return memcmp(mData, rhs.mData, mSize) < 0; + } else { + return mSize < rhs.mSize; + } +} + +const void* BlobCache::Blob::getData() const { + return mData; +} + +size_t BlobCache::Blob::getSize() const { + return mSize; +} + +BlobCache::CacheEntry::CacheEntry() { +} + +BlobCache::CacheEntry::CacheEntry(const sp<Blob>& key, const sp<Blob>& value): + mKey(key), + mValue(value) { +} + +BlobCache::CacheEntry::CacheEntry(const CacheEntry& ce): + mKey(ce.mKey), + mValue(ce.mValue) { +} + +bool BlobCache::CacheEntry::operator<(const CacheEntry& rhs) const { + return *mKey < *rhs.mKey; +} + +const BlobCache::CacheEntry& BlobCache::CacheEntry::operator=(const CacheEntry& rhs) { + mKey = rhs.mKey; + mValue = rhs.mValue; + return *this; +} + +sp<BlobCache::Blob> BlobCache::CacheEntry::getKey() const { + return mKey; +} + +sp<BlobCache::Blob> BlobCache::CacheEntry::getValue() const { + return mValue; +} + +void BlobCache::CacheEntry::setValue(const sp<Blob>& value) { + mValue = value; +} + +} // namespace android diff --git a/libutils/CallStack.cpp b/libutils/CallStack.cpp new file mode 100644 index 0000000..e60f5d8 --- /dev/null +++ b/libutils/CallStack.cpp @@ -0,0 +1,138 @@ +/* + * 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. + */ + +#define LOG_TAG "CallStack" + +#include <string.h> + +#include <utils/Log.h> +#include <utils/Errors.h> +#include <utils/CallStack.h> +#include <corkscrew/backtrace.h> + +/*****************************************************************************/ +namespace android { + +CallStack::CallStack() : + mCount(0) { +} + +CallStack::CallStack(const char* logtag, int32_t ignoreDepth, int32_t maxDepth) { + this->update(ignoreDepth+1, maxDepth); + this->dump(logtag); +} + +CallStack::CallStack(const CallStack& rhs) : + mCount(rhs.mCount) { + if (mCount) { + memcpy(mStack, rhs.mStack, mCount * sizeof(backtrace_frame_t)); + } +} + +CallStack::~CallStack() { +} + +CallStack& CallStack::operator = (const CallStack& rhs) { + mCount = rhs.mCount; + if (mCount) { + memcpy(mStack, rhs.mStack, mCount * sizeof(backtrace_frame_t)); + } + return *this; +} + +bool CallStack::operator == (const CallStack& rhs) const { + if (mCount != rhs.mCount) + return false; + return !mCount || memcmp(mStack, rhs.mStack, mCount * sizeof(backtrace_frame_t)) == 0; +} + +bool CallStack::operator != (const CallStack& rhs) const { + return !operator == (rhs); +} + +bool CallStack::operator < (const CallStack& rhs) const { + if (mCount != rhs.mCount) + return mCount < rhs.mCount; + return memcmp(mStack, rhs.mStack, mCount * sizeof(backtrace_frame_t)) < 0; +} + +bool CallStack::operator >= (const CallStack& rhs) const { + return !operator < (rhs); +} + +bool CallStack::operator > (const CallStack& rhs) const { + if (mCount != rhs.mCount) + return mCount > rhs.mCount; + return memcmp(mStack, rhs.mStack, mCount * sizeof(backtrace_frame_t)) > 0; +} + +bool CallStack::operator <= (const CallStack& rhs) const { + return !operator > (rhs); +} + +const void* CallStack::operator [] (int index) const { + if (index >= int(mCount)) + return 0; + return reinterpret_cast<const void*>(mStack[index].absolute_pc); +} + +void CallStack::clear() { + mCount = 0; +} + +void CallStack::update(int32_t ignoreDepth, int32_t maxDepth) { + if (maxDepth > MAX_DEPTH) { + maxDepth = MAX_DEPTH; + } + ssize_t count = unwind_backtrace(mStack, ignoreDepth + 1, maxDepth); + mCount = count > 0 ? count : 0; +} + +void CallStack::dump(const char* logtag, const char* prefix) const { + backtrace_symbol_t symbols[mCount]; + + get_backtrace_symbols(mStack, mCount, symbols); + for (size_t i = 0; i < mCount; i++) { + char line[MAX_BACKTRACE_LINE_LENGTH]; + format_backtrace_line(i, &mStack[i], &symbols[i], + line, MAX_BACKTRACE_LINE_LENGTH); + ALOG(LOG_DEBUG, logtag, "%s%s", + prefix ? prefix : "", + line); + } + free_backtrace_symbols(symbols, mCount); +} + +String8 CallStack::toString(const char* prefix) const { + String8 str; + backtrace_symbol_t symbols[mCount]; + + get_backtrace_symbols(mStack, mCount, symbols); + for (size_t i = 0; i < mCount; i++) { + char line[MAX_BACKTRACE_LINE_LENGTH]; + format_backtrace_line(i, &mStack[i], &symbols[i], + line, MAX_BACKTRACE_LINE_LENGTH); + if (prefix) { + str.append(prefix); + } + str.append(line); + str.append("\n"); + } + free_backtrace_symbols(symbols, mCount); + return str; +} + +}; // namespace android diff --git a/libutils/CleanSpec.mk b/libutils/CleanSpec.mk new file mode 100644 index 0000000..c3c5651 --- /dev/null +++ b/libutils/CleanSpec.mk @@ -0,0 +1,51 @@ +# Copyright (C) 2012 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. +# + +# If you don't need to do a full clean build but would like to touch +# a file or delete some intermediate files, add a clean step to the end +# of the list. These steps will only be run once, if they haven't been +# run before. +# +# E.g.: +# $(call add-clean-step, touch -c external/sqlite/sqlite3.h) +# $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates) +# +# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with +# files that are missing or have been moved. +# +# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory. +# Use $(OUT_DIR) to refer to the "out" directory. +# +# If you need to re-do something that's already mentioned, just copy +# the command and add it to the bottom of the list. E.g., if a change +# that you made last week required touching a file and a change you +# made today requires touching the same file, just copy the old +# touch step and add it to the end of the list. +# +# ************************************************ +# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST +# ************************************************ + +# For example: +#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/AndroidTests_intermediates) +#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates) +#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f) +#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*) + +# ************************************************ +# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST +# ************************************************ +$(call add-clean-step, rm -rf $(HOST_OUT)/obj/STATIC_LIBRARIES/libutils_intermediates/import_includes) +$(call add-clean-step, rm -rf $(HOST_OUT)/obj/STATIC_LIBRARIES/lib64utils_intermediates/import_includes) diff --git a/libutils/FileMap.cpp b/libutils/FileMap.cpp new file mode 100644 index 0000000..9ce370e --- /dev/null +++ b/libutils/FileMap.cpp @@ -0,0 +1,225 @@ +/* + * 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. + */ + +// +// Shared file mapping class. +// + +#define LOG_TAG "filemap" + +#include <utils/FileMap.h> +#include <utils/Log.h> + +#include <stdio.h> +#include <stdlib.h> + +#ifdef HAVE_POSIX_FILEMAP +#include <sys/mman.h> +#endif + +#include <string.h> +#include <memory.h> +#include <errno.h> +#include <assert.h> + +using namespace android; + +/*static*/ long FileMap::mPageSize = -1; + + +/* + * Constructor. Create an empty object. + */ +FileMap::FileMap(void) + : mRefCount(1), mFileName(NULL), mBasePtr(NULL), mBaseLength(0), + mDataPtr(NULL), mDataLength(0) +{ +} + +/* + * Destructor. + */ +FileMap::~FileMap(void) +{ + assert(mRefCount == 0); + + //printf("+++ removing FileMap %p %u\n", mDataPtr, mDataLength); + + mRefCount = -100; // help catch double-free + if (mFileName != NULL) { + free(mFileName); + } +#ifdef HAVE_POSIX_FILEMAP + if (mBasePtr && munmap(mBasePtr, mBaseLength) != 0) { + ALOGD("munmap(%p, %d) failed\n", mBasePtr, (int) mBaseLength); + } +#endif +#ifdef HAVE_WIN32_FILEMAP + if (mBasePtr && UnmapViewOfFile(mBasePtr) == 0) { + ALOGD("UnmapViewOfFile(%p) failed, error = %ld\n", mBasePtr, + GetLastError() ); + } + if (mFileMapping != INVALID_HANDLE_VALUE) { + CloseHandle(mFileMapping); + } + CloseHandle(mFileHandle); +#endif +} + + +/* + * Create a new mapping on an open file. + * + * Closing the file descriptor does not unmap the pages, so we don't + * claim ownership of the fd. + * + * Returns "false" on failure. + */ +bool FileMap::create(const char* origFileName, int fd, off64_t offset, size_t length, + bool readOnly) +{ +#ifdef HAVE_WIN32_FILEMAP + int adjust; + off64_t adjOffset; + size_t adjLength; + + if (mPageSize == -1) { + SYSTEM_INFO si; + + GetSystemInfo( &si ); + mPageSize = si.dwAllocationGranularity; + } + + DWORD protect = readOnly ? PAGE_READONLY : PAGE_READWRITE; + + mFileHandle = (HANDLE) _get_osfhandle(fd); + mFileMapping = CreateFileMapping( mFileHandle, NULL, protect, 0, 0, NULL); + if (mFileMapping == NULL) { + ALOGE("CreateFileMapping(%p, %lx) failed with error %ld\n", + mFileHandle, protect, GetLastError() ); + return false; + } + + adjust = offset % mPageSize; + adjOffset = offset - adjust; + adjLength = length + adjust; + + mBasePtr = MapViewOfFile( mFileMapping, + readOnly ? FILE_MAP_READ : FILE_MAP_ALL_ACCESS, + 0, + (DWORD)(adjOffset), + adjLength ); + if (mBasePtr == NULL) { + ALOGE("MapViewOfFile(%ld, %ld) failed with error %ld\n", + adjOffset, adjLength, GetLastError() ); + CloseHandle(mFileMapping); + mFileMapping = INVALID_HANDLE_VALUE; + return false; + } +#endif +#ifdef HAVE_POSIX_FILEMAP + int prot, flags, adjust; + off64_t adjOffset; + size_t adjLength; + + void* ptr; + + assert(mRefCount == 1); + assert(fd >= 0); + assert(offset >= 0); + assert(length > 0); + + /* init on first use */ + if (mPageSize == -1) { +#if NOT_USING_KLIBC + mPageSize = sysconf(_SC_PAGESIZE); + if (mPageSize == -1) { + ALOGE("could not get _SC_PAGESIZE\n"); + return false; + } +#else + /* this holds for Linux, Darwin, Cygwin, and doesn't pain the ARM */ + mPageSize = 4096; +#endif + } + + adjust = offset % mPageSize; +try_again: + adjOffset = offset - adjust; + adjLength = length + adjust; + + flags = MAP_SHARED; + prot = PROT_READ; + if (!readOnly) + prot |= PROT_WRITE; + + ptr = mmap(NULL, adjLength, prot, flags, fd, adjOffset); + if (ptr == MAP_FAILED) { + // Cygwin does not seem to like file mapping files from an offset. + // So if we fail, try again with offset zero + if (adjOffset > 0) { + adjust = offset; + goto try_again; + } + + ALOGE("mmap(%ld,%ld) failed: %s\n", + (long) adjOffset, (long) adjLength, strerror(errno)); + return false; + } + mBasePtr = ptr; +#endif /* HAVE_POSIX_FILEMAP */ + + mFileName = origFileName != NULL ? strdup(origFileName) : NULL; + mBaseLength = adjLength; + mDataOffset = offset; + mDataPtr = (char*) mBasePtr + adjust; + mDataLength = length; + + assert(mBasePtr != NULL); + + ALOGV("MAP: base %p/%d data %p/%d\n", + mBasePtr, (int) mBaseLength, mDataPtr, (int) mDataLength); + + return true; +} + +/* + * Provide guidance to the system. + */ +int FileMap::advise(MapAdvice advice) +{ +#if HAVE_MADVISE + int cc, sysAdvice; + + switch (advice) { + case NORMAL: sysAdvice = MADV_NORMAL; break; + case RANDOM: sysAdvice = MADV_RANDOM; break; + case SEQUENTIAL: sysAdvice = MADV_SEQUENTIAL; break; + case WILLNEED: sysAdvice = MADV_WILLNEED; break; + case DONTNEED: sysAdvice = MADV_DONTNEED; break; + default: + assert(false); + return -1; + } + + cc = madvise(mBasePtr, mBaseLength, sysAdvice); + if (cc != 0) + ALOGW("madvise(%d) failed: %s\n", sysAdvice, strerror(errno)); + return cc; +#else + return -1; +#endif // HAVE_MADVISE +} diff --git a/libutils/JenkinsHash.cpp b/libutils/JenkinsHash.cpp new file mode 100644 index 0000000..52c9bb7 --- /dev/null +++ b/libutils/JenkinsHash.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2012 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. + */ + +/* Implementation of Jenkins one-at-a-time hash function. These choices are + * optimized for code size and portability, rather than raw speed. But speed + * should still be quite good. + **/ + +#include <utils/JenkinsHash.h> + +namespace android { + +hash_t JenkinsHashWhiten(uint32_t hash) { + hash += (hash << 3); + hash ^= (hash >> 11); + hash += (hash << 15); + return hash; +} + +uint32_t JenkinsHashMixBytes(uint32_t hash, const uint8_t* bytes, size_t size) { + hash = JenkinsHashMix(hash, (uint32_t)size); + size_t i; + for (i = 0; i < (size & -4); i += 4) { + uint32_t data = bytes[i] | (bytes[i+1] << 8) | (bytes[i+2] << 16) | (bytes[i+3] << 24); + hash = JenkinsHashMix(hash, data); + } + if (size & 3) { + uint32_t data = bytes[i]; + data |= ((size & 3) > 1) ? (bytes[i+1] << 8) : 0; + data |= ((size & 3) > 2) ? (bytes[i+2] << 16) : 0; + hash = JenkinsHashMix(hash, data); + } + return hash; +} + +uint32_t JenkinsHashMixShorts(uint32_t hash, const uint16_t* shorts, size_t size) { + hash = JenkinsHashMix(hash, (uint32_t)size); + size_t i; + for (i = 0; i < (size & -2); i += 2) { + uint32_t data = shorts[i] | (shorts[i+1] << 16); + hash = JenkinsHashMix(hash, data); + } + if (size & 1) { + uint32_t data = shorts[i]; + hash = JenkinsHashMix(hash, data); + } + return hash; +} + +} + diff --git a/libutils/LinearAllocator.cpp b/libutils/LinearAllocator.cpp new file mode 100644 index 0000000..a07a291 --- /dev/null +++ b/libutils/LinearAllocator.cpp @@ -0,0 +1,227 @@ +/* + * Copyright 2012, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define LOG_TAG "LinearAllocator" +#define LOG_NDEBUG 1 + +#include <stdlib.h> +#include <utils/LinearAllocator.h> +#include <utils/Log.h> + + +// The ideal size of a page allocation (these need to be multiples of 8) +#define INITIAL_PAGE_SIZE ((size_t)4096) // 4kb +#define MAX_PAGE_SIZE ((size_t)131072) // 128kb + +// The maximum amount of wasted space we can have per page +// Allocations exceeding this will have their own dedicated page +// If this is too low, we will malloc too much +// Too high, and we may waste too much space +// Must be smaller than INITIAL_PAGE_SIZE +#define MAX_WASTE_SIZE ((size_t)1024) + +#if ALIGN_DOUBLE +#define ALIGN_SZ (sizeof(double)) +#else +#define ALIGN_SZ (sizeof(int)) +#endif + +#define ALIGN(x) ((x + ALIGN_SZ - 1 ) & ~(ALIGN_SZ - 1)) +#define ALIGN_PTR(p) ((void*)(ALIGN((size_t)p))) + +#if LOG_NDEBUG +#define ADD_ALLOCATION(size) +#define RM_ALLOCATION(size) +#else +#include <utils/Thread.h> +#include <utils/Timers.h> +static size_t s_totalAllocations = 0; +static nsecs_t s_nextLog = 0; +static android::Mutex s_mutex; + +static void _logUsageLocked() { + nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); + if (now > s_nextLog) { + s_nextLog = now + milliseconds_to_nanoseconds(10); + ALOGV("Total memory usage: %zu kb", s_totalAllocations / 1024); + } +} + +static void _addAllocation(size_t size) { + android::AutoMutex lock(s_mutex); + s_totalAllocations += size; + _logUsageLocked(); +} + +#define ADD_ALLOCATION(size) _addAllocation(size); +#define RM_ALLOCATION(size) _addAllocation(-size); +#endif + +#define min(x,y) (((x) < (y)) ? (x) : (y)) + +namespace android { + +class LinearAllocator::Page { +public: + Page* next() { return mNextPage; } + void setNext(Page* next) { mNextPage = next; } + + Page() + : mNextPage(0) + {} + + void* operator new(size_t size, void* buf) { return buf; } + + void* start() { + return (void*) (((size_t)this) + sizeof(Page)); + } + + void* end(int pageSize) { + return (void*) (((size_t)start()) + pageSize); + } + +private: + Page(const Page& other) {} + Page* mNextPage; +}; + +LinearAllocator::LinearAllocator() + : mPageSize(INITIAL_PAGE_SIZE) + , mMaxAllocSize(MAX_WASTE_SIZE) + , mNext(0) + , mCurrentPage(0) + , mPages(0) + , mTotalAllocated(0) + , mWastedSpace(0) + , mPageCount(0) + , mDedicatedPageCount(0) {} + +LinearAllocator::~LinearAllocator(void) { + Page* p = mPages; + while (p) { + Page* next = p->next(); + p->~Page(); + free(p); + RM_ALLOCATION(mPageSize); + p = next; + } +} + +void* LinearAllocator::start(Page* p) { + return ALIGN_PTR(((size_t*)p) + sizeof(Page)); +} + +void* LinearAllocator::end(Page* p) { + return ((char*)p) + mPageSize; +} + +bool LinearAllocator::fitsInCurrentPage(size_t size) { + return mNext && ((char*)mNext + size) <= end(mCurrentPage); +} + +void LinearAllocator::ensureNext(size_t size) { + if (fitsInCurrentPage(size)) return; + + if (mCurrentPage && mPageSize < MAX_PAGE_SIZE) { + mPageSize = min(MAX_PAGE_SIZE, mPageSize * 2); + mPageSize = ALIGN(mPageSize); + } + mWastedSpace += mPageSize; + Page* p = newPage(mPageSize); + if (mCurrentPage) { + mCurrentPage->setNext(p); + } + mCurrentPage = p; + if (!mPages) { + mPages = mCurrentPage; + } + mNext = start(mCurrentPage); +} + +void* LinearAllocator::alloc(size_t size) { + size = ALIGN(size); + if (size > mMaxAllocSize && !fitsInCurrentPage(size)) { + ALOGV("Exceeded max size %zu > %zu", size, mMaxAllocSize); + // Allocation is too large, create a dedicated page for the allocation + Page* page = newPage(size); + mDedicatedPageCount++; + page->setNext(mPages); + mPages = page; + if (!mCurrentPage) + mCurrentPage = mPages; + return start(page); + } + ensureNext(size); + void* ptr = mNext; + mNext = ((char*)mNext) + size; + mWastedSpace -= size; + return ptr; +} + +void LinearAllocator::rewindIfLastAlloc(void* ptr, size_t allocSize) { + // Don't bother rewinding across pages + allocSize = ALIGN(allocSize); + if (ptr >= start(mCurrentPage) && ptr < end(mCurrentPage) + && ptr == ((char*)mNext - allocSize)) { + mTotalAllocated -= allocSize; + mWastedSpace += allocSize; + mNext = ptr; + } +} + +LinearAllocator::Page* LinearAllocator::newPage(size_t pageSize) { + pageSize = ALIGN(pageSize + sizeof(LinearAllocator::Page)); + ADD_ALLOCATION(pageSize); + mTotalAllocated += pageSize; + mPageCount++; + void* buf = malloc(pageSize); + return new (buf) Page(); +} + +static const char* toSize(size_t value, float& result) { + if (value < 2000) { + result = value; + return "B"; + } + if (value < 2000000) { + result = value / 1024.0f; + return "KB"; + } + result = value / 1048576.0f; + return "MB"; +} + +void LinearAllocator::dumpMemoryStats(const char* prefix) { + float prettySize; + const char* prettySuffix; + prettySuffix = toSize(mTotalAllocated, prettySize); + ALOGD("%sTotal allocated: %.2f%s", prefix, prettySize, prettySuffix); + prettySuffix = toSize(mWastedSpace, prettySize); + ALOGD("%sWasted space: %.2f%s (%.1f%%)", prefix, prettySize, prettySuffix, + (float) mWastedSpace / (float) mTotalAllocated * 100.0f); + ALOGD("%sPages %zu (dedicated %zu)", prefix, mPageCount, mDedicatedPageCount); +} + +}; // namespace android diff --git a/libutils/LinearTransform.cpp b/libutils/LinearTransform.cpp new file mode 100644 index 0000000..b7d28d4 --- /dev/null +++ b/libutils/LinearTransform.cpp @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define __STDC_LIMIT_MACROS + +#include <assert.h> +#include <stdint.h> + +#include <utils/LinearTransform.h> + +namespace android { + +template<class T> static inline T ABS(T x) { return (x < 0) ? -x : x; } + +// Static math methods involving linear transformations +static bool scale_u64_to_u64( + uint64_t val, + uint32_t N, + uint32_t D, + uint64_t* res, + bool round_up_not_down) { + uint64_t tmp1, tmp2; + uint32_t r; + + assert(res); + assert(D); + + // Let U32(X) denote a uint32_t containing the upper 32 bits of a 64 bit + // integer X. + // Let L32(X) denote a uint32_t containing the lower 32 bits of a 64 bit + // integer X. + // Let X[A, B] with A <= B denote bits A through B of the integer X. + // Let (A | B) denote the concatination of two 32 bit ints, A and B. + // IOW X = (A | B) => U32(X) == A && L32(X) == B + // + // compute M = val * N (a 96 bit int) + // --------------------------------- + // tmp2 = U32(val) * N (a 64 bit int) + // tmp1 = L32(val) * N (a 64 bit int) + // which means + // M = val * N = (tmp2 << 32) + tmp1 + tmp2 = (val >> 32) * N; + tmp1 = (val & UINT32_MAX) * N; + + // compute M[32, 95] + // tmp2 = tmp2 + U32(tmp1) + // = (U32(val) * N) + U32(L32(val) * N) + // = M[32, 95] + tmp2 += tmp1 >> 32; + + // if M[64, 95] >= D, then M/D has bits > 63 set and we have + // an overflow. + if ((tmp2 >> 32) >= D) { + *res = UINT64_MAX; + return false; + } + + // Divide. Going in we know + // tmp2 = M[32, 95] + // U32(tmp2) < D + r = tmp2 % D; + tmp2 /= D; + + // At this point + // tmp1 = L32(val) * N + // tmp2 = M[32, 95] / D + // = (M / D)[32, 95] + // r = M[32, 95] % D + // U32(tmp2) = 0 + // + // compute tmp1 = (r | M[0, 31]) + tmp1 = (tmp1 & UINT32_MAX) | ((uint64_t)r << 32); + + // Divide again. Keep the remainder around in order to round properly. + r = tmp1 % D; + tmp1 /= D; + + // At this point + // tmp2 = (M / D)[32, 95] + // tmp1 = (M / D)[ 0, 31] + // r = M % D + // U32(tmp1) = 0 + // U32(tmp2) = 0 + + // Pack the result and deal with the round-up case (As well as the + // remote possiblility over overflow in such a case). + *res = (tmp2 << 32) | tmp1; + if (r && round_up_not_down) { + ++(*res); + if (!(*res)) { + *res = UINT64_MAX; + return false; + } + } + + return true; +} + +static bool linear_transform_s64_to_s64( + int64_t val, + int64_t basis1, + int32_t N, + uint32_t D, + bool invert_frac, + int64_t basis2, + int64_t* out) { + uint64_t scaled, res; + uint64_t abs_val; + bool is_neg; + + if (!out) + return false; + + // Compute abs(val - basis_64). Keep track of whether or not this delta + // will be negative after the scale opertaion. + if (val < basis1) { + is_neg = true; + abs_val = basis1 - val; + } else { + is_neg = false; + abs_val = val - basis1; + } + + if (N < 0) + is_neg = !is_neg; + + if (!scale_u64_to_u64(abs_val, + invert_frac ? D : ABS(N), + invert_frac ? ABS(N) : D, + &scaled, + is_neg)) + return false; // overflow/undeflow + + // if scaled is >= 0x8000<etc>, then we are going to overflow or + // underflow unless ABS(basis2) is large enough to pull us back into the + // non-overflow/underflow region. + if (scaled & INT64_MIN) { + if (is_neg && (basis2 < 0)) + return false; // certain underflow + + if (!is_neg && (basis2 >= 0)) + return false; // certain overflow + + if (ABS(basis2) <= static_cast<int64_t>(scaled & INT64_MAX)) + return false; // not enough + + // Looks like we are OK + *out = (is_neg ? (-scaled) : scaled) + basis2; + } else { + // Scaled fits within signed bounds, so we just need to check for + // over/underflow for two signed integers. Basically, if both scaled + // and basis2 have the same sign bit, and the result has a different + // sign bit, then we have under/overflow. An easy way to compute this + // is + // (scaled_signbit XNOR basis_signbit) && + // (scaled_signbit XOR res_signbit) + // == + // (scaled_signbit XOR basis_signbit XOR 1) && + // (scaled_signbit XOR res_signbit) + + if (is_neg) + scaled = -scaled; + res = scaled + basis2; + + if ((scaled ^ basis2 ^ INT64_MIN) & (scaled ^ res) & INT64_MIN) + return false; + + *out = res; + } + + return true; +} + +bool LinearTransform::doForwardTransform(int64_t a_in, int64_t* b_out) const { + if (0 == a_to_b_denom) + return false; + + return linear_transform_s64_to_s64(a_in, + a_zero, + a_to_b_numer, + a_to_b_denom, + false, + b_zero, + b_out); +} + +bool LinearTransform::doReverseTransform(int64_t b_in, int64_t* a_out) const { + if (0 == a_to_b_numer) + return false; + + return linear_transform_s64_to_s64(b_in, + b_zero, + a_to_b_numer, + a_to_b_denom, + true, + a_zero, + a_out); +} + +template <class T> void LinearTransform::reduce(T* N, T* D) { + T a, b; + if (!N || !D || !(*D)) { + assert(false); + return; + } + + a = *N; + b = *D; + + if (a == 0) { + *D = 1; + return; + } + + // This implements Euclid's method to find GCD. + if (a < b) { + T tmp = a; + a = b; + b = tmp; + } + + while (1) { + // a is now the greater of the two. + const T remainder = a % b; + if (remainder == 0) { + *N /= b; + *D /= b; + return; + } + // by swapping remainder and b, we are guaranteeing that a is + // still the greater of the two upon entrance to the loop. + a = b; + b = remainder; + } +}; + +template void LinearTransform::reduce<uint64_t>(uint64_t* N, uint64_t* D); +template void LinearTransform::reduce<uint32_t>(uint32_t* N, uint32_t* D); + +void LinearTransform::reduce(int32_t* N, uint32_t* D) { + if (N && D && *D) { + if (*N < 0) { + *N = -(*N); + reduce(reinterpret_cast<uint32_t*>(N), D); + *N = -(*N); + } else { + reduce(reinterpret_cast<uint32_t*>(N), D); + } + } +} + +} // namespace android diff --git a/libutils/Log.cpp b/libutils/Log.cpp new file mode 100644 index 0000000..bffb56e --- /dev/null +++ b/libutils/Log.cpp @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "Log" + +#include <utils/Log.h> +#include <utils/Timers.h> + +namespace android { + +LogIfSlow::LogIfSlow(const char* tag, android_LogPriority priority, + int timeoutMillis, const char* message) : + mTag(tag), mPriority(priority), mTimeoutMillis(timeoutMillis), mMessage(message), + mStart(systemTime(SYSTEM_TIME_BOOTTIME)) { +} + +LogIfSlow::~LogIfSlow() { + int durationMillis = nanoseconds_to_milliseconds(systemTime(SYSTEM_TIME_BOOTTIME) - mStart); + if (durationMillis > mTimeoutMillis) { + LOG_PRI(mPriority, mTag, "%s: %dms", mMessage, durationMillis); + } +} + +} // namespace android diff --git a/libutils/Looper.cpp b/libutils/Looper.cpp new file mode 100644 index 0000000..c51df2d --- /dev/null +++ b/libutils/Looper.cpp @@ -0,0 +1,573 @@ +// +// Copyright 2010 The Android Open Source Project +// +// A looper implementation based on epoll(). +// +#define LOG_TAG "Looper" + +//#define LOG_NDEBUG 0 + +// Debugs poll and wake interactions. +#define DEBUG_POLL_AND_WAKE 0 + +// Debugs callback registration and invocation. +#define DEBUG_CALLBACKS 0 + +#include <cutils/log.h> +#include <utils/Looper.h> +#include <utils/Timers.h> + +#include <unistd.h> +#include <fcntl.h> +#include <limits.h> + + +namespace android { + +// --- WeakMessageHandler --- + +WeakMessageHandler::WeakMessageHandler(const wp<MessageHandler>& handler) : + mHandler(handler) { +} + +WeakMessageHandler::~WeakMessageHandler() { +} + +void WeakMessageHandler::handleMessage(const Message& message) { + sp<MessageHandler> handler = mHandler.promote(); + if (handler != NULL) { + handler->handleMessage(message); + } +} + + +// --- SimpleLooperCallback --- + +SimpleLooperCallback::SimpleLooperCallback(ALooper_callbackFunc callback) : + mCallback(callback) { +} + +SimpleLooperCallback::~SimpleLooperCallback() { +} + +int SimpleLooperCallback::handleEvent(int fd, int events, void* data) { + return mCallback(fd, events, data); +} + + +// --- Looper --- + +// Hint for number of file descriptors to be associated with the epoll instance. +static const int EPOLL_SIZE_HINT = 8; + +// Maximum number of file descriptors for which to retrieve poll events each iteration. +static const int EPOLL_MAX_EVENTS = 16; + +static pthread_once_t gTLSOnce = PTHREAD_ONCE_INIT; +static pthread_key_t gTLSKey = 0; + +Looper::Looper(bool allowNonCallbacks) : + mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false), + mResponseIndex(0), mNextMessageUptime(LLONG_MAX) { + int wakeFds[2]; + int result = pipe(wakeFds); + LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe. errno=%d", errno); + + mWakeReadPipeFd = wakeFds[0]; + mWakeWritePipeFd = wakeFds[1]; + + result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK); + LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking. errno=%d", + errno); + + result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK); + LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking. errno=%d", + errno); + + mIdling = false; + + // Allocate the epoll instance and register the wake pipe. + mEpollFd = epoll_create(EPOLL_SIZE_HINT); + LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance. errno=%d", errno); + + struct epoll_event eventItem; + memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union + eventItem.events = EPOLLIN; + eventItem.data.fd = mWakeReadPipeFd; + result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem); + LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance. errno=%d", + errno); +} + +Looper::~Looper() { + close(mWakeReadPipeFd); + close(mWakeWritePipeFd); + close(mEpollFd); +} + +void Looper::initTLSKey() { + int result = pthread_key_create(& gTLSKey, threadDestructor); + LOG_ALWAYS_FATAL_IF(result != 0, "Could not allocate TLS key."); +} + +void Looper::threadDestructor(void *st) { + Looper* const self = static_cast<Looper*>(st); + if (self != NULL) { + self->decStrong((void*)threadDestructor); + } +} + +void Looper::setForThread(const sp<Looper>& looper) { + sp<Looper> old = getForThread(); // also has side-effect of initializing TLS + + if (looper != NULL) { + looper->incStrong((void*)threadDestructor); + } + + pthread_setspecific(gTLSKey, looper.get()); + + if (old != NULL) { + old->decStrong((void*)threadDestructor); + } +} + +sp<Looper> Looper::getForThread() { + int result = pthread_once(& gTLSOnce, initTLSKey); + LOG_ALWAYS_FATAL_IF(result != 0, "pthread_once failed"); + + return (Looper*)pthread_getspecific(gTLSKey); +} + +sp<Looper> Looper::prepare(int opts) { + bool allowNonCallbacks = opts & ALOOPER_PREPARE_ALLOW_NON_CALLBACKS; + sp<Looper> looper = Looper::getForThread(); + if (looper == NULL) { + looper = new Looper(allowNonCallbacks); + Looper::setForThread(looper); + } + if (looper->getAllowNonCallbacks() != allowNonCallbacks) { + ALOGW("Looper already prepared for this thread with a different value for the " + "ALOOPER_PREPARE_ALLOW_NON_CALLBACKS option."); + } + return looper; +} + +bool Looper::getAllowNonCallbacks() const { + return mAllowNonCallbacks; +} + +int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) { + int result = 0; + for (;;) { + while (mResponseIndex < mResponses.size()) { + const Response& response = mResponses.itemAt(mResponseIndex++); + int ident = response.request.ident; + if (ident >= 0) { + int fd = response.request.fd; + int events = response.events; + void* data = response.request.data; +#if DEBUG_POLL_AND_WAKE + ALOGD("%p ~ pollOnce - returning signalled identifier %d: " + "fd=%d, events=0x%x, data=%p", + this, ident, fd, events, data); +#endif + if (outFd != NULL) *outFd = fd; + if (outEvents != NULL) *outEvents = events; + if (outData != NULL) *outData = data; + return ident; + } + } + + if (result != 0) { +#if DEBUG_POLL_AND_WAKE + ALOGD("%p ~ pollOnce - returning result %d", this, result); +#endif + if (outFd != NULL) *outFd = 0; + if (outEvents != NULL) *outEvents = 0; + if (outData != NULL) *outData = NULL; + return result; + } + + result = pollInner(timeoutMillis); + } +} + +int Looper::pollInner(int timeoutMillis) { +#if DEBUG_POLL_AND_WAKE + ALOGD("%p ~ pollOnce - waiting: timeoutMillis=%d", this, timeoutMillis); +#endif + + // Adjust the timeout based on when the next message is due. + if (timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) { + nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); + int messageTimeoutMillis = toMillisecondTimeoutDelay(now, mNextMessageUptime); + if (messageTimeoutMillis >= 0 + && (timeoutMillis < 0 || messageTimeoutMillis < timeoutMillis)) { + timeoutMillis = messageTimeoutMillis; + } +#if DEBUG_POLL_AND_WAKE + ALOGD("%p ~ pollOnce - next message in %lldns, adjusted timeout: timeoutMillis=%d", + this, mNextMessageUptime - now, timeoutMillis); +#endif + } + + // Poll. + int result = ALOOPER_POLL_WAKE; + mResponses.clear(); + mResponseIndex = 0; + + // We are about to idle. + mIdling = true; + + struct epoll_event eventItems[EPOLL_MAX_EVENTS]; + int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis); + + // No longer idling. + mIdling = false; + + // Acquire lock. + mLock.lock(); + + // Check for poll error. + if (eventCount < 0) { + if (errno == EINTR) { + goto Done; + } + ALOGW("Poll failed with an unexpected error, errno=%d", errno); + result = ALOOPER_POLL_ERROR; + goto Done; + } + + // Check for poll timeout. + if (eventCount == 0) { +#if DEBUG_POLL_AND_WAKE + ALOGD("%p ~ pollOnce - timeout", this); +#endif + result = ALOOPER_POLL_TIMEOUT; + goto Done; + } + + // Handle all events. +#if DEBUG_POLL_AND_WAKE + ALOGD("%p ~ pollOnce - handling events from %d fds", this, eventCount); +#endif + + for (int i = 0; i < eventCount; i++) { + int fd = eventItems[i].data.fd; + uint32_t epollEvents = eventItems[i].events; + if (fd == mWakeReadPipeFd) { + if (epollEvents & EPOLLIN) { + awoken(); + } else { + ALOGW("Ignoring unexpected epoll events 0x%x on wake read pipe.", epollEvents); + } + } else { + ssize_t requestIndex = mRequests.indexOfKey(fd); + if (requestIndex >= 0) { + int events = 0; + if (epollEvents & EPOLLIN) events |= ALOOPER_EVENT_INPUT; + if (epollEvents & EPOLLOUT) events |= ALOOPER_EVENT_OUTPUT; + if (epollEvents & EPOLLERR) events |= ALOOPER_EVENT_ERROR; + if (epollEvents & EPOLLHUP) events |= ALOOPER_EVENT_HANGUP; + pushResponse(events, mRequests.valueAt(requestIndex)); + } else { + ALOGW("Ignoring unexpected epoll events 0x%x on fd %d that is " + "no longer registered.", epollEvents, fd); + } + } + } +Done: ; + + // Invoke pending message callbacks. + mNextMessageUptime = LLONG_MAX; + while (mMessageEnvelopes.size() != 0) { + nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); + const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0); + if (messageEnvelope.uptime <= now) { + // Remove the envelope from the list. + // We keep a strong reference to the handler until the call to handleMessage + // finishes. Then we drop it so that the handler can be deleted *before* + // we reacquire our lock. + { // obtain handler + sp<MessageHandler> handler = messageEnvelope.handler; + Message message = messageEnvelope.message; + mMessageEnvelopes.removeAt(0); + mSendingMessage = true; + mLock.unlock(); + +#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS + ALOGD("%p ~ pollOnce - sending message: handler=%p, what=%d", + this, handler.get(), message.what); +#endif + handler->handleMessage(message); + } // release handler + + mLock.lock(); + mSendingMessage = false; + result = ALOOPER_POLL_CALLBACK; + } else { + // The last message left at the head of the queue determines the next wakeup time. + mNextMessageUptime = messageEnvelope.uptime; + break; + } + } + + // Release lock. + mLock.unlock(); + + // Invoke all response callbacks. + for (size_t i = 0; i < mResponses.size(); i++) { + Response& response = mResponses.editItemAt(i); + if (response.request.ident == ALOOPER_POLL_CALLBACK) { + int fd = response.request.fd; + int events = response.events; + void* data = response.request.data; +#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS + ALOGD("%p ~ pollOnce - invoking fd event callback %p: fd=%d, events=0x%x, data=%p", + this, response.request.callback.get(), fd, events, data); +#endif + int callbackResult = response.request.callback->handleEvent(fd, events, data); + if (callbackResult == 0) { + removeFd(fd); + } + // Clear the callback reference in the response structure promptly because we + // will not clear the response vector itself until the next poll. + response.request.callback.clear(); + result = ALOOPER_POLL_CALLBACK; + } + } + return result; +} + +int Looper::pollAll(int timeoutMillis, int* outFd, int* outEvents, void** outData) { + if (timeoutMillis <= 0) { + int result; + do { + result = pollOnce(timeoutMillis, outFd, outEvents, outData); + } while (result == ALOOPER_POLL_CALLBACK); + return result; + } else { + nsecs_t endTime = systemTime(SYSTEM_TIME_MONOTONIC) + + milliseconds_to_nanoseconds(timeoutMillis); + + for (;;) { + int result = pollOnce(timeoutMillis, outFd, outEvents, outData); + if (result != ALOOPER_POLL_CALLBACK) { + return result; + } + + nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); + timeoutMillis = toMillisecondTimeoutDelay(now, endTime); + if (timeoutMillis == 0) { + return ALOOPER_POLL_TIMEOUT; + } + } + } +} + +void Looper::wake() { +#if DEBUG_POLL_AND_WAKE + ALOGD("%p ~ wake", this); +#endif + + ssize_t nWrite; + do { + nWrite = write(mWakeWritePipeFd, "W", 1); + } while (nWrite == -1 && errno == EINTR); + + if (nWrite != 1) { + if (errno != EAGAIN) { + ALOGW("Could not write wake signal, errno=%d", errno); + } + } +} + +void Looper::awoken() { +#if DEBUG_POLL_AND_WAKE + ALOGD("%p ~ awoken", this); +#endif + + char buffer[16]; + ssize_t nRead; + do { + nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer)); + } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer)); +} + +void Looper::pushResponse(int events, const Request& request) { + Response response; + response.events = events; + response.request = request; + mResponses.push(response); +} + +int Looper::addFd(int fd, int ident, int events, ALooper_callbackFunc callback, void* data) { + return addFd(fd, ident, events, callback ? new SimpleLooperCallback(callback) : NULL, data); +} + +int Looper::addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data) { +#if DEBUG_CALLBACKS + ALOGD("%p ~ addFd - fd=%d, ident=%d, events=0x%x, callback=%p, data=%p", this, fd, ident, + events, callback.get(), data); +#endif + + if (!callback.get()) { + if (! mAllowNonCallbacks) { + ALOGE("Invalid attempt to set NULL callback but not allowed for this looper."); + return -1; + } + + if (ident < 0) { + ALOGE("Invalid attempt to set NULL callback with ident < 0."); + return -1; + } + } else { + ident = ALOOPER_POLL_CALLBACK; + } + + int epollEvents = 0; + if (events & ALOOPER_EVENT_INPUT) epollEvents |= EPOLLIN; + if (events & ALOOPER_EVENT_OUTPUT) epollEvents |= EPOLLOUT; + + { // acquire lock + AutoMutex _l(mLock); + + Request request; + request.fd = fd; + request.ident = ident; + request.callback = callback; + request.data = data; + + struct epoll_event eventItem; + memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union + eventItem.events = epollEvents; + eventItem.data.fd = fd; + + ssize_t requestIndex = mRequests.indexOfKey(fd); + if (requestIndex < 0) { + int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, & eventItem); + if (epollResult < 0) { + ALOGE("Error adding epoll events for fd %d, errno=%d", fd, errno); + return -1; + } + mRequests.add(fd, request); + } else { + int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_MOD, fd, & eventItem); + if (epollResult < 0) { + ALOGE("Error modifying epoll events for fd %d, errno=%d", fd, errno); + return -1; + } + mRequests.replaceValueAt(requestIndex, request); + } + } // release lock + return 1; +} + +int Looper::removeFd(int fd) { +#if DEBUG_CALLBACKS + ALOGD("%p ~ removeFd - fd=%d", this, fd); +#endif + + { // acquire lock + AutoMutex _l(mLock); + ssize_t requestIndex = mRequests.indexOfKey(fd); + if (requestIndex < 0) { + return 0; + } + + int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_DEL, fd, NULL); + if (epollResult < 0) { + ALOGE("Error removing epoll events for fd %d, errno=%d", fd, errno); + return -1; + } + + mRequests.removeItemsAt(requestIndex); + } // release lock + return 1; +} + +void Looper::sendMessage(const sp<MessageHandler>& handler, const Message& message) { + nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); + sendMessageAtTime(now, handler, message); +} + +void Looper::sendMessageDelayed(nsecs_t uptimeDelay, const sp<MessageHandler>& handler, + const Message& message) { + nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); + sendMessageAtTime(now + uptimeDelay, handler, message); +} + +void Looper::sendMessageAtTime(nsecs_t uptime, const sp<MessageHandler>& handler, + const Message& message) { +#if DEBUG_CALLBACKS + ALOGD("%p ~ sendMessageAtTime - uptime=%lld, handler=%p, what=%d", + this, uptime, handler.get(), message.what); +#endif + + size_t i = 0; + { // acquire lock + AutoMutex _l(mLock); + + size_t messageCount = mMessageEnvelopes.size(); + while (i < messageCount && uptime >= mMessageEnvelopes.itemAt(i).uptime) { + i += 1; + } + + MessageEnvelope messageEnvelope(uptime, handler, message); + mMessageEnvelopes.insertAt(messageEnvelope, i, 1); + + // Optimization: If the Looper is currently sending a message, then we can skip + // the call to wake() because the next thing the Looper will do after processing + // messages is to decide when the next wakeup time should be. In fact, it does + // not even matter whether this code is running on the Looper thread. + if (mSendingMessage) { + return; + } + } // release lock + + // Wake the poll loop only when we enqueue a new message at the head. + if (i == 0) { + wake(); + } +} + +void Looper::removeMessages(const sp<MessageHandler>& handler) { +#if DEBUG_CALLBACKS + ALOGD("%p ~ removeMessages - handler=%p", this, handler.get()); +#endif + + { // acquire lock + AutoMutex _l(mLock); + + for (size_t i = mMessageEnvelopes.size(); i != 0; ) { + const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(--i); + if (messageEnvelope.handler == handler) { + mMessageEnvelopes.removeAt(i); + } + } + } // release lock +} + +void Looper::removeMessages(const sp<MessageHandler>& handler, int what) { +#if DEBUG_CALLBACKS + ALOGD("%p ~ removeMessages - handler=%p, what=%d", this, handler.get(), what); +#endif + + { // acquire lock + AutoMutex _l(mLock); + + for (size_t i = mMessageEnvelopes.size(); i != 0; ) { + const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(--i); + if (messageEnvelope.handler == handler + && messageEnvelope.message.what == what) { + mMessageEnvelopes.removeAt(i); + } + } + } // release lock +} + +bool Looper::isIdling() const { + return mIdling; +} + +} // namespace android diff --git a/libutils/MODULE_LICENSE_APACHE2 b/libutils/MODULE_LICENSE_APACHE2 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/libutils/MODULE_LICENSE_APACHE2 diff --git a/libutils/NOTICE b/libutils/NOTICE new file mode 100644 index 0000000..c5b1efa --- /dev/null +++ b/libutils/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-2008, The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/libutils/PropertyMap.cpp b/libutils/PropertyMap.cpp new file mode 100644 index 0000000..5520702 --- /dev/null +++ b/libutils/PropertyMap.cpp @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "PropertyMap" + +#include <stdlib.h> +#include <string.h> + +#include <utils/PropertyMap.h> +#include <utils/Log.h> + +// Enables debug output for the parser. +#define DEBUG_PARSER 0 + +// Enables debug output for parser performance. +#define DEBUG_PARSER_PERFORMANCE 0 + + +namespace android { + +static const char* WHITESPACE = " \t\r"; +static const char* WHITESPACE_OR_PROPERTY_DELIMITER = " \t\r="; + + +// --- PropertyMap --- + +PropertyMap::PropertyMap() { +} + +PropertyMap::~PropertyMap() { +} + +void PropertyMap::clear() { + mProperties.clear(); +} + +void PropertyMap::addProperty(const String8& key, const String8& value) { + mProperties.add(key, value); +} + +bool PropertyMap::hasProperty(const String8& key) const { + return mProperties.indexOfKey(key) >= 0; +} + +bool PropertyMap::tryGetProperty(const String8& key, String8& outValue) const { + ssize_t index = mProperties.indexOfKey(key); + if (index < 0) { + return false; + } + + outValue = mProperties.valueAt(index); + return true; +} + +bool PropertyMap::tryGetProperty(const String8& key, bool& outValue) const { + int32_t intValue; + if (!tryGetProperty(key, intValue)) { + return false; + } + + outValue = intValue; + return true; +} + +bool PropertyMap::tryGetProperty(const String8& key, int32_t& outValue) const { + String8 stringValue; + if (! tryGetProperty(key, stringValue) || stringValue.length() == 0) { + return false; + } + + char* end; + int value = strtol(stringValue.string(), & end, 10); + if (*end != '\0') { + ALOGW("Property key '%s' has invalid value '%s'. Expected an integer.", + key.string(), stringValue.string()); + return false; + } + outValue = value; + return true; +} + +bool PropertyMap::tryGetProperty(const String8& key, float& outValue) const { + String8 stringValue; + if (! tryGetProperty(key, stringValue) || stringValue.length() == 0) { + return false; + } + + char* end; + float value = strtof(stringValue.string(), & end); + if (*end != '\0') { + ALOGW("Property key '%s' has invalid value '%s'. Expected a float.", + key.string(), stringValue.string()); + return false; + } + outValue = value; + return true; +} + +void PropertyMap::addAll(const PropertyMap* map) { + for (size_t i = 0; i < map->mProperties.size(); i++) { + mProperties.add(map->mProperties.keyAt(i), map->mProperties.valueAt(i)); + } +} + +status_t PropertyMap::load(const String8& filename, PropertyMap** outMap) { + *outMap = NULL; + + Tokenizer* tokenizer; + status_t status = Tokenizer::open(filename, &tokenizer); + if (status) { + ALOGE("Error %d opening property file %s.", status, filename.string()); + } else { + PropertyMap* map = new PropertyMap(); + if (!map) { + ALOGE("Error allocating property map."); + status = NO_MEMORY; + } else { +#if DEBUG_PARSER_PERFORMANCE + nsecs_t startTime = systemTime(SYSTEM_TIME_MONOTONIC); +#endif + Parser parser(map, tokenizer); + status = parser.parse(); +#if DEBUG_PARSER_PERFORMANCE + nsecs_t elapsedTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime; + ALOGD("Parsed property file '%s' %d lines in %0.3fms.", + tokenizer->getFilename().string(), tokenizer->getLineNumber(), + elapsedTime / 1000000.0); +#endif + if (status) { + delete map; + } else { + *outMap = map; + } + } + delete tokenizer; + } + return status; +} + + +// --- PropertyMap::Parser --- + +PropertyMap::Parser::Parser(PropertyMap* map, Tokenizer* tokenizer) : + mMap(map), mTokenizer(tokenizer) { +} + +PropertyMap::Parser::~Parser() { +} + +status_t PropertyMap::Parser::parse() { + while (!mTokenizer->isEof()) { +#if DEBUG_PARSER + ALOGD("Parsing %s: '%s'.", mTokenizer->getLocation().string(), + mTokenizer->peekRemainderOfLine().string()); +#endif + + mTokenizer->skipDelimiters(WHITESPACE); + + if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') { + String8 keyToken = mTokenizer->nextToken(WHITESPACE_OR_PROPERTY_DELIMITER); + if (keyToken.isEmpty()) { + ALOGE("%s: Expected non-empty property key.", mTokenizer->getLocation().string()); + return BAD_VALUE; + } + + mTokenizer->skipDelimiters(WHITESPACE); + + if (mTokenizer->nextChar() != '=') { + ALOGE("%s: Expected '=' between property key and value.", + mTokenizer->getLocation().string()); + return BAD_VALUE; + } + + mTokenizer->skipDelimiters(WHITESPACE); + + String8 valueToken = mTokenizer->nextToken(WHITESPACE); + if (valueToken.find("\\", 0) >= 0 || valueToken.find("\"", 0) >= 0) { + ALOGE("%s: Found reserved character '\\' or '\"' in property value.", + mTokenizer->getLocation().string()); + return BAD_VALUE; + } + + mTokenizer->skipDelimiters(WHITESPACE); + if (!mTokenizer->isEol()) { + ALOGE("%s: Expected end of line, got '%s'.", + mTokenizer->getLocation().string(), + mTokenizer->peekRemainderOfLine().string()); + return BAD_VALUE; + } + + if (mMap->hasProperty(keyToken)) { + ALOGE("%s: Duplicate property value for key '%s'.", + mTokenizer->getLocation().string(), keyToken.string()); + return BAD_VALUE; + } + + mMap->addProperty(keyToken, valueToken); + } + + mTokenizer->nextLine(); + } + return NO_ERROR; +} + +} // namespace android diff --git a/libutils/README b/libutils/README new file mode 100644 index 0000000..01741e0 --- /dev/null +++ b/libutils/README @@ -0,0 +1,289 @@ +Android Utility Function Library +================================ + + +If you need a feature that is native to Linux but not present on other +platforms, construct a platform-dependent implementation that shares +the Linux interface. That way the actual device runs as "light" as +possible. + +If that isn't feasible, create a system-independent interface and hide +the details. + +The ultimate goal is *not* to create a super-duper platform abstraction +layer. The goal is to provide an optimized solution for Linux with +reasonable implementations for other platforms. + + + +Resource overlay +================ + + +Introduction +------------ + +Overlay packages are special .apk files which provide no code but +additional resource values (and possibly new configurations) for +resources in other packages. When an application requests resources, +the system will return values from either the application's original +package or any associated overlay package. Any redirection is completely +transparent to the calling application. + +Resource values have the following precedence table, listed in +descending precedence. + + * overlay package, matching config (eg res/values-en-land) + + * original package, matching config + + * overlay package, no config (eg res/values) + + * original package, no config + +During compilation, overlay packages are differentiated from regular +packages by passing the -o flag to aapt. + + +Background +---------- + +This section provides generic background material on resources in +Android. + + +How resources are bundled in .apk files +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Android .apk files are .zip files, usually housing .dex code, +certificates and resources, though packages containing resources but +no code are possible. Resources can be divided into the following +categories; a `configuration' indicates a set of phone language, display +density, network operator, etc. + + * assets: uncompressed, raw files packaged as part of an .apk and + explicitly referenced by filename. These files are + independent of configuration. + + * res/drawable: bitmap or xml graphics. Each file may have different + values depending on configuration. + + * res/values: integers, strings, etc. Each resource may have different + values depending on configuration. + +Resource meta information and information proper is stored in a binary +format in a named file resources.arsc, bundled as part of the .apk. + +Resource IDs and lookup +~~~~~~~~~~~~~~~~~~~~~~~ +During compilation, the aapt tool gathers application resources and +generates a resources.arsc file. Each resource name is assigned an +integer ID 0xppttiii (translated to a symbolic name via R.java), where + + * pp: corresponds to the package namespace (details below). + + * tt: corresponds to the resource type (string, int, etc). Every + resource of the same type within the same package has the same + tt value, but depending on available types, the actual numerical + value may be different between packages. + + * iiii: sequential number, assigned in the order resources are found. + +Resource values are specified paired with a set of configuration +constraints (the default being the empty set), eg res/values-sv-port +which imposes restrictions on language (Swedish) and display orientation +(portrait). During lookup, every constraint set is matched against the +current configuration, and the value corresponding to the best matching +constraint set is returned (ResourceTypes.{h,cpp}). + +Parsing of resources.arsc is handled by ResourceTypes.cpp; this utility +is governed by AssetManager.cpp, which tracks loaded resources per +process. + +Assets are looked up by path and filename in AssetManager.cpp. The path +to resources in res/drawable are located by ResourceTypes.cpp and then +handled like assets by AssetManager.cpp. Other resources are handled +solely by ResourceTypes.cpp. + +Package ID as namespace +~~~~~~~~~~~~~~~~~~~~~~~ +The pp part of a resource ID defines a namespace. Android currently +defines two namespaces: + + * 0x01: system resources (pre-installed in framework-res.apk) + + * 0x7f: application resources (bundled in the application .apk) + +ResourceTypes.cpp supports package IDs between 0x01 and 0x7f +(inclusive); values outside this range are invalid. + +Each running (Dalvik) process is assigned a unique instance of +AssetManager, which in turn keeps a forest structure of loaded +resource.arsc files. Normally, this forest is structured as follows, +where mPackageMap is the internal vector employed in ResourceTypes.cpp. + +mPackageMap[0x00] -> system package +mPackageMap[0x01] -> NULL +mPackageMap[0x02] -> NULL +... +mPackageMap[0x7f - 2] -> NULL +mPackageMap[0x7f - 1] -> application package + + + +The resource overlay extension +------------------------------ + +The resource overlay mechanism aims to (partly) shadow and extend +existing resources with new values for defined and new configurations. +Technically, this is achieved by adding resource-only packages (called +overlay packages) to existing resource namespaces, like so: + +mPackageMap[0x00] -> system package -> system overlay package +mPackageMap[0x01] -> NULL +mPackageMap[0x02] -> NULL +... +mPackageMap[0x7f - 2] -> NULL +mPackageMap[0x7f - 1] -> application package -> overlay 1 -> overlay 2 + +The use of overlay resources is completely transparent to +applications; no additional resource identifiers are introduced, only +configuration/value pairs. Any number of overlay packages may be loaded +at a time; overlay packages are agnostic to what they target -- both +system and application resources are fair game. + +The package targeted by an overlay package is called the target or +original package. + +Resource overlay operates on symbolic resources names. Hence, to +override the string/str1 resources in a package, the overlay package +would include a resource also named string/str1. The end user does not +have to worry about the numeric resources IDs assigned by aapt, as this +is resolved automatically by the system. + +As of this writing, the use of resource overlay has not been fully +explored. Until it has, only OEMs are trusted to use resource overlay. +For this reason, overlay packages must reside in /system/overlay. + + +Resource ID mapping +~~~~~~~~~~~~~~~~~~~ +Resource identifiers must be coherent within the same namespace (ie +PackageGroup in ResourceTypes.cpp). Calling applications will refer to +resources using the IDs defined in the original package, but there is no +guarantee aapt has assigned the same ID to the corresponding resource in +an overlay package. To translate between the two, a resource ID mapping +{original ID -> overlay ID} is created during package installation +(PackageManagerService.java) and used during resource lookup. The +mapping is stored in /data/resource-cache, with a @idmap file name +suffix. + +The idmap file format is documented in a separate section, below. + + +Package management +~~~~~~~~~~~~~~~~~~ +Packages are managed by the PackageManagerService. Addition and removal +of packages are monitored via the inotify framework, exposed via +android.os.FileObserver. + +During initialization of a Dalvik process, ActivityThread.java requests +the process' AssetManager (by proxy, via AssetManager.java and JNI) +to load a list of packages. This list includes overlay packages, if +present. + +When a target package or a corresponding overlay package is installed, +the target package's process is stopped and a new idmap is generated. +This is similar to how applications are stopped when their packages are +upgraded. + + +Creating overlay packages +------------------------- + +Overlay packages should contain no code, define (some) resources with +the same type and name as in the original package, and be compiled with +the -o flag passed to aapt. + +The aapt -o flag instructs aapt to create an overlay package. +Technically, this means the package will be assigned package id 0x00. + +There are no restrictions on overlay packages names, though the naming +convention <original.package.name>.overlay.<name> is recommended. + + +Example overlay package +~~~~~~~~~~~~~~~~~~~~~~~ + +To overlay the resource bool/b in package com.foo.bar, to be applied +when the display is in landscape mode, create a new package with +no source code and a single .xml file under res/values-land, with +an entry for bool/b. Compile with aapt -o and place the results in +/system/overlay by adding the following to Android.mk: + +LOCAL_AAPT_FLAGS := -o com.foo.bar +LOCAL_MODULE_PATH := $(TARGET_OUT)/overlay + + +The ID map (idmap) file format +------------------------------ + +The idmap format is designed for lookup performance. However, leading +and trailing undefined overlay values are discarded to reduce the memory +footprint. + + +idmap grammar +~~~~~~~~~~~~~ +All atoms (names in square brackets) are uint32_t integers. The +idmap-magic constant spells "idmp" in ASCII. Offsets are given relative +to the data_header, not to the beginning of the file. + +map := header data +header := idmap-magic <crc32-original-pkg> <crc32-overlay-pkg> +idmap-magic := <0x706d6469> +data := data_header type_block+ +data_header := <m> header_block{m} +header_block := <0> | <type_block_offset> +type_block := <n> <id_offset> entry{n} +entry := <resource_id_in_target_package> + + +idmap example +~~~~~~~~~~~~~ +Given a pair of target and overlay packages with CRC sums 0x216a8fe2 +and 0x6b9beaec, each defining the following resources + +Name Target package Overlay package +string/str0 0x7f010000 - +string/str1 0x7f010001 0x7f010000 +string/str2 0x7f010002 - +string/str3 0x7f010003 0x7f010001 +string/str4 0x7f010004 - +bool/bool0 0x7f020000 - +integer/int0 0x7f030000 0x7f020000 +integer/int1 0x7f030001 - + +the corresponding resource map is + +0x706d6469 0x216a8fe2 0x6b9beaec 0x00000003 \ +0x00000004 0x00000000 0x00000009 0x00000003 \ +0x00000001 0x7f010000 0x00000000 0x7f010001 \ +0x00000001 0x00000000 0x7f020000 + +or, formatted differently + +0x706d6469 # magic: all idmap files begin with this constant +0x216a8fe2 # CRC32 of the resources.arsc file in the original package +0x6b9beaec # CRC32 of the resources.arsc file in the overlay package +0x00000003 # header; three types (string, bool, integer) in the target package +0x00000004 # header_block for type 0 (string) is located at offset 4 +0x00000000 # no bool type exists in overlay package -> no header_block +0x00000009 # header_block for type 2 (integer) is located at offset 9 +0x00000003 # header_block for string; overlay IDs span 3 elements +0x00000001 # the first string in target package is entry 1 == offset +0x7f010000 # target 0x7f01001 -> overlay 0x7f010000 +0x00000000 # str2 not defined in overlay package +0x7f010001 # target 0x7f010003 -> overlay 0x7f010001 +0x00000001 # header_block for integer; overlay IDs span 1 element +0x00000000 # offset == 0 +0x7f020000 # target 0x7f030000 -> overlay 0x7f020000 diff --git a/libutils/RefBase.cpp b/libutils/RefBase.cpp new file mode 100644 index 0000000..f398a82 --- /dev/null +++ b/libutils/RefBase.cpp @@ -0,0 +1,650 @@ +/* + * Copyright (C) 2005 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "RefBase" +// #define LOG_NDEBUG 0 + +#include <utils/RefBase.h> + +#include <utils/Atomic.h> +#include <utils/CallStack.h> +#include <utils/Log.h> +#include <utils/threads.h> + +#include <stdlib.h> +#include <stdio.h> +#include <typeinfo> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> + +// compile with refcounting debugging enabled +#define DEBUG_REFS 0 + +// whether ref-tracking is enabled by default, if not, trackMe(true, false) +// needs to be called explicitly +#define DEBUG_REFS_ENABLED_BY_DEFAULT 0 + +// whether callstack are collected (significantly slows things down) +#define DEBUG_REFS_CALLSTACK_ENABLED 1 + +// folder where stack traces are saved when DEBUG_REFS is enabled +// this folder needs to exist and be writable +#define DEBUG_REFS_CALLSTACK_PATH "/data/debug" + +// log all reference counting operations +#define PRINT_REFS 0 + +// --------------------------------------------------------------------------- + +namespace android { + +#define INITIAL_STRONG_VALUE (1<<28) + +// --------------------------------------------------------------------------- + +class RefBase::weakref_impl : public RefBase::weakref_type +{ +public: + volatile int32_t mStrong; + volatile int32_t mWeak; + RefBase* const mBase; + volatile int32_t mFlags; + +#if !DEBUG_REFS + + weakref_impl(RefBase* base) + : mStrong(INITIAL_STRONG_VALUE) + , mWeak(0) + , mBase(base) + , mFlags(0) + { + } + + void addStrongRef(const void* /*id*/) { } + void removeStrongRef(const void* /*id*/) { } + void renameStrongRefId(const void* /*old_id*/, const void* /*new_id*/) { } + void addWeakRef(const void* /*id*/) { } + void removeWeakRef(const void* /*id*/) { } + void renameWeakRefId(const void* /*old_id*/, const void* /*new_id*/) { } + void printRefs() const { } + void trackMe(bool, bool) { } + +#else + + weakref_impl(RefBase* base) + : mStrong(INITIAL_STRONG_VALUE) + , mWeak(0) + , mBase(base) + , mFlags(0) + , mStrongRefs(NULL) + , mWeakRefs(NULL) + , mTrackEnabled(!!DEBUG_REFS_ENABLED_BY_DEFAULT) + , mRetain(false) + { + } + + ~weakref_impl() + { + bool dumpStack = false; + if (!mRetain && mStrongRefs != NULL) { + dumpStack = true; + ALOGE("Strong references remain:"); + ref_entry* refs = mStrongRefs; + while (refs) { + char inc = refs->ref >= 0 ? '+' : '-'; + ALOGD("\t%c ID %p (ref %d):", inc, refs->id, refs->ref); +#if DEBUG_REFS_CALLSTACK_ENABLED + refs->stack.dump(LOG_TAG); +#endif + refs = refs->next; + } + } + + if (!mRetain && mWeakRefs != NULL) { + dumpStack = true; + ALOGE("Weak references remain!"); + ref_entry* refs = mWeakRefs; + while (refs) { + char inc = refs->ref >= 0 ? '+' : '-'; + ALOGD("\t%c ID %p (ref %d):", inc, refs->id, refs->ref); +#if DEBUG_REFS_CALLSTACK_ENABLED + refs->stack.dump(LOG_TAG); +#endif + refs = refs->next; + } + } + if (dumpStack) { + ALOGE("above errors at:"); + CallStack stack(LOG_TAG); + } + } + + void addStrongRef(const void* id) { + //ALOGD_IF(mTrackEnabled, + // "addStrongRef: RefBase=%p, id=%p", mBase, id); + addRef(&mStrongRefs, id, mStrong); + } + + void removeStrongRef(const void* id) { + //ALOGD_IF(mTrackEnabled, + // "removeStrongRef: RefBase=%p, id=%p", mBase, id); + if (!mRetain) { + removeRef(&mStrongRefs, id); + } else { + addRef(&mStrongRefs, id, -mStrong); + } + } + + void renameStrongRefId(const void* old_id, const void* new_id) { + //ALOGD_IF(mTrackEnabled, + // "renameStrongRefId: RefBase=%p, oid=%p, nid=%p", + // mBase, old_id, new_id); + renameRefsId(mStrongRefs, old_id, new_id); + } + + void addWeakRef(const void* id) { + addRef(&mWeakRefs, id, mWeak); + } + + void removeWeakRef(const void* id) { + if (!mRetain) { + removeRef(&mWeakRefs, id); + } else { + addRef(&mWeakRefs, id, -mWeak); + } + } + + void renameWeakRefId(const void* old_id, const void* new_id) { + renameRefsId(mWeakRefs, old_id, new_id); + } + + void trackMe(bool track, bool retain) + { + mTrackEnabled = track; + mRetain = retain; + } + + void printRefs() const + { + String8 text; + + { + Mutex::Autolock _l(mMutex); + char buf[128]; + sprintf(buf, "Strong references on RefBase %p (weakref_type %p):\n", mBase, this); + text.append(buf); + printRefsLocked(&text, mStrongRefs); + sprintf(buf, "Weak references on RefBase %p (weakref_type %p):\n", mBase, this); + text.append(buf); + printRefsLocked(&text, mWeakRefs); + } + + { + char name[100]; + snprintf(name, 100, DEBUG_REFS_CALLSTACK_PATH "/%p.stack", this); + int rc = open(name, O_RDWR | O_CREAT | O_APPEND, 644); + if (rc >= 0) { + write(rc, text.string(), text.length()); + close(rc); + ALOGD("STACK TRACE for %p saved in %s", this, name); + } + else ALOGE("FAILED TO PRINT STACK TRACE for %p in %s: %s", this, + name, strerror(errno)); + } + } + +private: + struct ref_entry + { + ref_entry* next; + const void* id; +#if DEBUG_REFS_CALLSTACK_ENABLED + CallStack stack; +#endif + int32_t ref; + }; + + void addRef(ref_entry** refs, const void* id, int32_t mRef) + { + if (mTrackEnabled) { + AutoMutex _l(mMutex); + + ref_entry* ref = new ref_entry; + // Reference count at the time of the snapshot, but before the + // update. Positive value means we increment, negative--we + // decrement the reference count. + ref->ref = mRef; + ref->id = id; +#if DEBUG_REFS_CALLSTACK_ENABLED + ref->stack.update(2); +#endif + ref->next = *refs; + *refs = ref; + } + } + + void removeRef(ref_entry** refs, const void* id) + { + if (mTrackEnabled) { + AutoMutex _l(mMutex); + + ref_entry* const head = *refs; + ref_entry* ref = head; + while (ref != NULL) { + if (ref->id == id) { + *refs = ref->next; + delete ref; + return; + } + refs = &ref->next; + ref = *refs; + } + + ALOGE("RefBase: removing id %p on RefBase %p" + "(weakref_type %p) that doesn't exist!", + id, mBase, this); + + ref = head; + while (ref) { + char inc = ref->ref >= 0 ? '+' : '-'; + ALOGD("\t%c ID %p (ref %d):", inc, ref->id, ref->ref); + ref = ref->next; + } + + CallStack stack(LOG_TAG); + } + } + + void renameRefsId(ref_entry* r, const void* old_id, const void* new_id) + { + if (mTrackEnabled) { + AutoMutex _l(mMutex); + ref_entry* ref = r; + while (ref != NULL) { + if (ref->id == old_id) { + ref->id = new_id; + } + ref = ref->next; + } + } + } + + void printRefsLocked(String8* out, const ref_entry* refs) const + { + char buf[128]; + while (refs) { + char inc = refs->ref >= 0 ? '+' : '-'; + sprintf(buf, "\t%c ID %p (ref %d):\n", + inc, refs->id, refs->ref); + out->append(buf); +#if DEBUG_REFS_CALLSTACK_ENABLED + out->append(refs->stack.toString("\t\t")); +#else + out->append("\t\t(call stacks disabled)"); +#endif + refs = refs->next; + } + } + + mutable Mutex mMutex; + ref_entry* mStrongRefs; + ref_entry* mWeakRefs; + + bool mTrackEnabled; + // Collect stack traces on addref and removeref, instead of deleting the stack references + // on removeref that match the address ones. + bool mRetain; + +#endif +}; + +// --------------------------------------------------------------------------- + +void RefBase::incStrong(const void* id) const +{ + weakref_impl* const refs = mRefs; + refs->incWeak(id); + + refs->addStrongRef(id); + const int32_t c = android_atomic_inc(&refs->mStrong); + ALOG_ASSERT(c > 0, "incStrong() called on %p after last strong ref", refs); +#if PRINT_REFS + ALOGD("incStrong of %p from %p: cnt=%d\n", this, id, c); +#endif + if (c != INITIAL_STRONG_VALUE) { + return; + } + + android_atomic_add(-INITIAL_STRONG_VALUE, &refs->mStrong); + refs->mBase->onFirstRef(); +} + +void RefBase::decStrong(const void* id) const +{ + weakref_impl* const refs = mRefs; + refs->removeStrongRef(id); + const int32_t c = android_atomic_dec(&refs->mStrong); +#if PRINT_REFS + ALOGD("decStrong of %p from %p: cnt=%d\n", this, id, c); +#endif + ALOG_ASSERT(c >= 1, "decStrong() called on %p too many times", refs); + if (c == 1) { + refs->mBase->onLastStrongRef(id); + if ((refs->mFlags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_STRONG) { + delete this; + } + } + refs->decWeak(id); +} + +void RefBase::forceIncStrong(const void* id) const +{ + weakref_impl* const refs = mRefs; + refs->incWeak(id); + + refs->addStrongRef(id); + const int32_t c = android_atomic_inc(&refs->mStrong); + ALOG_ASSERT(c >= 0, "forceIncStrong called on %p after ref count underflow", + refs); +#if PRINT_REFS + ALOGD("forceIncStrong of %p from %p: cnt=%d\n", this, id, c); +#endif + + switch (c) { + case INITIAL_STRONG_VALUE: + android_atomic_add(-INITIAL_STRONG_VALUE, &refs->mStrong); + // fall through... + case 0: + refs->mBase->onFirstRef(); + } +} + +int32_t RefBase::getStrongCount() const +{ + return mRefs->mStrong; +} + +RefBase* RefBase::weakref_type::refBase() const +{ + return static_cast<const weakref_impl*>(this)->mBase; +} + +void RefBase::weakref_type::incWeak(const void* id) +{ + weakref_impl* const impl = static_cast<weakref_impl*>(this); + impl->addWeakRef(id); + const int32_t c = android_atomic_inc(&impl->mWeak); + ALOG_ASSERT(c >= 0, "incWeak called on %p after last weak ref", this); +} + + +void RefBase::weakref_type::decWeak(const void* id) +{ + weakref_impl* const impl = static_cast<weakref_impl*>(this); + impl->removeWeakRef(id); + const int32_t c = android_atomic_dec(&impl->mWeak); + ALOG_ASSERT(c >= 1, "decWeak called on %p too many times", this); + if (c != 1) return; + + if ((impl->mFlags&OBJECT_LIFETIME_WEAK) == OBJECT_LIFETIME_STRONG) { + // This is the regular lifetime case. The object is destroyed + // when the last strong reference goes away. Since weakref_impl + // outlive the object, it is not destroyed in the dtor, and + // we'll have to do it here. + if (impl->mStrong == INITIAL_STRONG_VALUE) { + // Special case: we never had a strong reference, so we need to + // destroy the object now. + delete impl->mBase; + } else { + // ALOGV("Freeing refs %p of old RefBase %p\n", this, impl->mBase); + delete impl; + } + } else { + // less common case: lifetime is OBJECT_LIFETIME_{WEAK|FOREVER} + impl->mBase->onLastWeakRef(id); + if ((impl->mFlags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_WEAK) { + // this is the OBJECT_LIFETIME_WEAK case. The last weak-reference + // is gone, we can destroy the object. + delete impl->mBase; + } + } +} + +bool RefBase::weakref_type::attemptIncStrong(const void* id) +{ + incWeak(id); + + weakref_impl* const impl = static_cast<weakref_impl*>(this); + int32_t curCount = impl->mStrong; + + ALOG_ASSERT(curCount >= 0, + "attemptIncStrong called on %p after underflow", this); + + while (curCount > 0 && curCount != INITIAL_STRONG_VALUE) { + // we're in the easy/common case of promoting a weak-reference + // from an existing strong reference. + if (android_atomic_cmpxchg(curCount, curCount+1, &impl->mStrong) == 0) { + break; + } + // the strong count has changed on us, we need to re-assert our + // situation. + curCount = impl->mStrong; + } + + if (curCount <= 0 || curCount == INITIAL_STRONG_VALUE) { + // we're now in the harder case of either: + // - there never was a strong reference on us + // - or, all strong references have been released + if ((impl->mFlags&OBJECT_LIFETIME_WEAK) == OBJECT_LIFETIME_STRONG) { + // this object has a "normal" life-time, i.e.: it gets destroyed + // when the last strong reference goes away + if (curCount <= 0) { + // the last strong-reference got released, the object cannot + // be revived. + decWeak(id); + return false; + } + + // here, curCount == INITIAL_STRONG_VALUE, which means + // there never was a strong-reference, so we can try to + // promote this object; we need to do that atomically. + while (curCount > 0) { + if (android_atomic_cmpxchg(curCount, curCount + 1, + &impl->mStrong) == 0) { + break; + } + // the strong count has changed on us, we need to re-assert our + // situation (e.g.: another thread has inc/decStrong'ed us) + curCount = impl->mStrong; + } + + if (curCount <= 0) { + // promote() failed, some other thread destroyed us in the + // meantime (i.e.: strong count reached zero). + decWeak(id); + return false; + } + } else { + // this object has an "extended" life-time, i.e.: it can be + // revived from a weak-reference only. + // Ask the object's implementation if it agrees to be revived + if (!impl->mBase->onIncStrongAttempted(FIRST_INC_STRONG, id)) { + // it didn't so give-up. + decWeak(id); + return false; + } + // grab a strong-reference, which is always safe due to the + // extended life-time. + curCount = android_atomic_inc(&impl->mStrong); + } + + // If the strong reference count has already been incremented by + // someone else, the implementor of onIncStrongAttempted() is holding + // an unneeded reference. So call onLastStrongRef() here to remove it. + // (No, this is not pretty.) Note that we MUST NOT do this if we + // are in fact acquiring the first reference. + if (curCount > 0 && curCount < INITIAL_STRONG_VALUE) { + impl->mBase->onLastStrongRef(id); + } + } + + impl->addStrongRef(id); + +#if PRINT_REFS + ALOGD("attemptIncStrong of %p from %p: cnt=%d\n", this, id, curCount); +#endif + + // now we need to fix-up the count if it was INITIAL_STRONG_VALUE + // this must be done safely, i.e.: handle the case where several threads + // were here in attemptIncStrong(). + curCount = impl->mStrong; + while (curCount >= INITIAL_STRONG_VALUE) { + ALOG_ASSERT(curCount > INITIAL_STRONG_VALUE, + "attemptIncStrong in %p underflowed to INITIAL_STRONG_VALUE", + this); + if (android_atomic_cmpxchg(curCount, curCount-INITIAL_STRONG_VALUE, + &impl->mStrong) == 0) { + break; + } + // the strong-count changed on us, we need to re-assert the situation, + // for e.g.: it's possible the fix-up happened in another thread. + curCount = impl->mStrong; + } + + return true; +} + +bool RefBase::weakref_type::attemptIncWeak(const void* id) +{ + weakref_impl* const impl = static_cast<weakref_impl*>(this); + + int32_t curCount = impl->mWeak; + ALOG_ASSERT(curCount >= 0, "attemptIncWeak called on %p after underflow", + this); + while (curCount > 0) { + if (android_atomic_cmpxchg(curCount, curCount+1, &impl->mWeak) == 0) { + break; + } + curCount = impl->mWeak; + } + + if (curCount > 0) { + impl->addWeakRef(id); + } + + return curCount > 0; +} + +int32_t RefBase::weakref_type::getWeakCount() const +{ + return static_cast<const weakref_impl*>(this)->mWeak; +} + +void RefBase::weakref_type::printRefs() const +{ + static_cast<const weakref_impl*>(this)->printRefs(); +} + +void RefBase::weakref_type::trackMe(bool enable, bool retain) +{ + static_cast<weakref_impl*>(this)->trackMe(enable, retain); +} + +RefBase::weakref_type* RefBase::createWeak(const void* id) const +{ + mRefs->incWeak(id); + return mRefs; +} + +RefBase::weakref_type* RefBase::getWeakRefs() const +{ + return mRefs; +} + +RefBase::RefBase() + : mRefs(new weakref_impl(this)) +{ +} + +RefBase::~RefBase() +{ + if (mRefs->mStrong == INITIAL_STRONG_VALUE) { + // we never acquired a strong (and/or weak) reference on this object. + delete mRefs; + } else { + // life-time of this object is extended to WEAK or FOREVER, in + // which case weakref_impl doesn't out-live the object and we + // can free it now. + if ((mRefs->mFlags & OBJECT_LIFETIME_MASK) != OBJECT_LIFETIME_STRONG) { + // It's possible that the weak count is not 0 if the object + // re-acquired a weak reference in its destructor + if (mRefs->mWeak == 0) { + delete mRefs; + } + } + } + // for debugging purposes, clear this. + const_cast<weakref_impl*&>(mRefs) = NULL; +} + +void RefBase::extendObjectLifetime(int32_t mode) +{ + android_atomic_or(mode, &mRefs->mFlags); +} + +void RefBase::onFirstRef() +{ +} + +void RefBase::onLastStrongRef(const void* /*id*/) +{ +} + +bool RefBase::onIncStrongAttempted(uint32_t flags, const void* id) +{ + return (flags&FIRST_INC_STRONG) ? true : false; +} + +void RefBase::onLastWeakRef(const void* /*id*/) +{ +} + +// --------------------------------------------------------------------------- + +void RefBase::renameRefs(size_t n, const ReferenceRenamer& renamer) { +#if DEBUG_REFS + for (size_t i=0 ; i<n ; i++) { + renamer(i); + } +#endif +} + +void RefBase::renameRefId(weakref_type* ref, + const void* old_id, const void* new_id) { + weakref_impl* const impl = static_cast<weakref_impl*>(ref); + impl->renameStrongRefId(old_id, new_id); + impl->renameWeakRefId(old_id, new_id); +} + +void RefBase::renameRefId(RefBase* ref, + const void* old_id, const void* new_id) { + ref->mRefs->renameStrongRefId(old_id, new_id); + ref->mRefs->renameWeakRefId(old_id, new_id); +} + +}; // namespace android diff --git a/libutils/SharedBuffer.cpp b/libutils/SharedBuffer.cpp new file mode 100644 index 0000000..3555fb7 --- /dev/null +++ b/libutils/SharedBuffer.cpp @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2005 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdlib.h> +#include <string.h> + +#include <utils/SharedBuffer.h> +#include <utils/Atomic.h> + +// --------------------------------------------------------------------------- + +namespace android { + +SharedBuffer* SharedBuffer::alloc(size_t size) +{ + SharedBuffer* sb = static_cast<SharedBuffer *>(malloc(sizeof(SharedBuffer) + size)); + if (sb) { + sb->mRefs = 1; + sb->mSize = size; + } + return sb; +} + + +ssize_t SharedBuffer::dealloc(const SharedBuffer* released) +{ + if (released->mRefs != 0) return -1; // XXX: invalid operation + free(const_cast<SharedBuffer*>(released)); + return 0; +} + +SharedBuffer* SharedBuffer::edit() const +{ + if (onlyOwner()) { + return const_cast<SharedBuffer*>(this); + } + SharedBuffer* sb = alloc(mSize); + if (sb) { + memcpy(sb->data(), data(), size()); + release(); + } + return sb; +} + +SharedBuffer* SharedBuffer::editResize(size_t newSize) const +{ + if (onlyOwner()) { + SharedBuffer* buf = const_cast<SharedBuffer*>(this); + if (buf->mSize == newSize) return buf; + buf = (SharedBuffer*)realloc(buf, sizeof(SharedBuffer) + newSize); + if (buf != NULL) { + buf->mSize = newSize; + return buf; + } + } + SharedBuffer* sb = alloc(newSize); + if (sb) { + const size_t mySize = mSize; + memcpy(sb->data(), data(), newSize < mySize ? newSize : mySize); + release(); + } + return sb; +} + +SharedBuffer* SharedBuffer::attemptEdit() const +{ + if (onlyOwner()) { + return const_cast<SharedBuffer*>(this); + } + return 0; +} + +SharedBuffer* SharedBuffer::reset(size_t new_size) const +{ + // cheap-o-reset. + SharedBuffer* sb = alloc(new_size); + if (sb) { + release(); + } + return sb; +} + +void SharedBuffer::acquire() const { + android_atomic_inc(&mRefs); +} + +int32_t SharedBuffer::release(uint32_t flags) const +{ + int32_t prev = 1; + if (onlyOwner() || ((prev = android_atomic_dec(&mRefs)) == 1)) { + mRefs = 0; + if ((flags & eKeepStorage) == 0) { + free(const_cast<SharedBuffer*>(this)); + } + } + return prev; +} + + +}; // namespace android diff --git a/libutils/Static.cpp b/libutils/Static.cpp new file mode 100644 index 0000000..3ed07a1 --- /dev/null +++ b/libutils/Static.cpp @@ -0,0 +1,49 @@ +/* + * 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. + */ + +// All static variables go here, to control initialization and +// destruction order in the library. + +namespace android { + +// For String8.cpp +extern void initialize_string8(); +extern void terminate_string8(); + +// For String16.cpp +extern void initialize_string16(); +extern void terminate_string16(); + +class LibUtilsFirstStatics +{ +public: + LibUtilsFirstStatics() + { + initialize_string8(); + initialize_string16(); + } + + ~LibUtilsFirstStatics() + { + terminate_string16(); + terminate_string8(); + } +}; + +static LibUtilsFirstStatics gFirstStatics; +int gDarwinCantLoadAllObjects = 1; + +} // namespace android diff --git a/libutils/StopWatch.cpp b/libutils/StopWatch.cpp new file mode 100644 index 0000000..b1708d6 --- /dev/null +++ b/libutils/StopWatch.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2005 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "StopWatch" + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +/* for PRId64 */ +#define __STDC_FORMAT_MACROS 1 +#include <inttypes.h> + +#include <utils/Log.h> +#include <utils/Errors.h> +#include <utils/StopWatch.h> + +/*****************************************************************************/ + +namespace android { + + +StopWatch::StopWatch(const char *name, int clock, uint32_t flags) + : mName(name), mClock(clock), mFlags(flags) +{ + reset(); +} + +StopWatch::~StopWatch() +{ + nsecs_t elapsed = elapsedTime(); + const int n = mNumLaps; + ALOGD("StopWatch %s (us): %" PRId64 " ", mName, ns2us(elapsed)); + for (int i=0 ; i<n ; i++) { + const nsecs_t soFar = mLaps[i].soFar; + const nsecs_t thisLap = mLaps[i].thisLap; + ALOGD(" [%d: %" PRId64 ", %" PRId64, i, ns2us(soFar), ns2us(thisLap)); + } +} + +const char* StopWatch::name() const +{ + return mName; +} + +nsecs_t StopWatch::lap() +{ + nsecs_t elapsed = elapsedTime(); + if (mNumLaps >= 8) { + elapsed = 0; + } else { + const int n = mNumLaps; + mLaps[n].soFar = elapsed; + mLaps[n].thisLap = n ? (elapsed - mLaps[n-1].soFar) : elapsed; + mNumLaps = n+1; + } + return elapsed; +} + +nsecs_t StopWatch::elapsedTime() const +{ + return systemTime(mClock) - mStartTime; +} + +void StopWatch::reset() +{ + mNumLaps = 0; + mStartTime = systemTime(mClock); +} + + +/*****************************************************************************/ + +}; // namespace android + diff --git a/libutils/String16.cpp b/libutils/String16.cpp new file mode 100644 index 0000000..b09b728 --- /dev/null +++ b/libutils/String16.cpp @@ -0,0 +1,422 @@ +/* + * Copyright (C) 2005 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <utils/String16.h> + +#include <utils/Debug.h> +#include <utils/Log.h> +#include <utils/Unicode.h> +#include <utils/String8.h> +#include <utils/threads.h> + +#include <memory.h> +#include <stdio.h> +#include <ctype.h> + + +namespace android { + +static SharedBuffer* gEmptyStringBuf = NULL; +static char16_t* gEmptyString = NULL; + +static inline char16_t* getEmptyString() +{ + gEmptyStringBuf->acquire(); + return gEmptyString; +} + +void initialize_string16() +{ + SharedBuffer* buf = SharedBuffer::alloc(sizeof(char16_t)); + char16_t* str = (char16_t*)buf->data(); + *str = 0; + gEmptyStringBuf = buf; + gEmptyString = str; +} + +void terminate_string16() +{ + SharedBuffer::bufferFromData(gEmptyString)->release(); + gEmptyStringBuf = NULL; + gEmptyString = NULL; +} + +// --------------------------------------------------------------------------- + +static char16_t* allocFromUTF8(const char* u8str, size_t u8len) +{ + if (u8len == 0) return getEmptyString(); + + const uint8_t* u8cur = (const uint8_t*) u8str; + + const ssize_t u16len = utf8_to_utf16_length(u8cur, u8len); + if (u16len < 0) { + return getEmptyString(); + } + + const uint8_t* const u8end = u8cur + u8len; + + SharedBuffer* buf = SharedBuffer::alloc(sizeof(char16_t)*(u16len+1)); + if (buf) { + u8cur = (const uint8_t*) u8str; + char16_t* u16str = (char16_t*)buf->data(); + + utf8_to_utf16(u8cur, u8len, u16str); + + //printf("Created UTF-16 string from UTF-8 \"%s\":", in); + //printHexData(1, str, buf->size(), 16, 1); + //printf("\n"); + + return u16str; + } + + return getEmptyString(); +} + +// --------------------------------------------------------------------------- + +String16::String16() + : mString(getEmptyString()) +{ +} + +String16::String16(StaticLinkage) + : mString(0) +{ + // this constructor is used when we can't rely on the static-initializers + // having run. In this case we always allocate an empty string. It's less + // efficient than using getEmptyString(), but we assume it's uncommon. + + char16_t* data = static_cast<char16_t*>( + SharedBuffer::alloc(sizeof(char16_t))->data()); + data[0] = 0; + mString = data; +} + +String16::String16(const String16& o) + : mString(o.mString) +{ + SharedBuffer::bufferFromData(mString)->acquire(); +} + +String16::String16(const String16& o, size_t len, size_t begin) + : mString(getEmptyString()) +{ + setTo(o, len, begin); +} + +String16::String16(const char16_t* o) +{ + size_t len = strlen16(o); + SharedBuffer* buf = SharedBuffer::alloc((len+1)*sizeof(char16_t)); + ALOG_ASSERT(buf, "Unable to allocate shared buffer"); + if (buf) { + char16_t* str = (char16_t*)buf->data(); + strcpy16(str, o); + mString = str; + return; + } + + mString = getEmptyString(); +} + +String16::String16(const char16_t* o, size_t len) +{ + SharedBuffer* buf = SharedBuffer::alloc((len+1)*sizeof(char16_t)); + ALOG_ASSERT(buf, "Unable to allocate shared buffer"); + if (buf) { + char16_t* str = (char16_t*)buf->data(); + memcpy(str, o, len*sizeof(char16_t)); + str[len] = 0; + mString = str; + return; + } + + mString = getEmptyString(); +} + +String16::String16(const String8& o) + : mString(allocFromUTF8(o.string(), o.size())) +{ +} + +String16::String16(const char* o) + : mString(allocFromUTF8(o, strlen(o))) +{ +} + +String16::String16(const char* o, size_t len) + : mString(allocFromUTF8(o, len)) +{ +} + +String16::~String16() +{ + SharedBuffer::bufferFromData(mString)->release(); +} + +void String16::setTo(const String16& other) +{ + SharedBuffer::bufferFromData(other.mString)->acquire(); + SharedBuffer::bufferFromData(mString)->release(); + mString = other.mString; +} + +status_t String16::setTo(const String16& other, size_t len, size_t begin) +{ + const size_t N = other.size(); + if (begin >= N) { + SharedBuffer::bufferFromData(mString)->release(); + mString = getEmptyString(); + return NO_ERROR; + } + if ((begin+len) > N) len = N-begin; + if (begin == 0 && len == N) { + setTo(other); + return NO_ERROR; + } + + if (&other == this) { + LOG_ALWAYS_FATAL("Not implemented"); + } + + return setTo(other.string()+begin, len); +} + +status_t String16::setTo(const char16_t* other) +{ + return setTo(other, strlen16(other)); +} + +status_t String16::setTo(const char16_t* other, size_t len) +{ + SharedBuffer* buf = SharedBuffer::bufferFromData(mString) + ->editResize((len+1)*sizeof(char16_t)); + if (buf) { + char16_t* str = (char16_t*)buf->data(); + memmove(str, other, len*sizeof(char16_t)); + str[len] = 0; + mString = str; + return NO_ERROR; + } + return NO_MEMORY; +} + +status_t String16::append(const String16& other) +{ + const size_t myLen = size(); + const size_t otherLen = other.size(); + if (myLen == 0) { + setTo(other); + return NO_ERROR; + } else if (otherLen == 0) { + return NO_ERROR; + } + + SharedBuffer* buf = SharedBuffer::bufferFromData(mString) + ->editResize((myLen+otherLen+1)*sizeof(char16_t)); + if (buf) { + char16_t* str = (char16_t*)buf->data(); + memcpy(str+myLen, other, (otherLen+1)*sizeof(char16_t)); + mString = str; + return NO_ERROR; + } + return NO_MEMORY; +} + +status_t String16::append(const char16_t* chrs, size_t otherLen) +{ + const size_t myLen = size(); + if (myLen == 0) { + setTo(chrs, otherLen); + return NO_ERROR; + } else if (otherLen == 0) { + return NO_ERROR; + } + + SharedBuffer* buf = SharedBuffer::bufferFromData(mString) + ->editResize((myLen+otherLen+1)*sizeof(char16_t)); + if (buf) { + char16_t* str = (char16_t*)buf->data(); + memcpy(str+myLen, chrs, otherLen*sizeof(char16_t)); + str[myLen+otherLen] = 0; + mString = str; + return NO_ERROR; + } + return NO_MEMORY; +} + +status_t String16::insert(size_t pos, const char16_t* chrs) +{ + return insert(pos, chrs, strlen16(chrs)); +} + +status_t String16::insert(size_t pos, const char16_t* chrs, size_t len) +{ + const size_t myLen = size(); + if (myLen == 0) { + return setTo(chrs, len); + return NO_ERROR; + } else if (len == 0) { + return NO_ERROR; + } + + if (pos > myLen) pos = myLen; + + #if 0 + printf("Insert in to %s: pos=%d, len=%d, myLen=%d, chrs=%s\n", + String8(*this).string(), pos, + len, myLen, String8(chrs, len).string()); + #endif + + SharedBuffer* buf = SharedBuffer::bufferFromData(mString) + ->editResize((myLen+len+1)*sizeof(char16_t)); + if (buf) { + char16_t* str = (char16_t*)buf->data(); + if (pos < myLen) { + memmove(str+pos+len, str+pos, (myLen-pos)*sizeof(char16_t)); + } + memcpy(str+pos, chrs, len*sizeof(char16_t)); + str[myLen+len] = 0; + mString = str; + #if 0 + printf("Result (%d chrs): %s\n", size(), String8(*this).string()); + #endif + return NO_ERROR; + } + return NO_MEMORY; +} + +ssize_t String16::findFirst(char16_t c) const +{ + const char16_t* str = string(); + const char16_t* p = str; + const char16_t* e = p + size(); + while (p < e) { + if (*p == c) { + return p-str; + } + p++; + } + return -1; +} + +ssize_t String16::findLast(char16_t c) const +{ + const char16_t* str = string(); + const char16_t* p = str; + const char16_t* e = p + size(); + while (p < e) { + e--; + if (*e == c) { + return e-str; + } + } + return -1; +} + +bool String16::startsWith(const String16& prefix) const +{ + const size_t ps = prefix.size(); + if (ps > size()) return false; + return strzcmp16(mString, ps, prefix.string(), ps) == 0; +} + +bool String16::startsWith(const char16_t* prefix) const +{ + const size_t ps = strlen16(prefix); + if (ps > size()) return false; + return strncmp16(mString, prefix, ps) == 0; +} + +status_t String16::makeLower() +{ + const size_t N = size(); + const char16_t* str = string(); + char16_t* edit = NULL; + for (size_t i=0; i<N; i++) { + const char16_t v = str[i]; + if (v >= 'A' && v <= 'Z') { + if (!edit) { + SharedBuffer* buf = SharedBuffer::bufferFromData(mString)->edit(); + if (!buf) { + return NO_MEMORY; + } + edit = (char16_t*)buf->data(); + mString = str = edit; + } + edit[i] = tolower((char)v); + } + } + return NO_ERROR; +} + +status_t String16::replaceAll(char16_t replaceThis, char16_t withThis) +{ + const size_t N = size(); + const char16_t* str = string(); + char16_t* edit = NULL; + for (size_t i=0; i<N; i++) { + if (str[i] == replaceThis) { + if (!edit) { + SharedBuffer* buf = SharedBuffer::bufferFromData(mString)->edit(); + if (!buf) { + return NO_MEMORY; + } + edit = (char16_t*)buf->data(); + mString = str = edit; + } + edit[i] = withThis; + } + } + return NO_ERROR; +} + +status_t String16::remove(size_t len, size_t begin) +{ + const size_t N = size(); + if (begin >= N) { + SharedBuffer::bufferFromData(mString)->release(); + mString = getEmptyString(); + return NO_ERROR; + } + if ((begin+len) > N) len = N-begin; + if (begin == 0 && len == N) { + return NO_ERROR; + } + + if (begin > 0) { + SharedBuffer* buf = SharedBuffer::bufferFromData(mString) + ->editResize((N+1)*sizeof(char16_t)); + if (!buf) { + return NO_MEMORY; + } + char16_t* str = (char16_t*)buf->data(); + memmove(str, str+begin, (N-begin+1)*sizeof(char16_t)); + mString = str; + } + SharedBuffer* buf = SharedBuffer::bufferFromData(mString) + ->editResize((len+1)*sizeof(char16_t)); + if (buf) { + char16_t* str = (char16_t*)buf->data(); + str[len] = 0; + mString = str; + return NO_ERROR; + } + return NO_MEMORY; +} + +}; // namespace android diff --git a/libutils/String8.cpp b/libutils/String8.cpp new file mode 100644 index 0000000..e852d77 --- /dev/null +++ b/libutils/String8.cpp @@ -0,0 +1,640 @@ +/* + * Copyright (C) 2005 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <utils/String8.h> + +#include <utils/Log.h> +#include <utils/Unicode.h> +#include <utils/SharedBuffer.h> +#include <utils/String16.h> +#include <utils/threads.h> + +#include <ctype.h> + +/* + * Functions outside android is below the namespace android, since they use + * functions and constants in android namespace. + */ + +// --------------------------------------------------------------------------- + +namespace android { + +// Separator used by resource paths. This is not platform dependent contrary +// to OS_PATH_SEPARATOR. +#define RES_PATH_SEPARATOR '/' + +static SharedBuffer* gEmptyStringBuf = NULL; +static char* gEmptyString = NULL; + +extern int gDarwinCantLoadAllObjects; +int gDarwinIsReallyAnnoying; + +void initialize_string8(); + +static inline char* getEmptyString() +{ + gEmptyStringBuf->acquire(); + return gEmptyString; +} + +void initialize_string8() +{ + // HACK: This dummy dependency forces linking libutils Static.cpp, + // which is needed to initialize String8/String16 classes. + // These variables are named for Darwin, but are needed elsewhere too, + // including static linking on any platform. + gDarwinIsReallyAnnoying = gDarwinCantLoadAllObjects; + + SharedBuffer* buf = SharedBuffer::alloc(1); + char* str = (char*)buf->data(); + *str = 0; + gEmptyStringBuf = buf; + gEmptyString = str; +} + +void terminate_string8() +{ + SharedBuffer::bufferFromData(gEmptyString)->release(); + gEmptyStringBuf = NULL; + gEmptyString = NULL; +} + +// --------------------------------------------------------------------------- + +static char* allocFromUTF8(const char* in, size_t len) +{ + if (len > 0) { + SharedBuffer* buf = SharedBuffer::alloc(len+1); + ALOG_ASSERT(buf, "Unable to allocate shared buffer"); + if (buf) { + char* str = (char*)buf->data(); + memcpy(str, in, len); + str[len] = 0; + return str; + } + return NULL; + } + + return getEmptyString(); +} + +static char* allocFromUTF16(const char16_t* in, size_t len) +{ + if (len == 0) return getEmptyString(); + + const ssize_t bytes = utf16_to_utf8_length(in, len); + if (bytes < 0) { + return getEmptyString(); + } + + SharedBuffer* buf = SharedBuffer::alloc(bytes+1); + ALOG_ASSERT(buf, "Unable to allocate shared buffer"); + if (!buf) { + return getEmptyString(); + } + + char* str = (char*)buf->data(); + utf16_to_utf8(in, len, str); + return str; +} + +static char* allocFromUTF32(const char32_t* in, size_t len) +{ + if (len == 0) { + return getEmptyString(); + } + + const ssize_t bytes = utf32_to_utf8_length(in, len); + if (bytes < 0) { + return getEmptyString(); + } + + SharedBuffer* buf = SharedBuffer::alloc(bytes+1); + ALOG_ASSERT(buf, "Unable to allocate shared buffer"); + if (!buf) { + return getEmptyString(); + } + + char* str = (char*) buf->data(); + utf32_to_utf8(in, len, str); + + return str; +} + +// --------------------------------------------------------------------------- + +String8::String8() + : mString(getEmptyString()) +{ +} + +String8::String8(StaticLinkage) + : mString(0) +{ + // this constructor is used when we can't rely on the static-initializers + // having run. In this case we always allocate an empty string. It's less + // efficient than using getEmptyString(), but we assume it's uncommon. + + char* data = static_cast<char*>( + SharedBuffer::alloc(sizeof(char))->data()); + data[0] = 0; + mString = data; +} + +String8::String8(const String8& o) + : mString(o.mString) +{ + SharedBuffer::bufferFromData(mString)->acquire(); +} + +String8::String8(const char* o) + : mString(allocFromUTF8(o, strlen(o))) +{ + if (mString == NULL) { + mString = getEmptyString(); + } +} + +String8::String8(const char* o, size_t len) + : mString(allocFromUTF8(o, len)) +{ + if (mString == NULL) { + mString = getEmptyString(); + } +} + +String8::String8(const String16& o) + : mString(allocFromUTF16(o.string(), o.size())) +{ +} + +String8::String8(const char16_t* o) + : mString(allocFromUTF16(o, strlen16(o))) +{ +} + +String8::String8(const char16_t* o, size_t len) + : mString(allocFromUTF16(o, len)) +{ +} + +String8::String8(const char32_t* o) + : mString(allocFromUTF32(o, strlen32(o))) +{ +} + +String8::String8(const char32_t* o, size_t len) + : mString(allocFromUTF32(o, len)) +{ +} + +String8::~String8() +{ + SharedBuffer::bufferFromData(mString)->release(); +} + +String8 String8::format(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + + String8 result(formatV(fmt, args)); + + va_end(args); + return result; +} + +String8 String8::formatV(const char* fmt, va_list args) +{ + String8 result; + result.appendFormatV(fmt, args); + return result; +} + +void String8::clear() { + SharedBuffer::bufferFromData(mString)->release(); + mString = getEmptyString(); +} + +void String8::setTo(const String8& other) +{ + SharedBuffer::bufferFromData(other.mString)->acquire(); + SharedBuffer::bufferFromData(mString)->release(); + mString = other.mString; +} + +status_t String8::setTo(const char* other) +{ + const char *newString = allocFromUTF8(other, strlen(other)); + SharedBuffer::bufferFromData(mString)->release(); + mString = newString; + if (mString) return NO_ERROR; + + mString = getEmptyString(); + return NO_MEMORY; +} + +status_t String8::setTo(const char* other, size_t len) +{ + const char *newString = allocFromUTF8(other, len); + SharedBuffer::bufferFromData(mString)->release(); + mString = newString; + if (mString) return NO_ERROR; + + mString = getEmptyString(); + return NO_MEMORY; +} + +status_t String8::setTo(const char16_t* other, size_t len) +{ + const char *newString = allocFromUTF16(other, len); + SharedBuffer::bufferFromData(mString)->release(); + mString = newString; + if (mString) return NO_ERROR; + + mString = getEmptyString(); + return NO_MEMORY; +} + +status_t String8::setTo(const char32_t* other, size_t len) +{ + const char *newString = allocFromUTF32(other, len); + SharedBuffer::bufferFromData(mString)->release(); + mString = newString; + if (mString) return NO_ERROR; + + mString = getEmptyString(); + return NO_MEMORY; +} + +status_t String8::append(const String8& other) +{ + const size_t otherLen = other.bytes(); + if (bytes() == 0) { + setTo(other); + return NO_ERROR; + } else if (otherLen == 0) { + return NO_ERROR; + } + + return real_append(other.string(), otherLen); +} + +status_t String8::append(const char* other) +{ + return append(other, strlen(other)); +} + +status_t String8::append(const char* other, size_t otherLen) +{ + if (bytes() == 0) { + return setTo(other, otherLen); + } else if (otherLen == 0) { + return NO_ERROR; + } + + return real_append(other, otherLen); +} + +status_t String8::appendFormat(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + + status_t result = appendFormatV(fmt, args); + + va_end(args); + return result; +} + +status_t String8::appendFormatV(const char* fmt, va_list args) +{ + int result = NO_ERROR; + int n = vsnprintf(NULL, 0, fmt, args); + if (n != 0) { + size_t oldLength = length(); + char* buf = lockBuffer(oldLength + n); + if (buf) { + vsnprintf(buf + oldLength, n + 1, fmt, args); + } else { + result = NO_MEMORY; + } + } + return result; +} + +status_t String8::real_append(const char* other, size_t otherLen) +{ + const size_t myLen = bytes(); + + SharedBuffer* buf = SharedBuffer::bufferFromData(mString) + ->editResize(myLen+otherLen+1); + if (buf) { + char* str = (char*)buf->data(); + mString = str; + str += myLen; + memcpy(str, other, otherLen); + str[otherLen] = '\0'; + return NO_ERROR; + } + return NO_MEMORY; +} + +char* String8::lockBuffer(size_t size) +{ + SharedBuffer* buf = SharedBuffer::bufferFromData(mString) + ->editResize(size+1); + if (buf) { + char* str = (char*)buf->data(); + mString = str; + return str; + } + return NULL; +} + +void String8::unlockBuffer() +{ + unlockBuffer(strlen(mString)); +} + +status_t String8::unlockBuffer(size_t size) +{ + if (size != this->size()) { + SharedBuffer* buf = SharedBuffer::bufferFromData(mString) + ->editResize(size+1); + if (! buf) { + return NO_MEMORY; + } + + char* str = (char*)buf->data(); + str[size] = 0; + mString = str; + } + + return NO_ERROR; +} + +ssize_t String8::find(const char* other, size_t start) const +{ + size_t len = size(); + if (start >= len) { + return -1; + } + const char* s = mString+start; + const char* p = strstr(s, other); + return p ? p-mString : -1; +} + +void String8::toLower() +{ + toLower(0, size()); +} + +void String8::toLower(size_t start, size_t length) +{ + const size_t len = size(); + if (start >= len) { + return; + } + if (start+length > len) { + length = len-start; + } + char* buf = lockBuffer(len); + buf += start; + while (length > 0) { + *buf = tolower(*buf); + buf++; + length--; + } + unlockBuffer(len); +} + +void String8::toUpper() +{ + toUpper(0, size()); +} + +void String8::toUpper(size_t start, size_t length) +{ + const size_t len = size(); + if (start >= len) { + return; + } + if (start+length > len) { + length = len-start; + } + char* buf = lockBuffer(len); + buf += start; + while (length > 0) { + *buf = toupper(*buf); + buf++; + length--; + } + unlockBuffer(len); +} + +size_t String8::getUtf32Length() const +{ + return utf8_to_utf32_length(mString, length()); +} + +int32_t String8::getUtf32At(size_t index, size_t *next_index) const +{ + return utf32_from_utf8_at(mString, length(), index, next_index); +} + +void String8::getUtf32(char32_t* dst) const +{ + utf8_to_utf32(mString, length(), dst); +} + +// --------------------------------------------------------------------------- +// Path functions + +void String8::setPathName(const char* name) +{ + setPathName(name, strlen(name)); +} + +void String8::setPathName(const char* name, size_t len) +{ + char* buf = lockBuffer(len); + + memcpy(buf, name, len); + + // remove trailing path separator, if present + if (len > 0 && buf[len-1] == OS_PATH_SEPARATOR) + len--; + + buf[len] = '\0'; + + unlockBuffer(len); +} + +String8 String8::getPathLeaf(void) const +{ + const char* cp; + const char*const buf = mString; + + cp = strrchr(buf, OS_PATH_SEPARATOR); + if (cp == NULL) + return String8(*this); + else + return String8(cp+1); +} + +String8 String8::getPathDir(void) const +{ + const char* cp; + const char*const str = mString; + + cp = strrchr(str, OS_PATH_SEPARATOR); + if (cp == NULL) + return String8(""); + else + return String8(str, cp - str); +} + +String8 String8::walkPath(String8* outRemains) const +{ + const char* cp; + const char*const str = mString; + const char* buf = str; + + cp = strchr(buf, OS_PATH_SEPARATOR); + if (cp == buf) { + // don't include a leading '/'. + buf = buf+1; + cp = strchr(buf, OS_PATH_SEPARATOR); + } + + if (cp == NULL) { + String8 res = buf != str ? String8(buf) : *this; + if (outRemains) *outRemains = String8(""); + return res; + } + + String8 res(buf, cp-buf); + if (outRemains) *outRemains = String8(cp+1); + return res; +} + +/* + * Helper function for finding the start of an extension in a pathname. + * + * Returns a pointer inside mString, or NULL if no extension was found. + */ +char* String8::find_extension(void) const +{ + const char* lastSlash; + const char* lastDot; + int extLen; + const char* const str = mString; + + // only look at the filename + lastSlash = strrchr(str, OS_PATH_SEPARATOR); + if (lastSlash == NULL) + lastSlash = str; + else + lastSlash++; + + // find the last dot + lastDot = strrchr(lastSlash, '.'); + if (lastDot == NULL) + return NULL; + + // looks good, ship it + return const_cast<char*>(lastDot); +} + +String8 String8::getPathExtension(void) const +{ + char* ext; + + ext = find_extension(); + if (ext != NULL) + return String8(ext); + else + return String8(""); +} + +String8 String8::getBasePath(void) const +{ + char* ext; + const char* const str = mString; + + ext = find_extension(); + if (ext == NULL) + return String8(*this); + else + return String8(str, ext - str); +} + +String8& String8::appendPath(const char* name) +{ + // TODO: The test below will fail for Win32 paths. Fix later or ignore. + if (name[0] != OS_PATH_SEPARATOR) { + if (*name == '\0') { + // nothing to do + return *this; + } + + size_t len = length(); + if (len == 0) { + // no existing filename, just use the new one + setPathName(name); + return *this; + } + + // make room for oldPath + '/' + newPath + int newlen = strlen(name); + + char* buf = lockBuffer(len+1+newlen); + + // insert a '/' if needed + if (buf[len-1] != OS_PATH_SEPARATOR) + buf[len++] = OS_PATH_SEPARATOR; + + memcpy(buf+len, name, newlen+1); + len += newlen; + + unlockBuffer(len); + + return *this; + } else { + setPathName(name); + return *this; + } +} + +String8& String8::convertToResPath() +{ +#if OS_PATH_SEPARATOR != RES_PATH_SEPARATOR + size_t len = length(); + if (len > 0) { + char * buf = lockBuffer(len); + for (char * end = buf + len; buf < end; ++buf) { + if (*buf == OS_PATH_SEPARATOR) + *buf = RES_PATH_SEPARATOR; + } + unlockBuffer(len); + } +#endif + return *this; +} + +}; // namespace android diff --git a/libutils/SystemClock.cpp b/libutils/SystemClock.cpp new file mode 100644 index 0000000..4b74889 --- /dev/null +++ b/libutils/SystemClock.cpp @@ -0,0 +1,155 @@ +/* + * 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. + */ + + +/* + * System clock functions. + */ + +#ifdef HAVE_ANDROID_OS +#include <linux/ioctl.h> +#include <linux/rtc.h> +#include <utils/Atomic.h> +#include <linux/android_alarm.h> +#endif + +#include <sys/time.h> +#include <limits.h> +#include <fcntl.h> +#include <errno.h> +#include <string.h> + +#include <utils/SystemClock.h> +#include <utils/Timers.h> + +#define LOG_TAG "SystemClock" +#include <utils/Log.h> + +namespace android { + +/* + * native public static long uptimeMillis(); + */ +int64_t uptimeMillis() +{ + int64_t when = systemTime(SYSTEM_TIME_MONOTONIC); + return (int64_t) nanoseconds_to_milliseconds(when); +} + +/* + * native public static long elapsedRealtime(); + */ +int64_t elapsedRealtime() +{ + return nanoseconds_to_milliseconds(elapsedRealtimeNano()); +} + +#define METHOD_CLOCK_GETTIME 0 +#define METHOD_IOCTL 1 +#define METHOD_SYSTEMTIME 2 + +static const char *gettime_method_names[] = { + "clock_gettime", + "ioctl", + "systemTime", +}; + +static inline void checkTimeStamps(int64_t timestamp, + int64_t volatile *prevTimestampPtr, + int volatile *prevMethodPtr, + int curMethod) +{ + /* + * Disable the check for SDK since the prebuilt toolchain doesn't contain + * gettid, and int64_t is different on the ARM platform + * (ie long vs long long). + */ +#ifdef ARCH_ARM + int64_t prevTimestamp = *prevTimestampPtr; + int prevMethod = *prevMethodPtr; + + if (timestamp < prevTimestamp) { + ALOGW("time going backwards: prev %lld(%s) vs now %lld(%s), tid=%d", + prevTimestamp, gettime_method_names[prevMethod], + timestamp, gettime_method_names[curMethod], + gettid()); + } + // NOTE - not atomic and may generate spurious warnings if the 64-bit + // write is interrupted or not observed as a whole. + *prevTimestampPtr = timestamp; + *prevMethodPtr = curMethod; +#endif +} + +/* + * native public static long elapsedRealtimeNano(); + */ +int64_t elapsedRealtimeNano() +{ +#ifdef HAVE_ANDROID_OS + struct timespec ts; + int result; + int64_t timestamp; + static volatile int64_t prevTimestamp; + static volatile int prevMethod; + +#if 0 + /* + * b/7100774 + * clock_gettime appears to have clock skews and can sometimes return + * backwards values. Disable its use until we find out what's wrong. + */ + result = clock_gettime(CLOCK_BOOTTIME, &ts); + if (result == 0) { + timestamp = seconds_to_nanoseconds(ts.tv_sec) + ts.tv_nsec; + checkTimeStamps(timestamp, &prevTimestamp, &prevMethod, + METHOD_CLOCK_GETTIME); + return timestamp; + } +#endif + + // CLOCK_BOOTTIME doesn't exist, fallback to /dev/alarm + static int s_fd = -1; + + if (s_fd == -1) { + int fd = open("/dev/alarm", O_RDONLY); + if (android_atomic_cmpxchg(-1, fd, &s_fd)) { + close(fd); + } + } + + result = ioctl(s_fd, + ANDROID_ALARM_GET_TIME(ANDROID_ALARM_ELAPSED_REALTIME), &ts); + + if (result == 0) { + timestamp = seconds_to_nanoseconds(ts.tv_sec) + ts.tv_nsec; + checkTimeStamps(timestamp, &prevTimestamp, &prevMethod, METHOD_IOCTL); + return timestamp; + } + + // XXX: there was an error, probably because the driver didn't + // exist ... this should return + // a real error, like an exception! + timestamp = systemTime(SYSTEM_TIME_MONOTONIC); + checkTimeStamps(timestamp, &prevTimestamp, &prevMethod, + METHOD_SYSTEMTIME); + return timestamp; +#else + return systemTime(SYSTEM_TIME_MONOTONIC); +#endif +} + +}; // namespace android diff --git a/libutils/Threads.cpp b/libutils/Threads.cpp new file mode 100644 index 0000000..ff74914 --- /dev/null +++ b/libutils/Threads.cpp @@ -0,0 +1,878 @@ +/* + * 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. + */ + +// #define LOG_NDEBUG 0 +#define LOG_TAG "libutils.threads" + +#include <utils/threads.h> +#include <utils/Log.h> + +#include <cutils/sched_policy.h> + +#include <stdio.h> +#include <stdlib.h> +#include <memory.h> +#include <errno.h> +#include <assert.h> +#include <unistd.h> + +#if defined(HAVE_PTHREADS) +# include <pthread.h> +# include <sched.h> +# include <sys/resource.h> +#ifdef HAVE_ANDROID_OS +# include <bionic_pthread.h> +#endif +#elif defined(HAVE_WIN32_THREADS) +# include <windows.h> +# include <stdint.h> +# include <process.h> +# define HAVE_CREATETHREAD // Cygwin, vs. HAVE__BEGINTHREADEX for MinGW +#endif + +#if defined(HAVE_PRCTL) +#include <sys/prctl.h> +#endif + +/* + * =========================================================================== + * Thread wrappers + * =========================================================================== + */ + +using namespace android; + +// ---------------------------------------------------------------------------- +#if defined(HAVE_PTHREADS) +// ---------------------------------------------------------------------------- + +/* + * Create and run a new thread. + * + * We create it "detached", so it cleans up after itself. + */ + +typedef void* (*android_pthread_entry)(void*); + +struct thread_data_t { + thread_func_t entryFunction; + void* userData; + int priority; + char * threadName; + + // we use this trampoline when we need to set the priority with + // nice/setpriority, and name with prctl. + static int trampoline(const thread_data_t* t) { + thread_func_t f = t->entryFunction; + void* u = t->userData; + int prio = t->priority; + char * name = t->threadName; + delete t; + setpriority(PRIO_PROCESS, 0, prio); + if (prio >= ANDROID_PRIORITY_BACKGROUND) { + set_sched_policy(0, SP_BACKGROUND); + } else { + set_sched_policy(0, SP_FOREGROUND); + } + + if (name) { + androidSetThreadName(name); + free(name); + } + return f(u); + } +}; + +void androidSetThreadName(const char* name) { +#if defined(HAVE_PRCTL) + // Mac OS doesn't have this, and we build libutil for the host too + int hasAt = 0; + int hasDot = 0; + const char *s = name; + while (*s) { + if (*s == '.') hasDot = 1; + else if (*s == '@') hasAt = 1; + s++; + } + int len = s - name; + if (len < 15 || hasAt || !hasDot) { + s = name; + } else { + s = name + len - 15; + } + prctl(PR_SET_NAME, (unsigned long) s, 0, 0, 0); +#endif +} + +int androidCreateRawThreadEtc(android_thread_func_t entryFunction, + void *userData, + const char* threadName, + int32_t threadPriority, + size_t threadStackSize, + android_thread_id_t *threadId) +{ + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + +#ifdef HAVE_ANDROID_OS /* valgrind is rejecting RT-priority create reqs */ + if (threadPriority != PRIORITY_DEFAULT || threadName != NULL) { + // Now that the pthread_t has a method to find the associated + // android_thread_id_t (pid) from pthread_t, it would be possible to avoid + // this trampoline in some cases as the parent could set the properties + // for the child. However, there would be a race condition because the + // child becomes ready immediately, and it doesn't work for the name. + // prctl(PR_SET_NAME) only works for self; prctl(PR_SET_THREAD_NAME) was + // proposed but not yet accepted. + thread_data_t* t = new thread_data_t; + t->priority = threadPriority; + t->threadName = threadName ? strdup(threadName) : NULL; + t->entryFunction = entryFunction; + t->userData = userData; + entryFunction = (android_thread_func_t)&thread_data_t::trampoline; + userData = t; + } +#endif + + if (threadStackSize) { + pthread_attr_setstacksize(&attr, threadStackSize); + } + + errno = 0; + pthread_t thread; + int result = pthread_create(&thread, &attr, + (android_pthread_entry)entryFunction, userData); + pthread_attr_destroy(&attr); + if (result != 0) { + ALOGE("androidCreateRawThreadEtc failed (entry=%p, res=%d, errno=%d)\n" + "(android threadPriority=%d)", + entryFunction, result, errno, threadPriority); + return 0; + } + + // Note that *threadID is directly available to the parent only, as it is + // assigned after the child starts. Use memory barrier / lock if the child + // or other threads also need access. + if (threadId != NULL) { + *threadId = (android_thread_id_t)thread; // XXX: this is not portable + } + return 1; +} + +#ifdef HAVE_ANDROID_OS +static pthread_t android_thread_id_t_to_pthread(android_thread_id_t thread) +{ + return (pthread_t) thread; +} +#endif + +android_thread_id_t androidGetThreadId() +{ + return (android_thread_id_t)pthread_self(); +} + +// ---------------------------------------------------------------------------- +#elif defined(HAVE_WIN32_THREADS) +// ---------------------------------------------------------------------------- + +/* + * Trampoline to make us __stdcall-compliant. + * + * We're expected to delete "vDetails" when we're done. + */ +struct threadDetails { + int (*func)(void*); + void* arg; +}; +static __stdcall unsigned int threadIntermediary(void* vDetails) +{ + struct threadDetails* pDetails = (struct threadDetails*) vDetails; + int result; + + result = (*(pDetails->func))(pDetails->arg); + + delete pDetails; + + ALOG(LOG_VERBOSE, "thread", "thread exiting\n"); + return (unsigned int) result; +} + +/* + * Create and run a new thread. + */ +static bool doCreateThread(android_thread_func_t fn, void* arg, android_thread_id_t *id) +{ + HANDLE hThread; + struct threadDetails* pDetails = new threadDetails; // must be on heap + unsigned int thrdaddr; + + pDetails->func = fn; + pDetails->arg = arg; + +#if defined(HAVE__BEGINTHREADEX) + hThread = (HANDLE) _beginthreadex(NULL, 0, threadIntermediary, pDetails, 0, + &thrdaddr); + if (hThread == 0) +#elif defined(HAVE_CREATETHREAD) + hThread = CreateThread(NULL, 0, + (LPTHREAD_START_ROUTINE) threadIntermediary, + (void*) pDetails, 0, (DWORD*) &thrdaddr); + if (hThread == NULL) +#endif + { + ALOG(LOG_WARN, "thread", "WARNING: thread create failed\n"); + return false; + } + +#if defined(HAVE_CREATETHREAD) + /* close the management handle */ + CloseHandle(hThread); +#endif + + if (id != NULL) { + *id = (android_thread_id_t)thrdaddr; + } + + return true; +} + +int androidCreateRawThreadEtc(android_thread_func_t fn, + void *userData, + const char* threadName, + int32_t threadPriority, + size_t threadStackSize, + android_thread_id_t *threadId) +{ + return doCreateThread( fn, userData, threadId); +} + +android_thread_id_t androidGetThreadId() +{ + return (android_thread_id_t)GetCurrentThreadId(); +} + +// ---------------------------------------------------------------------------- +#else +#error "Threads not supported" +#endif + +// ---------------------------------------------------------------------------- + +int androidCreateThread(android_thread_func_t fn, void* arg) +{ + return createThreadEtc(fn, arg); +} + +int androidCreateThreadGetID(android_thread_func_t fn, void *arg, android_thread_id_t *id) +{ + return createThreadEtc(fn, arg, "android:unnamed_thread", + PRIORITY_DEFAULT, 0, id); +} + +static android_create_thread_fn gCreateThreadFn = androidCreateRawThreadEtc; + +int androidCreateThreadEtc(android_thread_func_t entryFunction, + void *userData, + const char* threadName, + int32_t threadPriority, + size_t threadStackSize, + android_thread_id_t *threadId) +{ + return gCreateThreadFn(entryFunction, userData, threadName, + threadPriority, threadStackSize, threadId); +} + +void androidSetCreateThreadFunc(android_create_thread_fn func) +{ + gCreateThreadFn = func; +} + +pid_t androidGetTid() +{ +#ifdef HAVE_GETTID + return gettid(); +#else + return getpid(); +#endif +} + +#ifdef HAVE_ANDROID_OS +int androidSetThreadPriority(pid_t tid, int pri) +{ + int rc = 0; + +#if defined(HAVE_PTHREADS) + int lasterr = 0; + + if (pri >= ANDROID_PRIORITY_BACKGROUND) { + rc = set_sched_policy(tid, SP_BACKGROUND); + } else if (getpriority(PRIO_PROCESS, tid) >= ANDROID_PRIORITY_BACKGROUND) { + rc = set_sched_policy(tid, SP_FOREGROUND); + } + + if (rc) { + lasterr = errno; + } + + if (setpriority(PRIO_PROCESS, tid, pri) < 0) { + rc = INVALID_OPERATION; + } else { + errno = lasterr; + } +#endif + + return rc; +} + +int androidGetThreadPriority(pid_t tid) { +#if defined(HAVE_PTHREADS) + return getpriority(PRIO_PROCESS, tid); +#else + return ANDROID_PRIORITY_NORMAL; +#endif +} + +#endif + +namespace android { + +/* + * =========================================================================== + * Mutex class + * =========================================================================== + */ + +#if defined(HAVE_PTHREADS) +// implemented as inlines in threads.h +#elif defined(HAVE_WIN32_THREADS) + +Mutex::Mutex() +{ + HANDLE hMutex; + + assert(sizeof(hMutex) == sizeof(mState)); + + hMutex = CreateMutex(NULL, FALSE, NULL); + mState = (void*) hMutex; +} + +Mutex::Mutex(const char* name) +{ + // XXX: name not used for now + HANDLE hMutex; + + assert(sizeof(hMutex) == sizeof(mState)); + + hMutex = CreateMutex(NULL, FALSE, NULL); + mState = (void*) hMutex; +} + +Mutex::Mutex(int type, const char* name) +{ + // XXX: type and name not used for now + HANDLE hMutex; + + assert(sizeof(hMutex) == sizeof(mState)); + + hMutex = CreateMutex(NULL, FALSE, NULL); + mState = (void*) hMutex; +} + +Mutex::~Mutex() +{ + CloseHandle((HANDLE) mState); +} + +status_t Mutex::lock() +{ + DWORD dwWaitResult; + dwWaitResult = WaitForSingleObject((HANDLE) mState, INFINITE); + return dwWaitResult != WAIT_OBJECT_0 ? -1 : NO_ERROR; +} + +void Mutex::unlock() +{ + if (!ReleaseMutex((HANDLE) mState)) + ALOG(LOG_WARN, "thread", "WARNING: bad result from unlocking mutex\n"); +} + +status_t Mutex::tryLock() +{ + DWORD dwWaitResult; + + dwWaitResult = WaitForSingleObject((HANDLE) mState, 0); + if (dwWaitResult != WAIT_OBJECT_0 && dwWaitResult != WAIT_TIMEOUT) + ALOG(LOG_WARN, "thread", "WARNING: bad result from try-locking mutex\n"); + return (dwWaitResult == WAIT_OBJECT_0) ? 0 : -1; +} + +#else +#error "Somebody forgot to implement threads for this platform." +#endif + + +/* + * =========================================================================== + * Condition class + * =========================================================================== + */ + +#if defined(HAVE_PTHREADS) +// implemented as inlines in threads.h +#elif defined(HAVE_WIN32_THREADS) + +/* + * Windows doesn't have a condition variable solution. It's possible + * to create one, but it's easy to get it wrong. For a discussion, and + * the origin of this implementation, see: + * + * http://www.cs.wustl.edu/~schmidt/win32-cv-1.html + * + * The implementation shown on the page does NOT follow POSIX semantics. + * As an optimization they require acquiring the external mutex before + * calling signal() and broadcast(), whereas POSIX only requires grabbing + * it before calling wait(). The implementation here has been un-optimized + * to have the correct behavior. + */ +typedef struct WinCondition { + // Number of waiting threads. + int waitersCount; + + // Serialize access to waitersCount. + CRITICAL_SECTION waitersCountLock; + + // Semaphore used to queue up threads waiting for the condition to + // become signaled. + HANDLE sema; + + // An auto-reset event used by the broadcast/signal thread to wait + // for all the waiting thread(s) to wake up and be released from + // the semaphore. + HANDLE waitersDone; + + // This mutex wouldn't be necessary if we required that the caller + // lock the external mutex before calling signal() and broadcast(). + // I'm trying to mimic pthread semantics though. + HANDLE internalMutex; + + // Keeps track of whether we were broadcasting or signaling. This + // allows us to optimize the code if we're just signaling. + bool wasBroadcast; + + status_t wait(WinCondition* condState, HANDLE hMutex, nsecs_t* abstime) + { + // Increment the wait count, avoiding race conditions. + EnterCriticalSection(&condState->waitersCountLock); + condState->waitersCount++; + //printf("+++ wait: incr waitersCount to %d (tid=%ld)\n", + // condState->waitersCount, getThreadId()); + LeaveCriticalSection(&condState->waitersCountLock); + + DWORD timeout = INFINITE; + if (abstime) { + nsecs_t reltime = *abstime - systemTime(); + if (reltime < 0) + reltime = 0; + timeout = reltime/1000000; + } + + // Atomically release the external mutex and wait on the semaphore. + DWORD res = + SignalObjectAndWait(hMutex, condState->sema, timeout, FALSE); + + //printf("+++ wait: awake (tid=%ld)\n", getThreadId()); + + // Reacquire lock to avoid race conditions. + EnterCriticalSection(&condState->waitersCountLock); + + // No longer waiting. + condState->waitersCount--; + + // Check to see if we're the last waiter after a broadcast. + bool lastWaiter = (condState->wasBroadcast && condState->waitersCount == 0); + + //printf("+++ wait: lastWaiter=%d (wasBc=%d wc=%d)\n", + // lastWaiter, condState->wasBroadcast, condState->waitersCount); + + LeaveCriticalSection(&condState->waitersCountLock); + + // If we're the last waiter thread during this particular broadcast + // then signal broadcast() that we're all awake. It'll drop the + // internal mutex. + if (lastWaiter) { + // Atomically signal the "waitersDone" event and wait until we + // can acquire the internal mutex. We want to do this in one step + // because it ensures that everybody is in the mutex FIFO before + // any thread has a chance to run. Without it, another thread + // could wake up, do work, and hop back in ahead of us. + SignalObjectAndWait(condState->waitersDone, condState->internalMutex, + INFINITE, FALSE); + } else { + // Grab the internal mutex. + WaitForSingleObject(condState->internalMutex, INFINITE); + } + + // Release the internal and grab the external. + ReleaseMutex(condState->internalMutex); + WaitForSingleObject(hMutex, INFINITE); + + return res == WAIT_OBJECT_0 ? NO_ERROR : -1; + } +} WinCondition; + +/* + * Constructor. Set up the WinCondition stuff. + */ +Condition::Condition() +{ + WinCondition* condState = new WinCondition; + + condState->waitersCount = 0; + condState->wasBroadcast = false; + // semaphore: no security, initial value of 0 + condState->sema = CreateSemaphore(NULL, 0, 0x7fffffff, NULL); + InitializeCriticalSection(&condState->waitersCountLock); + // auto-reset event, not signaled initially + condState->waitersDone = CreateEvent(NULL, FALSE, FALSE, NULL); + // used so we don't have to lock external mutex on signal/broadcast + condState->internalMutex = CreateMutex(NULL, FALSE, NULL); + + mState = condState; +} + +/* + * Destructor. Free Windows resources as well as our allocated storage. + */ +Condition::~Condition() +{ + WinCondition* condState = (WinCondition*) mState; + if (condState != NULL) { + CloseHandle(condState->sema); + CloseHandle(condState->waitersDone); + delete condState; + } +} + + +status_t Condition::wait(Mutex& mutex) +{ + WinCondition* condState = (WinCondition*) mState; + HANDLE hMutex = (HANDLE) mutex.mState; + + return ((WinCondition*)mState)->wait(condState, hMutex, NULL); +} + +status_t Condition::waitRelative(Mutex& mutex, nsecs_t reltime) +{ + WinCondition* condState = (WinCondition*) mState; + HANDLE hMutex = (HANDLE) mutex.mState; + nsecs_t absTime = systemTime()+reltime; + + return ((WinCondition*)mState)->wait(condState, hMutex, &absTime); +} + +/* + * Signal the condition variable, allowing one thread to continue. + */ +void Condition::signal() +{ + WinCondition* condState = (WinCondition*) mState; + + // Lock the internal mutex. This ensures that we don't clash with + // broadcast(). + WaitForSingleObject(condState->internalMutex, INFINITE); + + EnterCriticalSection(&condState->waitersCountLock); + bool haveWaiters = (condState->waitersCount > 0); + LeaveCriticalSection(&condState->waitersCountLock); + + // If no waiters, then this is a no-op. Otherwise, knock the semaphore + // down a notch. + if (haveWaiters) + ReleaseSemaphore(condState->sema, 1, 0); + + // Release internal mutex. + ReleaseMutex(condState->internalMutex); +} + +/* + * Signal the condition variable, allowing all threads to continue. + * + * First we have to wake up all threads waiting on the semaphore, then + * we wait until all of the threads have actually been woken before + * releasing the internal mutex. This ensures that all threads are woken. + */ +void Condition::broadcast() +{ + WinCondition* condState = (WinCondition*) mState; + + // Lock the internal mutex. This keeps the guys we're waking up + // from getting too far. + WaitForSingleObject(condState->internalMutex, INFINITE); + + EnterCriticalSection(&condState->waitersCountLock); + bool haveWaiters = false; + + if (condState->waitersCount > 0) { + haveWaiters = true; + condState->wasBroadcast = true; + } + + if (haveWaiters) { + // Wake up all the waiters. + ReleaseSemaphore(condState->sema, condState->waitersCount, 0); + + LeaveCriticalSection(&condState->waitersCountLock); + + // Wait for all awakened threads to acquire the counting semaphore. + // The last guy who was waiting sets this. + WaitForSingleObject(condState->waitersDone, INFINITE); + + // Reset wasBroadcast. (No crit section needed because nobody + // else can wake up to poke at it.) + condState->wasBroadcast = 0; + } else { + // nothing to do + LeaveCriticalSection(&condState->waitersCountLock); + } + + // Release internal mutex. + ReleaseMutex(condState->internalMutex); +} + +#else +#error "condition variables not supported on this platform" +#endif + +// ---------------------------------------------------------------------------- + +/* + * This is our thread object! + */ + +Thread::Thread(bool canCallJava) + : mCanCallJava(canCallJava), + mThread(thread_id_t(-1)), + mLock("Thread::mLock"), + mStatus(NO_ERROR), + mExitPending(false), mRunning(false) +#ifdef HAVE_ANDROID_OS + , mTid(-1) +#endif +{ +} + +Thread::~Thread() +{ +} + +status_t Thread::readyToRun() +{ + return NO_ERROR; +} + +status_t Thread::run(const char* name, int32_t priority, size_t stack) +{ + Mutex::Autolock _l(mLock); + + if (mRunning) { + // thread already started + return INVALID_OPERATION; + } + + // reset status and exitPending to their default value, so we can + // try again after an error happened (either below, or in readyToRun()) + mStatus = NO_ERROR; + mExitPending = false; + mThread = thread_id_t(-1); + + // hold a strong reference on ourself + mHoldSelf = this; + + mRunning = true; + + bool res; + if (mCanCallJava) { + res = createThreadEtc(_threadLoop, + this, name, priority, stack, &mThread); + } else { + res = androidCreateRawThreadEtc(_threadLoop, + this, name, priority, stack, &mThread); + } + + if (res == false) { + mStatus = UNKNOWN_ERROR; // something happened! + mRunning = false; + mThread = thread_id_t(-1); + mHoldSelf.clear(); // "this" may have gone away after this. + + return UNKNOWN_ERROR; + } + + // Do not refer to mStatus here: The thread is already running (may, in fact + // already have exited with a valid mStatus result). The NO_ERROR indication + // here merely indicates successfully starting the thread and does not + // imply successful termination/execution. + return NO_ERROR; + + // Exiting scope of mLock is a memory barrier and allows new thread to run +} + +int Thread::_threadLoop(void* user) +{ + Thread* const self = static_cast<Thread*>(user); + + sp<Thread> strong(self->mHoldSelf); + wp<Thread> weak(strong); + self->mHoldSelf.clear(); + +#ifdef HAVE_ANDROID_OS + // this is very useful for debugging with gdb + self->mTid = gettid(); +#endif + + bool first = true; + + do { + bool result; + if (first) { + first = false; + self->mStatus = self->readyToRun(); + result = (self->mStatus == NO_ERROR); + + if (result && !self->exitPending()) { + // Binder threads (and maybe others) rely on threadLoop + // running at least once after a successful ::readyToRun() + // (unless, of course, the thread has already been asked to exit + // at that point). + // This is because threads are essentially used like this: + // (new ThreadSubclass())->run(); + // The caller therefore does not retain a strong reference to + // the thread and the thread would simply disappear after the + // successful ::readyToRun() call instead of entering the + // threadLoop at least once. + result = self->threadLoop(); + } + } else { + result = self->threadLoop(); + } + + // establish a scope for mLock + { + Mutex::Autolock _l(self->mLock); + if (result == false || self->mExitPending) { + self->mExitPending = true; + self->mRunning = false; + // clear thread ID so that requestExitAndWait() does not exit if + // called by a new thread using the same thread ID as this one. + self->mThread = thread_id_t(-1); + // note that interested observers blocked in requestExitAndWait are + // awoken by broadcast, but blocked on mLock until break exits scope + self->mThreadExitedCondition.broadcast(); + break; + } + } + + // Release our strong reference, to let a chance to the thread + // to die a peaceful death. + strong.clear(); + // And immediately, re-acquire a strong reference for the next loop + strong = weak.promote(); + } while(strong != 0); + + return 0; +} + +void Thread::requestExit() +{ + Mutex::Autolock _l(mLock); + mExitPending = true; +} + +status_t Thread::requestExitAndWait() +{ + Mutex::Autolock _l(mLock); + if (mThread == getThreadId()) { + ALOGW( + "Thread (this=%p): don't call waitForExit() from this " + "Thread object's thread. It's a guaranteed deadlock!", + this); + + return WOULD_BLOCK; + } + + mExitPending = true; + + while (mRunning == true) { + mThreadExitedCondition.wait(mLock); + } + // This next line is probably not needed any more, but is being left for + // historical reference. Note that each interested party will clear flag. + mExitPending = false; + + return mStatus; +} + +status_t Thread::join() +{ + Mutex::Autolock _l(mLock); + if (mThread == getThreadId()) { + ALOGW( + "Thread (this=%p): don't call join() from this " + "Thread object's thread. It's a guaranteed deadlock!", + this); + + return WOULD_BLOCK; + } + + while (mRunning == true) { + mThreadExitedCondition.wait(mLock); + } + + return mStatus; +} + +bool Thread::isRunning() const { + Mutex::Autolock _l(mLock); + return mRunning; +} + +#ifdef HAVE_ANDROID_OS +pid_t Thread::getTid() const +{ + // mTid is not defined until the child initializes it, and the caller may need it earlier + Mutex::Autolock _l(mLock); + pid_t tid; + if (mRunning) { + pthread_t pthread = android_thread_id_t_to_pthread(mThread); + tid = __pthread_gettid(pthread); + } else { + ALOGW("Thread (this=%p): getTid() is undefined before run()", this); + tid = -1; + } + return tid; +} +#endif + +bool Thread::exitPending() const +{ + Mutex::Autolock _l(mLock); + return mExitPending; +} + + + +}; // namespace android diff --git a/libutils/Timers.cpp b/libutils/Timers.cpp new file mode 100644 index 0000000..5293cd2 --- /dev/null +++ b/libutils/Timers.cpp @@ -0,0 +1,72 @@ +/* + * 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. + */ + +// +// Timer functions. +// +#include <utils/Timers.h> +#include <utils/Log.h> + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/time.h> +#include <time.h> +#include <errno.h> +#include <limits.h> + +#ifdef HAVE_WIN32_THREADS +#include <windows.h> +#endif + +nsecs_t systemTime(int clock) +{ +#if defined(HAVE_POSIX_CLOCKS) + static const clockid_t clocks[] = { + CLOCK_REALTIME, + CLOCK_MONOTONIC, + CLOCK_PROCESS_CPUTIME_ID, + CLOCK_THREAD_CPUTIME_ID, + CLOCK_BOOTTIME + }; + struct timespec t; + t.tv_sec = t.tv_nsec = 0; + clock_gettime(clocks[clock], &t); + return nsecs_t(t.tv_sec)*1000000000LL + t.tv_nsec; +#else + // we don't support the clocks here. + struct timeval t; + t.tv_sec = t.tv_usec = 0; + gettimeofday(&t, NULL); + return nsecs_t(t.tv_sec)*1000000000LL + nsecs_t(t.tv_usec)*1000LL; +#endif +} + +int toMillisecondTimeoutDelay(nsecs_t referenceTime, nsecs_t timeoutTime) +{ + int timeoutDelayMillis; + if (timeoutTime > referenceTime) { + uint64_t timeoutDelay = uint64_t(timeoutTime - referenceTime); + if (timeoutDelay > uint64_t((INT_MAX - 1) * 1000000LL)) { + timeoutDelayMillis = -1; + } else { + timeoutDelayMillis = (timeoutDelay + 999999LL) / 1000000LL; + } + } else { + timeoutDelayMillis = 0; + } + return timeoutDelayMillis; +} diff --git a/libutils/Tokenizer.cpp b/libutils/Tokenizer.cpp new file mode 100644 index 0000000..7067533 --- /dev/null +++ b/libutils/Tokenizer.cpp @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "Tokenizer" + +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <utils/Log.h> +#include <utils/Tokenizer.h> + +// Enables debug output for the tokenizer. +#define DEBUG_TOKENIZER 0 + + +namespace android { + +static inline bool isDelimiter(char ch, const char* delimiters) { + return strchr(delimiters, ch) != NULL; +} + +Tokenizer::Tokenizer(const String8& filename, FileMap* fileMap, char* buffer, + bool ownBuffer, size_t length) : + mFilename(filename), mFileMap(fileMap), + mBuffer(buffer), mOwnBuffer(ownBuffer), mLength(length), + mCurrent(buffer), mLineNumber(1) { +} + +Tokenizer::~Tokenizer() { + if (mFileMap) { + mFileMap->release(); + } + if (mOwnBuffer) { + delete[] mBuffer; + } +} + +status_t Tokenizer::open(const String8& filename, Tokenizer** outTokenizer) { + *outTokenizer = NULL; + + int result = NO_ERROR; + int fd = ::open(filename.string(), O_RDONLY); + if (fd < 0) { + result = -errno; + ALOGE("Error opening file '%s', %s.", filename.string(), strerror(errno)); + } else { + struct stat stat; + if (fstat(fd, &stat)) { + result = -errno; + ALOGE("Error getting size of file '%s', %s.", filename.string(), strerror(errno)); + } else { + size_t length = size_t(stat.st_size); + + FileMap* fileMap = new FileMap(); + bool ownBuffer = false; + char* buffer; + if (fileMap->create(NULL, fd, 0, length, true)) { + fileMap->advise(FileMap::SEQUENTIAL); + buffer = static_cast<char*>(fileMap->getDataPtr()); + } else { + fileMap->release(); + fileMap = NULL; + + // Fall back to reading into a buffer since we can't mmap files in sysfs. + // The length we obtained from stat is wrong too (it will always be 4096) + // so we must trust that read will read the entire file. + buffer = new char[length]; + ownBuffer = true; + ssize_t nrd = read(fd, buffer, length); + if (nrd < 0) { + result = -errno; + ALOGE("Error reading file '%s', %s.", filename.string(), strerror(errno)); + delete[] buffer; + buffer = NULL; + } else { + length = size_t(nrd); + } + } + + if (!result) { + *outTokenizer = new Tokenizer(filename, fileMap, buffer, ownBuffer, length); + } + } + close(fd); + } + return result; +} + +status_t Tokenizer::fromContents(const String8& filename, + const char* contents, Tokenizer** outTokenizer) { + *outTokenizer = new Tokenizer(filename, NULL, + const_cast<char*>(contents), false, strlen(contents)); + return OK; +} + +String8 Tokenizer::getLocation() const { + String8 result; + result.appendFormat("%s:%d", mFilename.string(), mLineNumber); + return result; +} + +String8 Tokenizer::peekRemainderOfLine() const { + const char* end = getEnd(); + const char* eol = mCurrent; + while (eol != end) { + char ch = *eol; + if (ch == '\n') { + break; + } + eol += 1; + } + return String8(mCurrent, eol - mCurrent); +} + +String8 Tokenizer::nextToken(const char* delimiters) { +#if DEBUG_TOKENIZER + ALOGD("nextToken"); +#endif + const char* end = getEnd(); + const char* tokenStart = mCurrent; + while (mCurrent != end) { + char ch = *mCurrent; + if (ch == '\n' || isDelimiter(ch, delimiters)) { + break; + } + mCurrent += 1; + } + return String8(tokenStart, mCurrent - tokenStart); +} + +void Tokenizer::nextLine() { +#if DEBUG_TOKENIZER + ALOGD("nextLine"); +#endif + const char* end = getEnd(); + while (mCurrent != end) { + char ch = *(mCurrent++); + if (ch == '\n') { + mLineNumber += 1; + break; + } + } +} + +void Tokenizer::skipDelimiters(const char* delimiters) { +#if DEBUG_TOKENIZER + ALOGD("skipDelimiters"); +#endif + const char* end = getEnd(); + while (mCurrent != end) { + char ch = *mCurrent; + if (ch == '\n' || !isDelimiter(ch, delimiters)) { + break; + } + mCurrent += 1; + } +} + +} // namespace android diff --git a/libutils/Trace.cpp b/libutils/Trace.cpp new file mode 100644 index 0000000..36fd802 --- /dev/null +++ b/libutils/Trace.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <utils/misc.h> +#include <utils/Trace.h> + +static void traceInit() __attribute__((constructor)); + +static void traceInit() +{ + ::android::add_sysprop_change_callback(atrace_update_tags, 0); +} diff --git a/libutils/Unicode.cpp b/libutils/Unicode.cpp new file mode 100644 index 0000000..a66e3bb --- /dev/null +++ b/libutils/Unicode.cpp @@ -0,0 +1,606 @@ +/* + * Copyright (C) 2005 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <utils/Unicode.h> + +#include <stddef.h> + +#ifdef HAVE_WINSOCK +# undef nhtol +# undef htonl +# undef nhtos +# undef htons + +# ifdef HAVE_LITTLE_ENDIAN +# define ntohl(x) ( ((x) << 24) | (((x) >> 24) & 255) | (((x) << 8) & 0xff0000) | (((x) >> 8) & 0xff00) ) +# define htonl(x) ntohl(x) +# define ntohs(x) ( (((x) << 8) & 0xff00) | (((x) >> 8) & 255) ) +# define htons(x) ntohs(x) +# else +# define ntohl(x) (x) +# define htonl(x) (x) +# define ntohs(x) (x) +# define htons(x) (x) +# endif +#else +# include <netinet/in.h> +#endif + +extern "C" { + +static const char32_t kByteMask = 0x000000BF; +static const char32_t kByteMark = 0x00000080; + +// Surrogates aren't valid for UTF-32 characters, so define some +// constants that will let us screen them out. +static const char32_t kUnicodeSurrogateHighStart = 0x0000D800; +static const char32_t kUnicodeSurrogateHighEnd = 0x0000DBFF; +static const char32_t kUnicodeSurrogateLowStart = 0x0000DC00; +static const char32_t kUnicodeSurrogateLowEnd = 0x0000DFFF; +static const char32_t kUnicodeSurrogateStart = kUnicodeSurrogateHighStart; +static const char32_t kUnicodeSurrogateEnd = kUnicodeSurrogateLowEnd; +static const char32_t kUnicodeMaxCodepoint = 0x0010FFFF; + +// Mask used to set appropriate bits in first byte of UTF-8 sequence, +// indexed by number of bytes in the sequence. +// 0xxxxxxx +// -> (00-7f) 7bit. Bit mask for the first byte is 0x00000000 +// 110yyyyx 10xxxxxx +// -> (c0-df)(80-bf) 11bit. Bit mask is 0x000000C0 +// 1110yyyy 10yxxxxx 10xxxxxx +// -> (e0-ef)(80-bf)(80-bf) 16bit. Bit mask is 0x000000E0 +// 11110yyy 10yyxxxx 10xxxxxx 10xxxxxx +// -> (f0-f7)(80-bf)(80-bf)(80-bf) 21bit. Bit mask is 0x000000F0 +static const char32_t kFirstByteMark[] = { + 0x00000000, 0x00000000, 0x000000C0, 0x000000E0, 0x000000F0 +}; + +// -------------------------------------------------------------------------- +// UTF-32 +// -------------------------------------------------------------------------- + +/** + * Return number of UTF-8 bytes required for the character. If the character + * is invalid, return size of 0. + */ +static inline size_t utf32_codepoint_utf8_length(char32_t srcChar) +{ + // Figure out how many bytes the result will require. + if (srcChar < 0x00000080) { + return 1; + } else if (srcChar < 0x00000800) { + return 2; + } else if (srcChar < 0x00010000) { + if ((srcChar < kUnicodeSurrogateStart) || (srcChar > kUnicodeSurrogateEnd)) { + return 3; + } else { + // Surrogates are invalid UTF-32 characters. + return 0; + } + } + // Max code point for Unicode is 0x0010FFFF. + else if (srcChar <= kUnicodeMaxCodepoint) { + return 4; + } else { + // Invalid UTF-32 character. + return 0; + } +} + +// Write out the source character to <dstP>. + +static inline void utf32_codepoint_to_utf8(uint8_t* dstP, char32_t srcChar, size_t bytes) +{ + dstP += bytes; + switch (bytes) + { /* note: everything falls through. */ + case 4: *--dstP = (uint8_t)((srcChar | kByteMark) & kByteMask); srcChar >>= 6; + case 3: *--dstP = (uint8_t)((srcChar | kByteMark) & kByteMask); srcChar >>= 6; + case 2: *--dstP = (uint8_t)((srcChar | kByteMark) & kByteMask); srcChar >>= 6; + case 1: *--dstP = (uint8_t)(srcChar | kFirstByteMark[bytes]); + } +} + +size_t strlen32(const char32_t *s) +{ + const char32_t *ss = s; + while ( *ss ) + ss++; + return ss-s; +} + +size_t strnlen32(const char32_t *s, size_t maxlen) +{ + const char32_t *ss = s; + while ((maxlen > 0) && *ss) { + ss++; + maxlen--; + } + return ss-s; +} + +static inline int32_t utf32_at_internal(const char* cur, size_t *num_read) +{ + const char first_char = *cur; + if ((first_char & 0x80) == 0) { // ASCII + *num_read = 1; + return *cur; + } + cur++; + char32_t mask, to_ignore_mask; + size_t num_to_read = 0; + char32_t utf32 = first_char; + for (num_to_read = 1, mask = 0x40, to_ignore_mask = 0xFFFFFF80; + (first_char & mask); + num_to_read++, to_ignore_mask |= mask, mask >>= 1) { + // 0x3F == 00111111 + utf32 = (utf32 << 6) + (*cur++ & 0x3F); + } + to_ignore_mask |= mask; + utf32 &= ~(to_ignore_mask << (6 * (num_to_read - 1))); + + *num_read = num_to_read; + return static_cast<int32_t>(utf32); +} + +int32_t utf32_from_utf8_at(const char *src, size_t src_len, size_t index, size_t *next_index) +{ + if (index >= src_len) { + return -1; + } + size_t dummy_index; + if (next_index == NULL) { + next_index = &dummy_index; + } + size_t num_read; + int32_t ret = utf32_at_internal(src + index, &num_read); + if (ret >= 0) { + *next_index = index + num_read; + } + + return ret; +} + +ssize_t utf32_to_utf8_length(const char32_t *src, size_t src_len) +{ + if (src == NULL || src_len == 0) { + return -1; + } + + size_t ret = 0; + const char32_t *end = src + src_len; + while (src < end) { + ret += utf32_codepoint_utf8_length(*src++); + } + return ret; +} + +void utf32_to_utf8(const char32_t* src, size_t src_len, char* dst) +{ + if (src == NULL || src_len == 0 || dst == NULL) { + return; + } + + const char32_t *cur_utf32 = src; + const char32_t *end_utf32 = src + src_len; + char *cur = dst; + while (cur_utf32 < end_utf32) { + size_t len = utf32_codepoint_utf8_length(*cur_utf32); + utf32_codepoint_to_utf8((uint8_t *)cur, *cur_utf32++, len); + cur += len; + } + *cur = '\0'; +} + +// -------------------------------------------------------------------------- +// UTF-16 +// -------------------------------------------------------------------------- + +int strcmp16(const char16_t *s1, const char16_t *s2) +{ + char16_t ch; + int d = 0; + + while ( 1 ) { + d = (int)(ch = *s1++) - (int)*s2++; + if ( d || !ch ) + break; + } + + return d; +} + +int strncmp16(const char16_t *s1, const char16_t *s2, size_t n) +{ + char16_t ch; + int d = 0; + + while ( n-- ) { + d = (int)(ch = *s1++) - (int)*s2++; + if ( d || !ch ) + break; + } + + return d; +} + +char16_t *strcpy16(char16_t *dst, const char16_t *src) +{ + char16_t *q = dst; + const char16_t *p = src; + char16_t ch; + + do { + *q++ = ch = *p++; + } while ( ch ); + + return dst; +} + +size_t strlen16(const char16_t *s) +{ + const char16_t *ss = s; + while ( *ss ) + ss++; + return ss-s; +} + + +char16_t *strncpy16(char16_t *dst, const char16_t *src, size_t n) +{ + char16_t *q = dst; + const char16_t *p = src; + char ch; + + while (n) { + n--; + *q++ = ch = *p++; + if ( !ch ) + break; + } + + *q = 0; + + return dst; +} + +size_t strnlen16(const char16_t *s, size_t maxlen) +{ + const char16_t *ss = s; + + /* Important: the maxlen test must precede the reference through ss; + since the byte beyond the maximum may segfault */ + while ((maxlen > 0) && *ss) { + ss++; + maxlen--; + } + return ss-s; +} + +int strzcmp16(const char16_t *s1, size_t n1, const char16_t *s2, size_t n2) +{ + const char16_t* e1 = s1+n1; + const char16_t* e2 = s2+n2; + + while (s1 < e1 && s2 < e2) { + const int d = (int)*s1++ - (int)*s2++; + if (d) { + return d; + } + } + + return n1 < n2 + ? (0 - (int)*s2) + : (n1 > n2 + ? ((int)*s1 - 0) + : 0); +} + +int strzcmp16_h_n(const char16_t *s1H, size_t n1, const char16_t *s2N, size_t n2) +{ + const char16_t* e1 = s1H+n1; + const char16_t* e2 = s2N+n2; + + while (s1H < e1 && s2N < e2) { + const char16_t c2 = ntohs(*s2N); + const int d = (int)*s1H++ - (int)c2; + s2N++; + if (d) { + return d; + } + } + + return n1 < n2 + ? (0 - (int)ntohs(*s2N)) + : (n1 > n2 + ? ((int)*s1H - 0) + : 0); +} + +void utf16_to_utf8(const char16_t* src, size_t src_len, char* dst) +{ + if (src == NULL || src_len == 0 || dst == NULL) { + return; + } + + const char16_t* cur_utf16 = src; + const char16_t* const end_utf16 = src + src_len; + char *cur = dst; + while (cur_utf16 < end_utf16) { + char32_t utf32; + // surrogate pairs + if ((*cur_utf16 & 0xFC00) == 0xD800) { + utf32 = (*cur_utf16++ - 0xD800) << 10; + utf32 |= *cur_utf16++ - 0xDC00; + utf32 += 0x10000; + } else { + utf32 = (char32_t) *cur_utf16++; + } + const size_t len = utf32_codepoint_utf8_length(utf32); + utf32_codepoint_to_utf8((uint8_t*)cur, utf32, len); + cur += len; + } + *cur = '\0'; +} + +// -------------------------------------------------------------------------- +// UTF-8 +// -------------------------------------------------------------------------- + +ssize_t utf8_length(const char *src) +{ + const char *cur = src; + size_t ret = 0; + while (*cur != '\0') { + const char first_char = *cur++; + if ((first_char & 0x80) == 0) { // ASCII + ret += 1; + continue; + } + // (UTF-8's character must not be like 10xxxxxx, + // but 110xxxxx, 1110xxxx, ... or 1111110x) + if ((first_char & 0x40) == 0) { + return -1; + } + + int32_t mask, to_ignore_mask; + size_t num_to_read = 0; + char32_t utf32 = 0; + for (num_to_read = 1, mask = 0x40, to_ignore_mask = 0x80; + num_to_read < 5 && (first_char & mask); + num_to_read++, to_ignore_mask |= mask, mask >>= 1) { + if ((*cur & 0xC0) != 0x80) { // must be 10xxxxxx + return -1; + } + // 0x3F == 00111111 + utf32 = (utf32 << 6) + (*cur++ & 0x3F); + } + // "first_char" must be (110xxxxx - 11110xxx) + if (num_to_read == 5) { + return -1; + } + to_ignore_mask |= mask; + utf32 |= ((~to_ignore_mask) & first_char) << (6 * (num_to_read - 1)); + if (utf32 > kUnicodeMaxCodepoint) { + return -1; + } + + ret += num_to_read; + } + return ret; +} + +ssize_t utf16_to_utf8_length(const char16_t *src, size_t src_len) +{ + if (src == NULL || src_len == 0) { + return -1; + } + + size_t ret = 0; + const char16_t* const end = src + src_len; + while (src < end) { + if ((*src & 0xFC00) == 0xD800 && (src + 1) < end + && (*++src & 0xFC00) == 0xDC00) { + // surrogate pairs are always 4 bytes. + ret += 4; + src++; + } else { + ret += utf32_codepoint_utf8_length((char32_t) *src++); + } + } + return ret; +} + +/** + * Returns 1-4 based on the number of leading bits. + * + * 1111 -> 4 + * 1110 -> 3 + * 110x -> 2 + * 10xx -> 1 + * 0xxx -> 1 + */ +static inline size_t utf8_codepoint_len(uint8_t ch) +{ + return ((0xe5000000 >> ((ch >> 3) & 0x1e)) & 3) + 1; +} + +static inline void utf8_shift_and_mask(uint32_t* codePoint, const uint8_t byte) +{ + *codePoint <<= 6; + *codePoint |= 0x3F & byte; +} + +size_t utf8_to_utf32_length(const char *src, size_t src_len) +{ + if (src == NULL || src_len == 0) { + return 0; + } + size_t ret = 0; + const char* cur; + const char* end; + size_t num_to_skip; + for (cur = src, end = src + src_len, num_to_skip = 1; + cur < end; + cur += num_to_skip, ret++) { + const char first_char = *cur; + num_to_skip = 1; + if ((first_char & 0x80) == 0) { // ASCII + continue; + } + int32_t mask; + + for (mask = 0x40; (first_char & mask); num_to_skip++, mask >>= 1) { + } + } + return ret; +} + +void utf8_to_utf32(const char* src, size_t src_len, char32_t* dst) +{ + if (src == NULL || src_len == 0 || dst == NULL) { + return; + } + + const char* cur = src; + const char* const end = src + src_len; + char32_t* cur_utf32 = dst; + while (cur < end) { + size_t num_read; + *cur_utf32++ = static_cast<char32_t>(utf32_at_internal(cur, &num_read)); + cur += num_read; + } + *cur_utf32 = 0; +} + +static inline uint32_t utf8_to_utf32_codepoint(const uint8_t *src, size_t length) +{ + uint32_t unicode; + + switch (length) + { + case 1: + return src[0]; + case 2: + unicode = src[0] & 0x1f; + utf8_shift_and_mask(&unicode, src[1]); + return unicode; + case 3: + unicode = src[0] & 0x0f; + utf8_shift_and_mask(&unicode, src[1]); + utf8_shift_and_mask(&unicode, src[2]); + return unicode; + case 4: + unicode = src[0] & 0x07; + utf8_shift_and_mask(&unicode, src[1]); + utf8_shift_and_mask(&unicode, src[2]); + utf8_shift_and_mask(&unicode, src[3]); + return unicode; + default: + return 0xffff; + } + + //printf("Char at %p: len=%d, utf-16=%p\n", src, length, (void*)result); +} + +ssize_t utf8_to_utf16_length(const uint8_t* u8str, size_t u8len) +{ + const uint8_t* const u8end = u8str + u8len; + const uint8_t* u8cur = u8str; + + /* Validate that the UTF-8 is the correct len */ + size_t u16measuredLen = 0; + while (u8cur < u8end) { + u16measuredLen++; + int u8charLen = utf8_codepoint_len(*u8cur); + uint32_t codepoint = utf8_to_utf32_codepoint(u8cur, u8charLen); + if (codepoint > 0xFFFF) u16measuredLen++; // this will be a surrogate pair in utf16 + u8cur += u8charLen; + } + + /** + * Make sure that we ended where we thought we would and the output UTF-16 + * will be exactly how long we were told it would be. + */ + if (u8cur != u8end) { + return -1; + } + + return u16measuredLen; +} + +char16_t* utf8_to_utf16_no_null_terminator(const uint8_t* u8str, size_t u8len, char16_t* u16str) +{ + const uint8_t* const u8end = u8str + u8len; + const uint8_t* u8cur = u8str; + char16_t* u16cur = u16str; + + while (u8cur < u8end) { + size_t u8len = utf8_codepoint_len(*u8cur); + uint32_t codepoint = utf8_to_utf32_codepoint(u8cur, u8len); + + // Convert the UTF32 codepoint to one or more UTF16 codepoints + if (codepoint <= 0xFFFF) { + // Single UTF16 character + *u16cur++ = (char16_t) codepoint; + } else { + // Multiple UTF16 characters with surrogates + codepoint = codepoint - 0x10000; + *u16cur++ = (char16_t) ((codepoint >> 10) + 0xD800); + *u16cur++ = (char16_t) ((codepoint & 0x3FF) + 0xDC00); + } + + u8cur += u8len; + } + return u16cur; +} + +void utf8_to_utf16(const uint8_t* u8str, size_t u8len, char16_t* u16str) { + char16_t* end = utf8_to_utf16_no_null_terminator(u8str, u8len, u16str); + *end = 0; +} + +char16_t* utf8_to_utf16_n(const uint8_t* src, size_t srcLen, char16_t* dst, size_t dstLen) { + const uint8_t* const u8end = src + srcLen; + const uint8_t* u8cur = src; + const uint16_t* const u16end = dst + dstLen; + char16_t* u16cur = dst; + + while (u8cur < u8end && u16cur < u16end) { + size_t u8len = utf8_codepoint_len(*u8cur); + uint32_t codepoint = utf8_to_utf32_codepoint(u8cur, u8len); + + // Convert the UTF32 codepoint to one or more UTF16 codepoints + if (codepoint <= 0xFFFF) { + // Single UTF16 character + *u16cur++ = (char16_t) codepoint; + } else { + // Multiple UTF16 characters with surrogates + codepoint = codepoint - 0x10000; + *u16cur++ = (char16_t) ((codepoint >> 10) + 0xD800); + if (u16cur >= u16end) { + // Ooops... not enough room for this surrogate pair. + return u16cur-1; + } + *u16cur++ = (char16_t) ((codepoint & 0x3FF) + 0xDC00); + } + + u8cur += u8len; + } + return u16cur; +} + +} diff --git a/libutils/VectorImpl.cpp b/libutils/VectorImpl.cpp new file mode 100644 index 0000000..5a79647 --- /dev/null +++ b/libutils/VectorImpl.cpp @@ -0,0 +1,625 @@ +/* + * Copyright (C) 2005 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "Vector" + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +#include <cutils/log.h> + +#include <utils/Errors.h> +#include <utils/SharedBuffer.h> +#include <utils/VectorImpl.h> + +/*****************************************************************************/ + + +namespace android { + +// ---------------------------------------------------------------------------- + +const size_t kMinVectorCapacity = 4; + +static inline size_t max(size_t a, size_t b) { + return a>b ? a : b; +} + +// ---------------------------------------------------------------------------- + +VectorImpl::VectorImpl(size_t itemSize, uint32_t flags) + : mStorage(0), mCount(0), mFlags(flags), mItemSize(itemSize) +{ +} + +VectorImpl::VectorImpl(const VectorImpl& rhs) + : mStorage(rhs.mStorage), mCount(rhs.mCount), + mFlags(rhs.mFlags), mItemSize(rhs.mItemSize) +{ + if (mStorage) { + SharedBuffer::bufferFromData(mStorage)->acquire(); + } +} + +VectorImpl::~VectorImpl() +{ + ALOGW_IF(mCount, + "[%p] subclasses of VectorImpl must call finish_vector()" + " in their destructor. Leaking %d bytes.", + this, (int)(mCount*mItemSize)); + // We can't call _do_destroy() here because the vtable is already gone. +} + +VectorImpl& VectorImpl::operator = (const VectorImpl& rhs) +{ + LOG_ALWAYS_FATAL_IF(mItemSize != rhs.mItemSize, + "Vector<> have different types (this=%p, rhs=%p)", this, &rhs); + if (this != &rhs) { + release_storage(); + if (rhs.mCount) { + mStorage = rhs.mStorage; + mCount = rhs.mCount; + SharedBuffer::bufferFromData(mStorage)->acquire(); + } else { + mStorage = 0; + mCount = 0; + } + } + return *this; +} + +void* VectorImpl::editArrayImpl() +{ + if (mStorage) { + SharedBuffer* sb = SharedBuffer::bufferFromData(mStorage)->attemptEdit(); + if (sb == 0) { + sb = SharedBuffer::alloc(capacity() * mItemSize); + if (sb) { + _do_copy(sb->data(), mStorage, mCount); + release_storage(); + mStorage = sb->data(); + } + } + } + return mStorage; +} + +size_t VectorImpl::capacity() const +{ + if (mStorage) { + return SharedBuffer::bufferFromData(mStorage)->size() / mItemSize; + } + return 0; +} + +ssize_t VectorImpl::insertVectorAt(const VectorImpl& vector, size_t index) +{ + return insertArrayAt(vector.arrayImpl(), index, vector.size()); +} + +ssize_t VectorImpl::appendVector(const VectorImpl& vector) +{ + return insertVectorAt(vector, size()); +} + +ssize_t VectorImpl::insertArrayAt(const void* array, size_t index, size_t length) +{ + if (index > size()) + return BAD_INDEX; + void* where = _grow(index, length); + if (where) { + _do_copy(where, array, length); + } + return where ? index : (ssize_t)NO_MEMORY; +} + +ssize_t VectorImpl::appendArray(const void* array, size_t length) +{ + return insertArrayAt(array, size(), length); +} + +ssize_t VectorImpl::insertAt(size_t index, size_t numItems) +{ + return insertAt(0, index, numItems); +} + +ssize_t VectorImpl::insertAt(const void* item, size_t index, size_t numItems) +{ + if (index > size()) + return BAD_INDEX; + void* where = _grow(index, numItems); + if (where) { + if (item) { + _do_splat(where, item, numItems); + } else { + _do_construct(where, numItems); + } + } + return where ? index : (ssize_t)NO_MEMORY; +} + +static int sortProxy(const void* lhs, const void* rhs, void* func) +{ + return (*(VectorImpl::compar_t)func)(lhs, rhs); +} + +status_t VectorImpl::sort(VectorImpl::compar_t cmp) +{ + return sort(sortProxy, (void*)cmp); +} + +status_t VectorImpl::sort(VectorImpl::compar_r_t cmp, void* state) +{ + // the sort must be stable. we're using insertion sort which + // is well suited for small and already sorted arrays + // for big arrays, it could be better to use mergesort + const ssize_t count = size(); + if (count > 1) { + void* array = const_cast<void*>(arrayImpl()); + void* temp = 0; + ssize_t i = 1; + while (i < count) { + void* item = reinterpret_cast<char*>(array) + mItemSize*(i); + void* curr = reinterpret_cast<char*>(array) + mItemSize*(i-1); + if (cmp(curr, item, state) > 0) { + + if (!temp) { + // we're going to have to modify the array... + array = editArrayImpl(); + if (!array) return NO_MEMORY; + temp = malloc(mItemSize); + if (!temp) return NO_MEMORY; + item = reinterpret_cast<char*>(array) + mItemSize*(i); + curr = reinterpret_cast<char*>(array) + mItemSize*(i-1); + } else { + _do_destroy(temp, 1); + } + + _do_copy(temp, item, 1); + + ssize_t j = i-1; + void* next = reinterpret_cast<char*>(array) + mItemSize*(i); + do { + _do_destroy(next, 1); + _do_copy(next, curr, 1); + next = curr; + --j; + curr = reinterpret_cast<char*>(array) + mItemSize*(j); + } while (j>=0 && (cmp(curr, temp, state) > 0)); + + _do_destroy(next, 1); + _do_copy(next, temp, 1); + } + i++; + } + + if (temp) { + _do_destroy(temp, 1); + free(temp); + } + } + return NO_ERROR; +} + +void VectorImpl::pop() +{ + if (size()) + removeItemsAt(size()-1, 1); +} + +void VectorImpl::push() +{ + push(0); +} + +void VectorImpl::push(const void* item) +{ + insertAt(item, size()); +} + +ssize_t VectorImpl::add() +{ + return add(0); +} + +ssize_t VectorImpl::add(const void* item) +{ + return insertAt(item, size()); +} + +ssize_t VectorImpl::replaceAt(size_t index) +{ + return replaceAt(0, index); +} + +ssize_t VectorImpl::replaceAt(const void* prototype, size_t index) +{ + ALOG_ASSERT(index<size(), + "[%p] replace: index=%d, size=%d", this, (int)index, (int)size()); + + if (index >= size()) { + return BAD_INDEX; + } + + void* item = editItemLocation(index); + if (item != prototype) { + if (item == 0) + return NO_MEMORY; + _do_destroy(item, 1); + if (prototype == 0) { + _do_construct(item, 1); + } else { + _do_copy(item, prototype, 1); + } + } + return ssize_t(index); +} + +ssize_t VectorImpl::removeItemsAt(size_t index, size_t count) +{ + ALOG_ASSERT((index+count)<=size(), + "[%p] remove: index=%d, count=%d, size=%d", + this, (int)index, (int)count, (int)size()); + + if ((index+count) > size()) + return BAD_VALUE; + _shrink(index, count); + return index; +} + +void VectorImpl::finish_vector() +{ + release_storage(); + mStorage = 0; + mCount = 0; +} + +void VectorImpl::clear() +{ + _shrink(0, mCount); +} + +void* VectorImpl::editItemLocation(size_t index) +{ + ALOG_ASSERT(index<capacity(), + "[%p] editItemLocation: index=%d, capacity=%d, count=%d", + this, (int)index, (int)capacity(), (int)mCount); + + if (index < capacity()) { + void* buffer = editArrayImpl(); + if (buffer) { + return reinterpret_cast<char*>(buffer) + index*mItemSize; + } + } + return 0; +} + +const void* VectorImpl::itemLocation(size_t index) const +{ + ALOG_ASSERT(index<capacity(), + "[%p] itemLocation: index=%d, capacity=%d, count=%d", + this, (int)index, (int)capacity(), (int)mCount); + + if (index < capacity()) { + const void* buffer = arrayImpl(); + if (buffer) { + return reinterpret_cast<const char*>(buffer) + index*mItemSize; + } + } + return 0; +} + +ssize_t VectorImpl::setCapacity(size_t new_capacity) +{ + size_t current_capacity = capacity(); + ssize_t amount = new_capacity - size(); + if (amount <= 0) { + // we can't reduce the capacity + return current_capacity; + } + SharedBuffer* sb = SharedBuffer::alloc(new_capacity * mItemSize); + if (sb) { + void* array = sb->data(); + _do_copy(array, mStorage, size()); + release_storage(); + mStorage = const_cast<void*>(array); + } else { + return NO_MEMORY; + } + return new_capacity; +} + +ssize_t VectorImpl::resize(size_t size) { + ssize_t result = NO_ERROR; + if (size > mCount) { + result = insertAt(mCount, size - mCount); + } else if (size < mCount) { + result = removeItemsAt(size, mCount - size); + } + return result < 0 ? result : size; +} + +void VectorImpl::release_storage() +{ + if (mStorage) { + const SharedBuffer* sb = SharedBuffer::bufferFromData(mStorage); + if (sb->release(SharedBuffer::eKeepStorage) == 1) { + _do_destroy(mStorage, mCount); + SharedBuffer::dealloc(sb); + } + } +} + +void* VectorImpl::_grow(size_t where, size_t amount) +{ +// ALOGV("_grow(this=%p, where=%d, amount=%d) count=%d, capacity=%d", +// this, (int)where, (int)amount, (int)mCount, (int)capacity()); + + ALOG_ASSERT(where <= mCount, + "[%p] _grow: where=%d, amount=%d, count=%d", + this, (int)where, (int)amount, (int)mCount); // caller already checked + + const size_t new_size = mCount + amount; + if (capacity() < new_size) { + const size_t new_capacity = max(kMinVectorCapacity, ((new_size*3)+1)/2); +// ALOGV("grow vector %p, new_capacity=%d", this, (int)new_capacity); + if ((mStorage) && + (mCount==where) && + (mFlags & HAS_TRIVIAL_COPY) && + (mFlags & HAS_TRIVIAL_DTOR)) + { + const SharedBuffer* cur_sb = SharedBuffer::bufferFromData(mStorage); + SharedBuffer* sb = cur_sb->editResize(new_capacity * mItemSize); + mStorage = sb->data(); + } else { + SharedBuffer* sb = SharedBuffer::alloc(new_capacity * mItemSize); + if (sb) { + void* array = sb->data(); + if (where != 0) { + _do_copy(array, mStorage, where); + } + if (where != mCount) { + const void* from = reinterpret_cast<const uint8_t *>(mStorage) + where*mItemSize; + void* dest = reinterpret_cast<uint8_t *>(array) + (where+amount)*mItemSize; + _do_copy(dest, from, mCount-where); + } + release_storage(); + mStorage = const_cast<void*>(array); + } + } + } else { + void* array = editArrayImpl(); + if (where != mCount) { + const void* from = reinterpret_cast<const uint8_t *>(array) + where*mItemSize; + void* to = reinterpret_cast<uint8_t *>(array) + (where+amount)*mItemSize; + _do_move_forward(to, from, mCount - where); + } + } + mCount = new_size; + void* free_space = const_cast<void*>(itemLocation(where)); + return free_space; +} + +void VectorImpl::_shrink(size_t where, size_t amount) +{ + if (!mStorage) + return; + +// ALOGV("_shrink(this=%p, where=%d, amount=%d) count=%d, capacity=%d", +// this, (int)where, (int)amount, (int)mCount, (int)capacity()); + + ALOG_ASSERT(where + amount <= mCount, + "[%p] _shrink: where=%d, amount=%d, count=%d", + this, (int)where, (int)amount, (int)mCount); // caller already checked + + const size_t new_size = mCount - amount; + if (new_size*3 < capacity()) { + const size_t new_capacity = max(kMinVectorCapacity, new_size*2); +// ALOGV("shrink vector %p, new_capacity=%d", this, (int)new_capacity); + if ((where == new_size) && + (mFlags & HAS_TRIVIAL_COPY) && + (mFlags & HAS_TRIVIAL_DTOR)) + { + const SharedBuffer* cur_sb = SharedBuffer::bufferFromData(mStorage); + SharedBuffer* sb = cur_sb->editResize(new_capacity * mItemSize); + mStorage = sb->data(); + } else { + SharedBuffer* sb = SharedBuffer::alloc(new_capacity * mItemSize); + if (sb) { + void* array = sb->data(); + if (where != 0) { + _do_copy(array, mStorage, where); + } + if (where != new_size) { + const void* from = reinterpret_cast<const uint8_t *>(mStorage) + (where+amount)*mItemSize; + void* dest = reinterpret_cast<uint8_t *>(array) + where*mItemSize; + _do_copy(dest, from, new_size - where); + } + release_storage(); + mStorage = const_cast<void*>(array); + } + } + } else { + void* array = editArrayImpl(); + void* to = reinterpret_cast<uint8_t *>(array) + where*mItemSize; + _do_destroy(to, amount); + if (where != new_size) { + const void* from = reinterpret_cast<uint8_t *>(array) + (where+amount)*mItemSize; + _do_move_backward(to, from, new_size - where); + } + } + mCount = new_size; +} + +size_t VectorImpl::itemSize() const { + return mItemSize; +} + +void VectorImpl::_do_construct(void* storage, size_t num) const +{ + if (!(mFlags & HAS_TRIVIAL_CTOR)) { + do_construct(storage, num); + } +} + +void VectorImpl::_do_destroy(void* storage, size_t num) const +{ + if (!(mFlags & HAS_TRIVIAL_DTOR)) { + do_destroy(storage, num); + } +} + +void VectorImpl::_do_copy(void* dest, const void* from, size_t num) const +{ + if (!(mFlags & HAS_TRIVIAL_COPY)) { + do_copy(dest, from, num); + } else { + memcpy(dest, from, num*itemSize()); + } +} + +void VectorImpl::_do_splat(void* dest, const void* item, size_t num) const { + do_splat(dest, item, num); +} + +void VectorImpl::_do_move_forward(void* dest, const void* from, size_t num) const { + do_move_forward(dest, from, num); +} + +void VectorImpl::_do_move_backward(void* dest, const void* from, size_t num) const { + do_move_backward(dest, from, num); +} + +/*****************************************************************************/ + +SortedVectorImpl::SortedVectorImpl(size_t itemSize, uint32_t flags) + : VectorImpl(itemSize, flags) +{ +} + +SortedVectorImpl::SortedVectorImpl(const VectorImpl& rhs) +: VectorImpl(rhs) +{ +} + +SortedVectorImpl::~SortedVectorImpl() +{ +} + +SortedVectorImpl& SortedVectorImpl::operator = (const SortedVectorImpl& rhs) +{ + return static_cast<SortedVectorImpl&>( VectorImpl::operator = (static_cast<const VectorImpl&>(rhs)) ); +} + +ssize_t SortedVectorImpl::indexOf(const void* item) const +{ + return _indexOrderOf(item); +} + +size_t SortedVectorImpl::orderOf(const void* item) const +{ + size_t o; + _indexOrderOf(item, &o); + return o; +} + +ssize_t SortedVectorImpl::_indexOrderOf(const void* item, size_t* order) const +{ + // binary search + ssize_t err = NAME_NOT_FOUND; + ssize_t l = 0; + ssize_t h = size()-1; + ssize_t mid; + const void* a = arrayImpl(); + const size_t s = itemSize(); + while (l <= h) { + mid = l + (h - l)/2; + const void* const curr = reinterpret_cast<const char *>(a) + (mid*s); + const int c = do_compare(curr, item); + if (c == 0) { + err = l = mid; + break; + } else if (c < 0) { + l = mid + 1; + } else { + h = mid - 1; + } + } + if (order) *order = l; + return err; +} + +ssize_t SortedVectorImpl::add(const void* item) +{ + size_t order; + ssize_t index = _indexOrderOf(item, &order); + if (index < 0) { + index = VectorImpl::insertAt(item, order, 1); + } else { + index = VectorImpl::replaceAt(item, index); + } + return index; +} + +ssize_t SortedVectorImpl::merge(const VectorImpl& vector) +{ + // naive merge... + if (!vector.isEmpty()) { + const void* buffer = vector.arrayImpl(); + const size_t is = itemSize(); + size_t s = vector.size(); + for (size_t i=0 ; i<s ; i++) { + ssize_t err = add( reinterpret_cast<const char*>(buffer) + i*is ); + if (err<0) { + return err; + } + } + } + return NO_ERROR; +} + +ssize_t SortedVectorImpl::merge(const SortedVectorImpl& vector) +{ + // we've merging a sorted vector... nice! + ssize_t err = NO_ERROR; + if (!vector.isEmpty()) { + // first take care of the case where the vectors are sorted together + if (do_compare(vector.itemLocation(vector.size()-1), arrayImpl()) <= 0) { + err = VectorImpl::insertVectorAt(static_cast<const VectorImpl&>(vector), 0); + } else if (do_compare(vector.arrayImpl(), itemLocation(size()-1)) >= 0) { + err = VectorImpl::appendVector(static_cast<const VectorImpl&>(vector)); + } else { + // this could be made a little better + err = merge(static_cast<const VectorImpl&>(vector)); + } + } + return err; +} + +ssize_t SortedVectorImpl::remove(const void* item) +{ + ssize_t i = indexOf(item); + if (i>=0) { + VectorImpl::removeItemsAt(i, 1); + } + return i; +} + +/*****************************************************************************/ + +}; // namespace android + diff --git a/libutils/misc.cpp b/libutils/misc.cpp new file mode 100644 index 0000000..58eb499 --- /dev/null +++ b/libutils/misc.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2005 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "misc" + +// +// Miscellaneous utility functions. +// +#include <utils/misc.h> +#include <utils/Log.h> + +#include <sys/stat.h> +#include <string.h> +#include <errno.h> +#include <stdio.h> + +#if defined(HAVE_PTHREADS) +# include <pthread.h> +#endif + +#include <utils/Vector.h> + +using namespace android; + +namespace android { + +struct sysprop_change_callback_info { + sysprop_change_callback callback; + int priority; +}; + +#if defined(HAVE_PTHREADS) +static pthread_mutex_t gSyspropMutex = PTHREAD_MUTEX_INITIALIZER; +static Vector<sysprop_change_callback_info>* gSyspropList = NULL; +#endif + +void add_sysprop_change_callback(sysprop_change_callback cb, int priority) { +#if defined(HAVE_PTHREADS) + pthread_mutex_lock(&gSyspropMutex); + if (gSyspropList == NULL) { + gSyspropList = new Vector<sysprop_change_callback_info>(); + } + sysprop_change_callback_info info; + info.callback = cb; + info.priority = priority; + bool added = false; + for (size_t i=0; i<gSyspropList->size(); i++) { + if (priority >= gSyspropList->itemAt(i).priority) { + gSyspropList->insertAt(info, i); + added = true; + break; + } + } + if (!added) { + gSyspropList->add(info); + } + pthread_mutex_unlock(&gSyspropMutex); +#endif +} + +void report_sysprop_change() { +#if defined(HAVE_PTHREADS) + pthread_mutex_lock(&gSyspropMutex); + Vector<sysprop_change_callback_info> listeners; + if (gSyspropList != NULL) { + listeners = *gSyspropList; + } + pthread_mutex_unlock(&gSyspropMutex); + + //ALOGI("Reporting sysprop change to %d listeners", listeners.size()); + for (size_t i=0; i<listeners.size(); i++) { + listeners[i].callback(); + } +#endif +} + +}; // namespace android diff --git a/libutils/primes.py b/libutils/primes.py new file mode 100755 index 0000000..e161dd8 --- /dev/null +++ b/libutils/primes.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python2.6 +# +# Copyright (C) 2011 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# +# Generates a table of prime numbers for use in BasicHashtable.cpp. +# +# Each prime is chosen such that it is a little more than twice as large as +# the previous prime in the table. This makes it easier to choose a new +# hashtable size when the underlying array is grown by as nominal factor +# of two each time. +# + +def is_odd_prime(n): + limit = (n - 1) / 2 + d = 3 + while d <= limit: + if n % d == 0: + return False + d += 2 + return True + +print "static size_t PRIMES[] = {" + +n = 5 +max = 2**31 - 1 +while n < max: + print " %d," % (n) + n = n * 2 + 1 + while not is_odd_prime(n): + n += 2 + +print " 0," +print "};" diff --git a/libutils/tests/Android.mk b/libutils/tests/Android.mk new file mode 100644 index 0000000..caedaff --- /dev/null +++ b/libutils/tests/Android.mk @@ -0,0 +1,34 @@ +# Build the unit tests. +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +# Build the unit tests. +test_src_files := \ + BasicHashtable_test.cpp \ + BlobCache_test.cpp \ + BitSet_test.cpp \ + Looper_test.cpp \ + LruCache_test.cpp \ + String8_test.cpp \ + Unicode_test.cpp \ + Vector_test.cpp + +shared_libraries := \ + libz \ + liblog \ + libcutils \ + libutils \ + libstlport + +static_libraries := \ + libgtest \ + libgtest_main + +$(foreach file,$(test_src_files), \ + $(eval include $(CLEAR_VARS)) \ + $(eval LOCAL_SHARED_LIBRARIES := $(shared_libraries)) \ + $(eval LOCAL_STATIC_LIBRARIES := $(static_libraries)) \ + $(eval LOCAL_SRC_FILES := $(file)) \ + $(eval LOCAL_MODULE := $(notdir $(file:%.cpp=%))) \ + $(eval include $(BUILD_NATIVE_TEST)) \ +) diff --git a/libutils/tests/BasicHashtable_test.cpp b/libutils/tests/BasicHashtable_test.cpp new file mode 100644 index 0000000..7dcf750 --- /dev/null +++ b/libutils/tests/BasicHashtable_test.cpp @@ -0,0 +1,577 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "BasicHashtable_test" + +#include <utils/BasicHashtable.h> +#include <cutils/log.h> +#include <gtest/gtest.h> +#include <unistd.h> + +namespace android { + +typedef int SimpleKey; +typedef int SimpleValue; +typedef key_value_pair_t<SimpleKey, SimpleValue> SimpleEntry; +typedef BasicHashtable<SimpleKey, SimpleEntry> SimpleHashtable; + +struct ComplexKey { + int k; + + explicit ComplexKey(int k) : k(k) { + instanceCount += 1; + } + + ComplexKey(const ComplexKey& other) : k(other.k) { + instanceCount += 1; + } + + ~ComplexKey() { + instanceCount -= 1; + } + + bool operator ==(const ComplexKey& other) const { + return k == other.k; + } + + bool operator !=(const ComplexKey& other) const { + return k != other.k; + } + + static ssize_t instanceCount; +}; + +ssize_t ComplexKey::instanceCount = 0; + +template<> inline hash_t hash_type(const ComplexKey& value) { + return hash_type(value.k); +} + +struct ComplexValue { + int v; + + explicit ComplexValue(int v) : v(v) { + instanceCount += 1; + } + + ComplexValue(const ComplexValue& other) : v(other.v) { + instanceCount += 1; + } + + ~ComplexValue() { + instanceCount -= 1; + } + + static ssize_t instanceCount; +}; + +ssize_t ComplexValue::instanceCount = 0; + +typedef key_value_pair_t<ComplexKey, ComplexValue> ComplexEntry; +typedef BasicHashtable<ComplexKey, ComplexEntry> ComplexHashtable; + +class BasicHashtableTest : public testing::Test { +protected: + virtual void SetUp() { + ComplexKey::instanceCount = 0; + ComplexValue::instanceCount = 0; + } + + virtual void TearDown() { + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(0, 0)); + } + + void assertInstanceCount(ssize_t keys, ssize_t values) { + if (keys != ComplexKey::instanceCount || values != ComplexValue::instanceCount) { + FAIL() << "Expected " << keys << " keys and " << values << " values " + "but there were actually " << ComplexKey::instanceCount << " keys and " + << ComplexValue::instanceCount << " values"; + } + } + +public: + template <typename TKey, typename TEntry> + static void cookieAt(const BasicHashtable<TKey, TEntry>& h, size_t index, + bool* collision, bool* present, hash_t* hash) { + uint32_t cookie = h.cookieAt(index); + *collision = cookie & BasicHashtable<TKey, TEntry>::Bucket::COLLISION; + *present = cookie & BasicHashtable<TKey, TEntry>::Bucket::PRESENT; + *hash = cookie & BasicHashtable<TKey, TEntry>::Bucket::HASH_MASK; + } + + template <typename TKey, typename TEntry> + static const void* getBuckets(const BasicHashtable<TKey, TEntry>& h) { + return h.mBuckets; + } +}; + +template <typename TKey, typename TValue> +static size_t add(BasicHashtable<TKey, key_value_pair_t<TKey, TValue> >& h, + const TKey& key, const TValue& value) { + return h.add(hash_type(key), key_value_pair_t<TKey, TValue>(key, value)); +} + +template <typename TKey, typename TValue> +static ssize_t find(BasicHashtable<TKey, key_value_pair_t<TKey, TValue> >& h, + ssize_t index, const TKey& key) { + return h.find(index, hash_type(key), key); +} + +template <typename TKey, typename TValue> +static bool remove(BasicHashtable<TKey, key_value_pair_t<TKey, TValue> >& h, + const TKey& key) { + ssize_t index = find(h, -1, key); + if (index >= 0) { + h.removeAt(index); + return true; + } + return false; +} + +template <typename TEntry> +static void getKeyValue(const TEntry& entry, int* key, int* value); + +template <> void getKeyValue(const SimpleEntry& entry, int* key, int* value) { + *key = entry.key; + *value = entry.value; +} + +template <> void getKeyValue(const ComplexEntry& entry, int* key, int* value) { + *key = entry.key.k; + *value = entry.value.v; +} + +template <typename TKey, typename TValue> +static void dump(BasicHashtable<TKey, key_value_pair_t<TKey, TValue> >& h) { + ALOGD("hashtable %p, size=%u, capacity=%u, bucketCount=%u", + &h, h.size(), h.capacity(), h.bucketCount()); + for (size_t i = 0; i < h.bucketCount(); i++) { + bool collision, present; + hash_t hash; + BasicHashtableTest::cookieAt(h, i, &collision, &present, &hash); + if (present) { + int key, value; + getKeyValue(h.entryAt(i), &key, &value); + ALOGD(" [%3u] = collision=%d, present=%d, hash=0x%08x, key=%3d, value=%3d, " + "hash_type(key)=0x%08x", + i, collision, present, hash, key, value, hash_type(key)); + } else { + ALOGD(" [%3u] = collision=%d, present=%d", + i, collision, present); + } + } +} + +TEST_F(BasicHashtableTest, DefaultConstructor_WithDefaultProperties) { + SimpleHashtable h; + + EXPECT_EQ(0U, h.size()); + EXPECT_EQ(3U, h.capacity()); + EXPECT_EQ(5U, h.bucketCount()); + EXPECT_EQ(0.75f, h.loadFactor()); +} + +TEST_F(BasicHashtableTest, Constructor_WithNonUnityLoadFactor) { + SimpleHashtable h(52, 0.8f); + + EXPECT_EQ(0U, h.size()); + EXPECT_EQ(77U, h.capacity()); + EXPECT_EQ(97U, h.bucketCount()); + EXPECT_EQ(0.8f, h.loadFactor()); +} + +TEST_F(BasicHashtableTest, Constructor_WithUnityLoadFactorAndExactCapacity) { + SimpleHashtable h(46, 1.0f); + + EXPECT_EQ(0U, h.size()); + EXPECT_EQ(46U, h.capacity()); // must be one less than bucketCount because loadFactor == 1.0f + EXPECT_EQ(47U, h.bucketCount()); + EXPECT_EQ(1.0f, h.loadFactor()); +} + +TEST_F(BasicHashtableTest, Constructor_WithUnityLoadFactorAndInexactCapacity) { + SimpleHashtable h(42, 1.0f); + + EXPECT_EQ(0U, h.size()); + EXPECT_EQ(46U, h.capacity()); // must be one less than bucketCount because loadFactor == 1.0f + EXPECT_EQ(47U, h.bucketCount()); + EXPECT_EQ(1.0f, h.loadFactor()); +} + +TEST_F(BasicHashtableTest, FindAddFindRemoveFind_OneEntry) { + SimpleHashtable h; + ssize_t index = find(h, -1, 8); + ASSERT_EQ(-1, index); + + index = add(h, 8, 1); + ASSERT_EQ(1U, h.size()); + + ASSERT_EQ(index, find(h, -1, 8)); + ASSERT_EQ(8, h.entryAt(index).key); + ASSERT_EQ(1, h.entryAt(index).value); + + index = find(h, index, 8); + ASSERT_EQ(-1, index); + + ASSERT_TRUE(remove(h, 8)); + ASSERT_EQ(0U, h.size()); + + index = find(h, -1, 8); + ASSERT_EQ(-1, index); +} + +TEST_F(BasicHashtableTest, FindAddFindRemoveFind_MultipleEntryWithUniqueKey) { + const size_t N = 11; + + SimpleHashtable h; + for (size_t i = 0; i < N; i++) { + ssize_t index = find(h, -1, int(i)); + ASSERT_EQ(-1, index); + + index = add(h, int(i), int(i * 10)); + ASSERT_EQ(i + 1, h.size()); + + ASSERT_EQ(index, find(h, -1, int(i))); + ASSERT_EQ(int(i), h.entryAt(index).key); + ASSERT_EQ(int(i * 10), h.entryAt(index).value); + + index = find(h, index, int(i)); + ASSERT_EQ(-1, index); + } + + for (size_t i = N; --i > 0; ) { + ASSERT_TRUE(remove(h, int(i))) << "i = " << i; + ASSERT_EQ(i, h.size()); + + ssize_t index = find(h, -1, int(i)); + ASSERT_EQ(-1, index); + } +} + +TEST_F(BasicHashtableTest, FindAddFindRemoveFind_MultipleEntryWithDuplicateKey) { + const size_t N = 11; + const int K = 1; + + SimpleHashtable h; + for (size_t i = 0; i < N; i++) { + ssize_t index = find(h, -1, K); + if (i == 0) { + ASSERT_EQ(-1, index); + } else { + ASSERT_NE(-1, index); + } + + add(h, K, int(i)); + ASSERT_EQ(i + 1, h.size()); + + index = -1; + int values = 0; + for (size_t j = 0; j <= i; j++) { + index = find(h, index, K); + ASSERT_GE(index, 0); + ASSERT_EQ(K, h.entryAt(index).key); + values |= 1 << h.entryAt(index).value; + } + ASSERT_EQ(values, (1 << (i + 1)) - 1); + + index = find(h, index, K); + ASSERT_EQ(-1, index); + } + + for (size_t i = N; --i > 0; ) { + ASSERT_TRUE(remove(h, K)) << "i = " << i; + ASSERT_EQ(i, h.size()); + + ssize_t index = -1; + for (size_t j = 0; j < i; j++) { + index = find(h, index, K); + ASSERT_GE(index, 0); + ASSERT_EQ(K, h.entryAt(index).key); + } + + index = find(h, index, K); + ASSERT_EQ(-1, index); + } +} + +TEST_F(BasicHashtableTest, Clear_WhenAlreadyEmpty_DoesNothing) { + SimpleHashtable h; + h.clear(); + + EXPECT_EQ(0U, h.size()); + EXPECT_EQ(3U, h.capacity()); + EXPECT_EQ(5U, h.bucketCount()); + EXPECT_EQ(0.75f, h.loadFactor()); +} + +TEST_F(BasicHashtableTest, Clear_AfterElementsAdded_RemovesThem) { + SimpleHashtable h; + add(h, 0, 0); + add(h, 1, 0); + h.clear(); + + EXPECT_EQ(0U, h.size()); + EXPECT_EQ(3U, h.capacity()); + EXPECT_EQ(5U, h.bucketCount()); + EXPECT_EQ(0.75f, h.loadFactor()); +} + +TEST_F(BasicHashtableTest, Clear_AfterElementsAdded_DestroysThem) { + ComplexHashtable h; + add(h, ComplexKey(0), ComplexValue(0)); + add(h, ComplexKey(1), ComplexValue(0)); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(2, 2)); + + h.clear(); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(0, 0)); + + EXPECT_EQ(0U, h.size()); + EXPECT_EQ(3U, h.capacity()); + EXPECT_EQ(5U, h.bucketCount()); + EXPECT_EQ(0.75f, h.loadFactor()); +} + +TEST_F(BasicHashtableTest, Remove_AfterElementsAdded_DestroysThem) { + ComplexHashtable h; + add(h, ComplexKey(0), ComplexValue(0)); + add(h, ComplexKey(1), ComplexValue(0)); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(2, 2)); + + ASSERT_TRUE(remove(h, ComplexKey(0))); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(1, 1)); + + ASSERT_TRUE(remove(h, ComplexKey(1))); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(0, 0)); + + EXPECT_EQ(0U, h.size()); + EXPECT_EQ(3U, h.capacity()); + EXPECT_EQ(5U, h.bucketCount()); + EXPECT_EQ(0.75f, h.loadFactor()); +} + +TEST_F(BasicHashtableTest, Destructor_AfterElementsAdded_DestroysThem) { + { + ComplexHashtable h; + add(h, ComplexKey(0), ComplexValue(0)); + add(h, ComplexKey(1), ComplexValue(0)); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(2, 2)); + } // h is destroyed here + + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(0, 0)); +} + +TEST_F(BasicHashtableTest, Next_WhenEmpty_ReturnsMinusOne) { + SimpleHashtable h; + + ASSERT_EQ(-1, h.next(-1)); +} + +TEST_F(BasicHashtableTest, Next_WhenNonEmpty_IteratesOverAllEntries) { + const int N = 88; + + SimpleHashtable h; + for (int i = 0; i < N; i++) { + add(h, i, i * 10); + } + + bool set[N]; + memset(set, 0, sizeof(bool) * N); + int count = 0; + for (ssize_t index = -1; (index = h.next(index)) != -1; ) { + ASSERT_GE(index, 0); + ASSERT_LT(size_t(index), h.bucketCount()); + + const SimpleEntry& entry = h.entryAt(index); + ASSERT_GE(entry.key, 0); + ASSERT_LT(entry.key, N); + ASSERT_EQ(false, set[entry.key]); + ASSERT_EQ(entry.key * 10, entry.value); + + set[entry.key] = true; + count += 1; + } + ASSERT_EQ(N, count); +} + +TEST_F(BasicHashtableTest, Add_RehashesOnDemand) { + SimpleHashtable h; + size_t initialCapacity = h.capacity(); + size_t initialBucketCount = h.bucketCount(); + + for (size_t i = 0; i < initialCapacity; i++) { + add(h, int(i), 0); + } + + EXPECT_EQ(initialCapacity, h.size()); + EXPECT_EQ(initialCapacity, h.capacity()); + EXPECT_EQ(initialBucketCount, h.bucketCount()); + + add(h, -1, -1); + + EXPECT_EQ(initialCapacity + 1, h.size()); + EXPECT_GT(h.capacity(), initialCapacity); + EXPECT_GT(h.bucketCount(), initialBucketCount); + EXPECT_GT(h.bucketCount(), h.capacity()); +} + +TEST_F(BasicHashtableTest, Rehash_WhenCapacityAndBucketCountUnchanged_DoesNothing) { + ComplexHashtable h; + add(h, ComplexKey(0), ComplexValue(0)); + const void* oldBuckets = getBuckets(h); + ASSERT_NE((void*)NULL, oldBuckets); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(1, 1)); + + h.rehash(h.capacity(), h.loadFactor()); + + ASSERT_EQ(oldBuckets, getBuckets(h)); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(1, 1)); +} + +TEST_F(BasicHashtableTest, Rehash_WhenEmptyAndHasNoBuckets_ButDoesNotAllocateBuckets) { + ComplexHashtable h; + ASSERT_EQ((void*)NULL, getBuckets(h)); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(0, 0)); + + h.rehash(9, 1.0f); + + EXPECT_EQ(0U, h.size()); + EXPECT_EQ(10U, h.capacity()); + EXPECT_EQ(11U, h.bucketCount()); + EXPECT_EQ(1.0f, h.loadFactor()); + EXPECT_EQ((void*)NULL, getBuckets(h)); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(0, 0)); +} + +TEST_F(BasicHashtableTest, Rehash_WhenEmptyAndHasBuckets_ReleasesBucketsAndSetsCapacity) { + ComplexHashtable h(10); + add(h, ComplexKey(0), ComplexValue(0)); + ASSERT_TRUE(remove(h, ComplexKey(0))); + ASSERT_NE((void*)NULL, getBuckets(h)); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(0, 0)); + + h.rehash(0, 0.75f); + + EXPECT_EQ(0U, h.size()); + EXPECT_EQ(3U, h.capacity()); + EXPECT_EQ(5U, h.bucketCount()); + EXPECT_EQ(0.75f, h.loadFactor()); + EXPECT_EQ((void*)NULL, getBuckets(h)); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(0, 0)); +} + +TEST_F(BasicHashtableTest, Rehash_WhenLessThanCurrentCapacity_ShrinksBuckets) { + ComplexHashtable h(10); + add(h, ComplexKey(0), ComplexValue(0)); + add(h, ComplexKey(1), ComplexValue(1)); + const void* oldBuckets = getBuckets(h); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(2, 2)); + + h.rehash(0, 0.75f); + + EXPECT_EQ(2U, h.size()); + EXPECT_EQ(3U, h.capacity()); + EXPECT_EQ(5U, h.bucketCount()); + EXPECT_EQ(0.75f, h.loadFactor()); + EXPECT_NE(oldBuckets, getBuckets(h)); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(2, 2)); +} + +TEST_F(BasicHashtableTest, CopyOnWrite) { + ComplexHashtable h1; + add(h1, ComplexKey(0), ComplexValue(0)); + add(h1, ComplexKey(1), ComplexValue(1)); + const void* originalBuckets = getBuckets(h1); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(2, 2)); + ssize_t index0 = find(h1, -1, ComplexKey(0)); + EXPECT_GE(index0, 0); + + // copy constructor acquires shared reference + ComplexHashtable h2(h1); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(2, 2)); + ASSERT_EQ(originalBuckets, getBuckets(h2)); + EXPECT_EQ(h1.size(), h2.size()); + EXPECT_EQ(h1.capacity(), h2.capacity()); + EXPECT_EQ(h1.bucketCount(), h2.bucketCount()); + EXPECT_EQ(h1.loadFactor(), h2.loadFactor()); + EXPECT_EQ(index0, find(h2, -1, ComplexKey(0))); + + // operator= acquires shared reference + ComplexHashtable h3; + h3 = h2; + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(2, 2)); + ASSERT_EQ(originalBuckets, getBuckets(h3)); + EXPECT_EQ(h1.size(), h3.size()); + EXPECT_EQ(h1.capacity(), h3.capacity()); + EXPECT_EQ(h1.bucketCount(), h3.bucketCount()); + EXPECT_EQ(h1.loadFactor(), h3.loadFactor()); + EXPECT_EQ(index0, find(h3, -1, ComplexKey(0))); + + // editEntryAt copies shared contents + h1.editEntryAt(index0).value.v = 42; + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(4, 4)); + ASSERT_NE(originalBuckets, getBuckets(h1)); + EXPECT_EQ(42, h1.entryAt(index0).value.v); + EXPECT_EQ(0, h2.entryAt(index0).value.v); + EXPECT_EQ(0, h3.entryAt(index0).value.v); + + // clear releases reference to shared contents + h2.clear(); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(4, 4)); + EXPECT_EQ(0U, h2.size()); + ASSERT_NE(originalBuckets, getBuckets(h2)); + + // operator= acquires shared reference, destroys unshared contents + h1 = h3; + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(2, 2)); + ASSERT_EQ(originalBuckets, getBuckets(h1)); + EXPECT_EQ(h3.size(), h1.size()); + EXPECT_EQ(h3.capacity(), h1.capacity()); + EXPECT_EQ(h3.bucketCount(), h1.bucketCount()); + EXPECT_EQ(h3.loadFactor(), h1.loadFactor()); + EXPECT_EQ(index0, find(h1, -1, ComplexKey(0))); + + // add copies shared contents + add(h1, ComplexKey(2), ComplexValue(2)); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(5, 5)); + ASSERT_NE(originalBuckets, getBuckets(h1)); + EXPECT_EQ(3U, h1.size()); + EXPECT_EQ(0U, h2.size()); + EXPECT_EQ(2U, h3.size()); + + // remove copies shared contents + h1 = h3; + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(2, 2)); + ASSERT_EQ(originalBuckets, getBuckets(h1)); + h1.removeAt(index0); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(3, 3)); + ASSERT_NE(originalBuckets, getBuckets(h1)); + EXPECT_EQ(1U, h1.size()); + EXPECT_EQ(0U, h2.size()); + EXPECT_EQ(2U, h3.size()); + + // rehash copies shared contents + h1 = h3; + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(2, 2)); + ASSERT_EQ(originalBuckets, getBuckets(h1)); + h1.rehash(10, 1.0f); + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(4, 4)); + ASSERT_NE(originalBuckets, getBuckets(h1)); + EXPECT_EQ(2U, h1.size()); + EXPECT_EQ(0U, h2.size()); + EXPECT_EQ(2U, h3.size()); +} + +} // namespace android diff --git a/libutils/tests/BitSet_test.cpp b/libutils/tests/BitSet_test.cpp new file mode 100644 index 0000000..752e56d --- /dev/null +++ b/libutils/tests/BitSet_test.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "BitSet_test" + +#include <utils/BitSet.h> +#include <cutils/log.h> +#include <gtest/gtest.h> +#include <unistd.h> + +namespace android { + +class BitSetTest : public testing::Test { +protected: + BitSet32 b1; + BitSet32 b2; + virtual void TearDown() { + b1.clear(); + b2.clear(); + } +}; + + +TEST_F(BitSetTest, BitWiseOr) { + b1.markBit(2); + b2.markBit(4); + + BitSet32 tmp = b1 | b2; + EXPECT_EQ(tmp.count(), 2u); + EXPECT_TRUE(tmp.hasBit(2) && tmp.hasBit(4)); + // Check that the operator is symmetric + EXPECT_TRUE((b2 | b1) == (b1 | b2)); + + b1 |= b2; + EXPECT_EQ(b1.count(), 2u); + EXPECT_TRUE(b1.hasBit(2) && b1.hasBit(4)); + EXPECT_TRUE(b2.hasBit(4) && b2.count() == 1u); +} +TEST_F(BitSetTest, BitWiseAnd_Disjoint) { + b1.markBit(2); + b1.markBit(4); + b1.markBit(6); + + BitSet32 tmp = b1 & b2; + EXPECT_TRUE(tmp.isEmpty()); + // Check that the operator is symmetric + EXPECT_TRUE((b2 & b1) == (b1 & b2)); + + b2 &= b1; + EXPECT_TRUE(b2.isEmpty()); + EXPECT_EQ(b1.count(), 3u); + EXPECT_TRUE(b1.hasBit(2) && b1.hasBit(4) && b1.hasBit(6)); +} + +TEST_F(BitSetTest, BitWiseAnd_NonDisjoint) { + b1.markBit(2); + b1.markBit(4); + b1.markBit(6); + b2.markBit(3); + b2.markBit(6); + b2.markBit(9); + + BitSet32 tmp = b1 & b2; + EXPECT_EQ(tmp.count(), 1u); + EXPECT_TRUE(tmp.hasBit(6)); + // Check that the operator is symmetric + EXPECT_TRUE((b2 & b1) == (b1 & b2)); + + b1 &= b2; + EXPECT_EQ(b1.count(), 1u); + EXPECT_EQ(b2.count(), 3u); + EXPECT_TRUE(b2.hasBit(3) && b2.hasBit(6) && b2.hasBit(9)); +} +} // namespace android diff --git a/libutils/tests/BlobCache_test.cpp b/libutils/tests/BlobCache_test.cpp new file mode 100644 index 0000000..7202123 --- /dev/null +++ b/libutils/tests/BlobCache_test.cpp @@ -0,0 +1,421 @@ +/* + ** Copyright 2011, The Android Open Source Project + ** + ** Licensed under the Apache License, Version 2.0 (the "License"); + ** you may not use this file except in compliance with the License. + ** You may obtain a copy of the License at + ** + ** http://www.apache.org/licenses/LICENSE-2.0 + ** + ** Unless required by applicable law or agreed to in writing, software + ** distributed under the License is distributed on an "AS IS" BASIS, + ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ** See the License for the specific language governing permissions and + ** limitations under the License. + */ + +#include <fcntl.h> +#include <stdio.h> + +#include <gtest/gtest.h> + +#include <utils/BlobCache.h> +#include <utils/Errors.h> + +namespace android { + +class BlobCacheTest : public ::testing::Test { +protected: + enum { + MAX_KEY_SIZE = 6, + MAX_VALUE_SIZE = 8, + MAX_TOTAL_SIZE = 13, + }; + + virtual void SetUp() { + mBC = new BlobCache(MAX_KEY_SIZE, MAX_VALUE_SIZE, MAX_TOTAL_SIZE); + } + + virtual void TearDown() { + mBC.clear(); + } + + sp<BlobCache> mBC; +}; + +TEST_F(BlobCacheTest, CacheSingleValueSucceeds) { + char buf[4] = { 0xee, 0xee, 0xee, 0xee }; + mBC->set("abcd", 4, "efgh", 4); + ASSERT_EQ(size_t(4), mBC->get("abcd", 4, buf, 4)); + ASSERT_EQ('e', buf[0]); + ASSERT_EQ('f', buf[1]); + ASSERT_EQ('g', buf[2]); + ASSERT_EQ('h', buf[3]); +} + +TEST_F(BlobCacheTest, CacheTwoValuesSucceeds) { + char buf[2] = { 0xee, 0xee }; + mBC->set("ab", 2, "cd", 2); + mBC->set("ef", 2, "gh", 2); + ASSERT_EQ(size_t(2), mBC->get("ab", 2, buf, 2)); + ASSERT_EQ('c', buf[0]); + ASSERT_EQ('d', buf[1]); + ASSERT_EQ(size_t(2), mBC->get("ef", 2, buf, 2)); + ASSERT_EQ('g', buf[0]); + ASSERT_EQ('h', buf[1]); +} + +TEST_F(BlobCacheTest, GetOnlyWritesInsideBounds) { + char buf[6] = { 0xee, 0xee, 0xee, 0xee, 0xee, 0xee }; + mBC->set("abcd", 4, "efgh", 4); + ASSERT_EQ(size_t(4), mBC->get("abcd", 4, buf+1, 4)); + ASSERT_EQ(0xee, buf[0]); + ASSERT_EQ('e', buf[1]); + ASSERT_EQ('f', buf[2]); + ASSERT_EQ('g', buf[3]); + ASSERT_EQ('h', buf[4]); + ASSERT_EQ(0xee, buf[5]); +} + +TEST_F(BlobCacheTest, GetOnlyWritesIfBufferIsLargeEnough) { + char buf[3] = { 0xee, 0xee, 0xee }; + mBC->set("abcd", 4, "efgh", 4); + ASSERT_EQ(size_t(4), mBC->get("abcd", 4, buf, 3)); + ASSERT_EQ(0xee, buf[0]); + ASSERT_EQ(0xee, buf[1]); + ASSERT_EQ(0xee, buf[2]); +} + +TEST_F(BlobCacheTest, GetDoesntAccessNullBuffer) { + mBC->set("abcd", 4, "efgh", 4); + ASSERT_EQ(size_t(4), mBC->get("abcd", 4, NULL, 0)); +} + +TEST_F(BlobCacheTest, MultipleSetsCacheLatestValue) { + char buf[4] = { 0xee, 0xee, 0xee, 0xee }; + mBC->set("abcd", 4, "efgh", 4); + mBC->set("abcd", 4, "ijkl", 4); + ASSERT_EQ(size_t(4), mBC->get("abcd", 4, buf, 4)); + ASSERT_EQ('i', buf[0]); + ASSERT_EQ('j', buf[1]); + ASSERT_EQ('k', buf[2]); + ASSERT_EQ('l', buf[3]); +} + +TEST_F(BlobCacheTest, SecondSetKeepsFirstValueIfTooLarge) { + char buf[MAX_VALUE_SIZE+1] = { 0xee, 0xee, 0xee, 0xee }; + mBC->set("abcd", 4, "efgh", 4); + mBC->set("abcd", 4, buf, MAX_VALUE_SIZE+1); + ASSERT_EQ(size_t(4), mBC->get("abcd", 4, buf, 4)); + ASSERT_EQ('e', buf[0]); + ASSERT_EQ('f', buf[1]); + ASSERT_EQ('g', buf[2]); + ASSERT_EQ('h', buf[3]); +} + +TEST_F(BlobCacheTest, DoesntCacheIfKeyIsTooBig) { + char key[MAX_KEY_SIZE+1]; + char buf[4] = { 0xee, 0xee, 0xee, 0xee }; + for (int i = 0; i < MAX_KEY_SIZE+1; i++) { + key[i] = 'a'; + } + mBC->set(key, MAX_KEY_SIZE+1, "bbbb", 4); + ASSERT_EQ(size_t(0), mBC->get(key, MAX_KEY_SIZE+1, buf, 4)); + ASSERT_EQ(0xee, buf[0]); + ASSERT_EQ(0xee, buf[1]); + ASSERT_EQ(0xee, buf[2]); + ASSERT_EQ(0xee, buf[3]); +} + +TEST_F(BlobCacheTest, DoesntCacheIfValueIsTooBig) { + char buf[MAX_VALUE_SIZE+1]; + for (int i = 0; i < MAX_VALUE_SIZE+1; i++) { + buf[i] = 'b'; + } + mBC->set("abcd", 4, buf, MAX_VALUE_SIZE+1); + for (int i = 0; i < MAX_VALUE_SIZE+1; i++) { + buf[i] = 0xee; + } + ASSERT_EQ(size_t(0), mBC->get("abcd", 4, buf, MAX_VALUE_SIZE+1)); + for (int i = 0; i < MAX_VALUE_SIZE+1; i++) { + SCOPED_TRACE(i); + ASSERT_EQ(0xee, buf[i]); + } +} + +TEST_F(BlobCacheTest, DoesntCacheIfKeyValuePairIsTooBig) { + // Check a testing assumptions + ASSERT_TRUE(MAX_TOTAL_SIZE < MAX_KEY_SIZE + MAX_VALUE_SIZE); + ASSERT_TRUE(MAX_KEY_SIZE < MAX_TOTAL_SIZE); + + enum { bufSize = MAX_TOTAL_SIZE - MAX_KEY_SIZE + 1 }; + + char key[MAX_KEY_SIZE]; + char buf[bufSize]; + for (int i = 0; i < MAX_KEY_SIZE; i++) { + key[i] = 'a'; + } + for (int i = 0; i < bufSize; i++) { + buf[i] = 'b'; + } + + mBC->set(key, MAX_KEY_SIZE, buf, MAX_VALUE_SIZE); + ASSERT_EQ(size_t(0), mBC->get(key, MAX_KEY_SIZE, NULL, 0)); +} + +TEST_F(BlobCacheTest, CacheMaxKeySizeSucceeds) { + char key[MAX_KEY_SIZE]; + char buf[4] = { 0xee, 0xee, 0xee, 0xee }; + for (int i = 0; i < MAX_KEY_SIZE; i++) { + key[i] = 'a'; + } + mBC->set(key, MAX_KEY_SIZE, "wxyz", 4); + ASSERT_EQ(size_t(4), mBC->get(key, MAX_KEY_SIZE, buf, 4)); + ASSERT_EQ('w', buf[0]); + ASSERT_EQ('x', buf[1]); + ASSERT_EQ('y', buf[2]); + ASSERT_EQ('z', buf[3]); +} + +TEST_F(BlobCacheTest, CacheMaxValueSizeSucceeds) { + char buf[MAX_VALUE_SIZE]; + for (int i = 0; i < MAX_VALUE_SIZE; i++) { + buf[i] = 'b'; + } + mBC->set("abcd", 4, buf, MAX_VALUE_SIZE); + for (int i = 0; i < MAX_VALUE_SIZE; i++) { + buf[i] = 0xee; + } + ASSERT_EQ(size_t(MAX_VALUE_SIZE), mBC->get("abcd", 4, buf, + MAX_VALUE_SIZE)); + for (int i = 0; i < MAX_VALUE_SIZE; i++) { + SCOPED_TRACE(i); + ASSERT_EQ('b', buf[i]); + } +} + +TEST_F(BlobCacheTest, CacheMaxKeyValuePairSizeSucceeds) { + // Check a testing assumption + ASSERT_TRUE(MAX_KEY_SIZE < MAX_TOTAL_SIZE); + + enum { bufSize = MAX_TOTAL_SIZE - MAX_KEY_SIZE }; + + char key[MAX_KEY_SIZE]; + char buf[bufSize]; + for (int i = 0; i < MAX_KEY_SIZE; i++) { + key[i] = 'a'; + } + for (int i = 0; i < bufSize; i++) { + buf[i] = 'b'; + } + + mBC->set(key, MAX_KEY_SIZE, buf, bufSize); + ASSERT_EQ(size_t(bufSize), mBC->get(key, MAX_KEY_SIZE, NULL, 0)); +} + +TEST_F(BlobCacheTest, CacheMinKeyAndValueSizeSucceeds) { + char buf[1] = { 0xee }; + mBC->set("x", 1, "y", 1); + ASSERT_EQ(size_t(1), mBC->get("x", 1, buf, 1)); + ASSERT_EQ('y', buf[0]); +} + +TEST_F(BlobCacheTest, CacheSizeDoesntExceedTotalLimit) { + for (int i = 0; i < 256; i++) { + uint8_t k = i; + mBC->set(&k, 1, "x", 1); + } + int numCached = 0; + for (int i = 0; i < 256; i++) { + uint8_t k = i; + if (mBC->get(&k, 1, NULL, 0) == 1) { + numCached++; + } + } + ASSERT_GE(MAX_TOTAL_SIZE / 2, numCached); +} + +TEST_F(BlobCacheTest, ExceedingTotalLimitHalvesCacheSize) { + // Fill up the entire cache with 1 char key/value pairs. + const int maxEntries = MAX_TOTAL_SIZE / 2; + for (int i = 0; i < maxEntries; i++) { + uint8_t k = i; + mBC->set(&k, 1, "x", 1); + } + // Insert one more entry, causing a cache overflow. + { + uint8_t k = maxEntries; + mBC->set(&k, 1, "x", 1); + } + // Count the number of entries in the cache. + int numCached = 0; + for (int i = 0; i < maxEntries+1; i++) { + uint8_t k = i; + if (mBC->get(&k, 1, NULL, 0) == 1) { + numCached++; + } + } + ASSERT_EQ(maxEntries/2 + 1, numCached); +} + +class BlobCacheFlattenTest : public BlobCacheTest { +protected: + virtual void SetUp() { + BlobCacheTest::SetUp(); + mBC2 = new BlobCache(MAX_KEY_SIZE, MAX_VALUE_SIZE, MAX_TOTAL_SIZE); + } + + virtual void TearDown() { + mBC2.clear(); + BlobCacheTest::TearDown(); + } + + void roundTrip() { + size_t size = mBC->getFlattenedSize(); + uint8_t* flat = new uint8_t[size]; + ASSERT_EQ(OK, mBC->flatten(flat, size)); + ASSERT_EQ(OK, mBC2->unflatten(flat, size)); + delete[] flat; + } + + sp<BlobCache> mBC2; +}; + +TEST_F(BlobCacheFlattenTest, FlattenOneValue) { + char buf[4] = { 0xee, 0xee, 0xee, 0xee }; + mBC->set("abcd", 4, "efgh", 4); + roundTrip(); + ASSERT_EQ(size_t(4), mBC2->get("abcd", 4, buf, 4)); + ASSERT_EQ('e', buf[0]); + ASSERT_EQ('f', buf[1]); + ASSERT_EQ('g', buf[2]); + ASSERT_EQ('h', buf[3]); +} + +TEST_F(BlobCacheFlattenTest, FlattenFullCache) { + // Fill up the entire cache with 1 char key/value pairs. + const int maxEntries = MAX_TOTAL_SIZE / 2; + for (int i = 0; i < maxEntries; i++) { + uint8_t k = i; + mBC->set(&k, 1, &k, 1); + } + + roundTrip(); + + // Verify the deserialized cache + for (int i = 0; i < maxEntries; i++) { + uint8_t k = i; + uint8_t v = 0xee; + ASSERT_EQ(size_t(1), mBC2->get(&k, 1, &v, 1)); + ASSERT_EQ(k, v); + } +} + +TEST_F(BlobCacheFlattenTest, FlattenDoesntChangeCache) { + // Fill up the entire cache with 1 char key/value pairs. + const int maxEntries = MAX_TOTAL_SIZE / 2; + for (int i = 0; i < maxEntries; i++) { + uint8_t k = i; + mBC->set(&k, 1, &k, 1); + } + + size_t size = mBC->getFlattenedSize(); + uint8_t* flat = new uint8_t[size]; + ASSERT_EQ(OK, mBC->flatten(flat, size)); + delete[] flat; + + // Verify the cache that we just serialized + for (int i = 0; i < maxEntries; i++) { + uint8_t k = i; + uint8_t v = 0xee; + ASSERT_EQ(size_t(1), mBC->get(&k, 1, &v, 1)); + ASSERT_EQ(k, v); + } +} + +TEST_F(BlobCacheFlattenTest, FlattenCatchesBufferTooSmall) { + // Fill up the entire cache with 1 char key/value pairs. + const int maxEntries = MAX_TOTAL_SIZE / 2; + for (int i = 0; i < maxEntries; i++) { + uint8_t k = i; + mBC->set(&k, 1, &k, 1); + } + + size_t size = mBC->getFlattenedSize() - 1; + uint8_t* flat = new uint8_t[size]; + ASSERT_EQ(BAD_VALUE, mBC->flatten(flat, size)); + delete[] flat; +} + +TEST_F(BlobCacheFlattenTest, UnflattenCatchesBadMagic) { + char buf[4] = { 0xee, 0xee, 0xee, 0xee }; + mBC->set("abcd", 4, "efgh", 4); + + size_t size = mBC->getFlattenedSize(); + uint8_t* flat = new uint8_t[size]; + ASSERT_EQ(OK, mBC->flatten(flat, size)); + flat[1] = ~flat[1]; + + // Bad magic should cause an error. + ASSERT_EQ(BAD_VALUE, mBC2->unflatten(flat, size)); + delete[] flat; + + // The error should cause the unflatten to result in an empty cache + ASSERT_EQ(size_t(0), mBC2->get("abcd", 4, buf, 4)); +} + +TEST_F(BlobCacheFlattenTest, UnflattenCatchesBadBlobCacheVersion) { + char buf[4] = { 0xee, 0xee, 0xee, 0xee }; + mBC->set("abcd", 4, "efgh", 4); + + size_t size = mBC->getFlattenedSize(); + uint8_t* flat = new uint8_t[size]; + ASSERT_EQ(OK, mBC->flatten(flat, size)); + flat[5] = ~flat[5]; + + // Version mismatches shouldn't cause errors, but should not use the + // serialized entries + ASSERT_EQ(OK, mBC2->unflatten(flat, size)); + delete[] flat; + + // The version mismatch should cause the unflatten to result in an empty + // cache + ASSERT_EQ(size_t(0), mBC2->get("abcd", 4, buf, 4)); +} + +TEST_F(BlobCacheFlattenTest, UnflattenCatchesBadBlobCacheDeviceVersion) { + char buf[4] = { 0xee, 0xee, 0xee, 0xee }; + mBC->set("abcd", 4, "efgh", 4); + + size_t size = mBC->getFlattenedSize(); + uint8_t* flat = new uint8_t[size]; + ASSERT_EQ(OK, mBC->flatten(flat, size)); + flat[10] = ~flat[10]; + + // Version mismatches shouldn't cause errors, but should not use the + // serialized entries + ASSERT_EQ(OK, mBC2->unflatten(flat, size)); + delete[] flat; + + // The version mismatch should cause the unflatten to result in an empty + // cache + ASSERT_EQ(size_t(0), mBC2->get("abcd", 4, buf, 4)); +} + +TEST_F(BlobCacheFlattenTest, UnflattenCatchesBufferTooSmall) { + char buf[4] = { 0xee, 0xee, 0xee, 0xee }; + mBC->set("abcd", 4, "efgh", 4); + + size_t size = mBC->getFlattenedSize(); + uint8_t* flat = new uint8_t[size]; + ASSERT_EQ(OK, mBC->flatten(flat, size)); + + // A buffer truncation shouldt cause an error + ASSERT_EQ(BAD_VALUE, mBC2->unflatten(flat, size-1)); + delete[] flat; + + // The error should cause the unflatten to result in an empty cache + ASSERT_EQ(size_t(0), mBC2->get("abcd", 4, buf, 4)); +} + +} // namespace android diff --git a/libutils/tests/Looper_test.cpp b/libutils/tests/Looper_test.cpp new file mode 100644 index 0000000..8bf2ba2 --- /dev/null +++ b/libutils/tests/Looper_test.cpp @@ -0,0 +1,693 @@ +// +// Copyright 2010 The Android Open Source Project +// + +#include <utils/Looper.h> +#include <utils/Timers.h> +#include <utils/StopWatch.h> +#include <gtest/gtest.h> +#include <unistd.h> +#include <time.h> + +#include "TestHelpers.h" + +// # of milliseconds to fudge stopwatch measurements +#define TIMING_TOLERANCE_MS 25 + +namespace android { + +enum { + MSG_TEST1 = 1, + MSG_TEST2 = 2, + MSG_TEST3 = 3, + MSG_TEST4 = 4, +}; + +class DelayedWake : public DelayedTask { + sp<Looper> mLooper; + +public: + DelayedWake(int delayMillis, const sp<Looper> looper) : + DelayedTask(delayMillis), mLooper(looper) { + } + +protected: + virtual void doTask() { + mLooper->wake(); + } +}; + +class DelayedWriteSignal : public DelayedTask { + Pipe* mPipe; + +public: + DelayedWriteSignal(int delayMillis, Pipe* pipe) : + DelayedTask(delayMillis), mPipe(pipe) { + } + +protected: + virtual void doTask() { + mPipe->writeSignal(); + } +}; + +class CallbackHandler { +public: + void setCallback(const sp<Looper>& looper, int fd, int events) { + looper->addFd(fd, 0, events, staticHandler, this); + } + +protected: + virtual ~CallbackHandler() { } + + virtual int handler(int fd, int events) = 0; + +private: + static int staticHandler(int fd, int events, void* data) { + return static_cast<CallbackHandler*>(data)->handler(fd, events); + } +}; + +class StubCallbackHandler : public CallbackHandler { +public: + int nextResult; + int callbackCount; + + int fd; + int events; + + StubCallbackHandler(int nextResult) : nextResult(nextResult), + callbackCount(0), fd(-1), events(-1) { + } + +protected: + virtual int handler(int fd, int events) { + callbackCount += 1; + this->fd = fd; + this->events = events; + return nextResult; + } +}; + +class StubMessageHandler : public MessageHandler { +public: + Vector<Message> messages; + + virtual void handleMessage(const Message& message) { + messages.push(message); + } +}; + +class LooperTest : public testing::Test { +protected: + sp<Looper> mLooper; + + virtual void SetUp() { + mLooper = new Looper(true); + } + + virtual void TearDown() { + mLooper.clear(); + } +}; + + +TEST_F(LooperTest, PollOnce_WhenNonZeroTimeoutAndNotAwoken_WaitsForTimeout) { + StopWatch stopWatch("pollOnce"); + int result = mLooper->pollOnce(100); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. equal timeout"; + EXPECT_EQ(ALOOPER_POLL_TIMEOUT, result) + << "pollOnce result should be ALOOPER_POLL_TIMEOUT"; +} + +TEST_F(LooperTest, PollOnce_WhenNonZeroTimeoutAndAwokenBeforeWaiting_ImmediatelyReturns) { + mLooper->wake(); + + StopWatch stopWatch("pollOnce"); + int result = mLooper->pollOnce(1000); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. zero because wake() was called before waiting"; + EXPECT_EQ(ALOOPER_POLL_WAKE, result) + << "pollOnce result should be ALOOPER_POLL_CALLBACK because loop was awoken"; +} + +TEST_F(LooperTest, PollOnce_WhenNonZeroTimeoutAndAwokenWhileWaiting_PromptlyReturns) { + sp<DelayedWake> delayedWake = new DelayedWake(100, mLooper); + delayedWake->run(); + + StopWatch stopWatch("pollOnce"); + int result = mLooper->pollOnce(1000); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. equal wake delay"; + EXPECT_EQ(ALOOPER_POLL_WAKE, result) + << "pollOnce result should be ALOOPER_POLL_CALLBACK because loop was awoken"; +} + +TEST_F(LooperTest, PollOnce_WhenZeroTimeoutAndNoRegisteredFDs_ImmediatelyReturns) { + StopWatch stopWatch("pollOnce"); + int result = mLooper->pollOnce(0); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should be approx. zero"; + EXPECT_EQ(ALOOPER_POLL_TIMEOUT, result) + << "pollOnce result should be ALOOPER_POLL_TIMEOUT"; +} + +TEST_F(LooperTest, PollOnce_WhenZeroTimeoutAndNoSignalledFDs_ImmediatelyReturns) { + Pipe pipe; + StubCallbackHandler handler(true); + + handler.setCallback(mLooper, pipe.receiveFd, ALOOPER_EVENT_INPUT); + + StopWatch stopWatch("pollOnce"); + int result = mLooper->pollOnce(0); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should be approx. zero"; + EXPECT_EQ(ALOOPER_POLL_TIMEOUT, result) + << "pollOnce result should be ALOOPER_POLL_TIMEOUT"; + EXPECT_EQ(0, handler.callbackCount) + << "callback should not have been invoked because FD was not signalled"; +} + +TEST_F(LooperTest, PollOnce_WhenZeroTimeoutAndSignalledFD_ImmediatelyInvokesCallbackAndReturns) { + Pipe pipe; + StubCallbackHandler handler(true); + + ASSERT_EQ(OK, pipe.writeSignal()); + handler.setCallback(mLooper, pipe.receiveFd, ALOOPER_EVENT_INPUT); + + StopWatch stopWatch("pollOnce"); + int result = mLooper->pollOnce(0); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should be approx. zero"; + EXPECT_EQ(ALOOPER_POLL_CALLBACK, result) + << "pollOnce result should be ALOOPER_POLL_CALLBACK because FD was signalled"; + EXPECT_EQ(1, handler.callbackCount) + << "callback should be invoked exactly once"; + EXPECT_EQ(pipe.receiveFd, handler.fd) + << "callback should have received pipe fd as parameter"; + EXPECT_EQ(ALOOPER_EVENT_INPUT, handler.events) + << "callback should have received ALOOPER_EVENT_INPUT as events"; +} + +TEST_F(LooperTest, PollOnce_WhenNonZeroTimeoutAndNoSignalledFDs_WaitsForTimeoutAndReturns) { + Pipe pipe; + StubCallbackHandler handler(true); + + handler.setCallback(mLooper, pipe.receiveFd, ALOOPER_EVENT_INPUT); + + StopWatch stopWatch("pollOnce"); + int result = mLooper->pollOnce(100); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. equal timeout"; + EXPECT_EQ(ALOOPER_POLL_TIMEOUT, result) + << "pollOnce result should be ALOOPER_POLL_TIMEOUT"; + EXPECT_EQ(0, handler.callbackCount) + << "callback should not have been invoked because FD was not signalled"; +} + +TEST_F(LooperTest, PollOnce_WhenNonZeroTimeoutAndSignalledFDBeforeWaiting_ImmediatelyInvokesCallbackAndReturns) { + Pipe pipe; + StubCallbackHandler handler(true); + + pipe.writeSignal(); + handler.setCallback(mLooper, pipe.receiveFd, ALOOPER_EVENT_INPUT); + + StopWatch stopWatch("pollOnce"); + int result = mLooper->pollOnce(100); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + ASSERT_EQ(OK, pipe.readSignal()) + << "signal should actually have been written"; + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should be approx. zero"; + EXPECT_EQ(ALOOPER_POLL_CALLBACK, result) + << "pollOnce result should be ALOOPER_POLL_CALLBACK because FD was signalled"; + EXPECT_EQ(1, handler.callbackCount) + << "callback should be invoked exactly once"; + EXPECT_EQ(pipe.receiveFd, handler.fd) + << "callback should have received pipe fd as parameter"; + EXPECT_EQ(ALOOPER_EVENT_INPUT, handler.events) + << "callback should have received ALOOPER_EVENT_INPUT as events"; +} + +TEST_F(LooperTest, PollOnce_WhenNonZeroTimeoutAndSignalledFDWhileWaiting_PromptlyInvokesCallbackAndReturns) { + Pipe pipe; + StubCallbackHandler handler(true); + sp<DelayedWriteSignal> delayedWriteSignal = new DelayedWriteSignal(100, & pipe); + + handler.setCallback(mLooper, pipe.receiveFd, ALOOPER_EVENT_INPUT); + delayedWriteSignal->run(); + + StopWatch stopWatch("pollOnce"); + int result = mLooper->pollOnce(1000); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + ASSERT_EQ(OK, pipe.readSignal()) + << "signal should actually have been written"; + EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. equal signal delay"; + EXPECT_EQ(ALOOPER_POLL_CALLBACK, result) + << "pollOnce result should be ALOOPER_POLL_CALLBACK because FD was signalled"; + EXPECT_EQ(1, handler.callbackCount) + << "callback should be invoked exactly once"; + EXPECT_EQ(pipe.receiveFd, handler.fd) + << "callback should have received pipe fd as parameter"; + EXPECT_EQ(ALOOPER_EVENT_INPUT, handler.events) + << "callback should have received ALOOPER_EVENT_INPUT as events"; +} + +TEST_F(LooperTest, PollOnce_WhenCallbackAddedThenRemoved_CallbackShouldNotBeInvoked) { + Pipe pipe; + StubCallbackHandler handler(true); + + handler.setCallback(mLooper, pipe.receiveFd, ALOOPER_EVENT_INPUT); + pipe.writeSignal(); // would cause FD to be considered signalled + mLooper->removeFd(pipe.receiveFd); + + StopWatch stopWatch("pollOnce"); + int result = mLooper->pollOnce(100); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + ASSERT_EQ(OK, pipe.readSignal()) + << "signal should actually have been written"; + EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. equal timeout because FD was no longer registered"; + EXPECT_EQ(ALOOPER_POLL_TIMEOUT, result) + << "pollOnce result should be ALOOPER_POLL_TIMEOUT"; + EXPECT_EQ(0, handler.callbackCount) + << "callback should not be invoked"; +} + +TEST_F(LooperTest, PollOnce_WhenCallbackReturnsFalse_CallbackShouldNotBeInvokedAgainLater) { + Pipe pipe; + StubCallbackHandler handler(false); + + handler.setCallback(mLooper, pipe.receiveFd, ALOOPER_EVENT_INPUT); + + // First loop: Callback is registered and FD is signalled. + pipe.writeSignal(); + + StopWatch stopWatch("pollOnce"); + int result = mLooper->pollOnce(0); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + ASSERT_EQ(OK, pipe.readSignal()) + << "signal should actually have been written"; + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. equal zero because FD was already signalled"; + EXPECT_EQ(ALOOPER_POLL_CALLBACK, result) + << "pollOnce result should be ALOOPER_POLL_CALLBACK because FD was signalled"; + EXPECT_EQ(1, handler.callbackCount) + << "callback should be invoked"; + + // Second loop: Callback is no longer registered and FD is signalled. + pipe.writeSignal(); + + stopWatch.reset(); + result = mLooper->pollOnce(0); + elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + ASSERT_EQ(OK, pipe.readSignal()) + << "signal should actually have been written"; + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. equal zero because timeout was zero"; + EXPECT_EQ(ALOOPER_POLL_TIMEOUT, result) + << "pollOnce result should be ALOOPER_POLL_TIMEOUT"; + EXPECT_EQ(1, handler.callbackCount) + << "callback should not be invoked this time"; +} + +TEST_F(LooperTest, PollOnce_WhenNonCallbackFdIsSignalled_ReturnsIdent) { + const int expectedIdent = 5; + void* expectedData = this; + + Pipe pipe; + + pipe.writeSignal(); + mLooper->addFd(pipe.receiveFd, expectedIdent, ALOOPER_EVENT_INPUT, NULL, expectedData); + + StopWatch stopWatch("pollOnce"); + int fd; + int events; + void* data; + int result = mLooper->pollOnce(100, &fd, &events, &data); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + ASSERT_EQ(OK, pipe.readSignal()) + << "signal should actually have been written"; + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should be approx. zero"; + EXPECT_EQ(expectedIdent, result) + << "pollOnce result should be the ident of the FD that was signalled"; + EXPECT_EQ(pipe.receiveFd, fd) + << "pollOnce should have returned the received pipe fd"; + EXPECT_EQ(ALOOPER_EVENT_INPUT, events) + << "pollOnce should have returned ALOOPER_EVENT_INPUT as events"; + EXPECT_EQ(expectedData, data) + << "pollOnce should have returned the data"; +} + +TEST_F(LooperTest, AddFd_WhenCallbackAdded_ReturnsOne) { + Pipe pipe; + int result = mLooper->addFd(pipe.receiveFd, 0, ALOOPER_EVENT_INPUT, NULL, NULL); + + EXPECT_EQ(1, result) + << "addFd should return 1 because FD was added"; +} + +TEST_F(LooperTest, AddFd_WhenIdentIsNegativeAndCallbackIsNull_ReturnsError) { + Pipe pipe; + int result = mLooper->addFd(pipe.receiveFd, -1, ALOOPER_EVENT_INPUT, NULL, NULL); + + EXPECT_EQ(-1, result) + << "addFd should return -1 because arguments were invalid"; +} + +TEST_F(LooperTest, AddFd_WhenNoCallbackAndAllowNonCallbacksIsFalse_ReturnsError) { + Pipe pipe; + sp<Looper> looper = new Looper(false /*allowNonCallbacks*/); + int result = looper->addFd(pipe.receiveFd, 0, 0, NULL, NULL); + + EXPECT_EQ(-1, result) + << "addFd should return -1 because arguments were invalid"; +} + +TEST_F(LooperTest, RemoveFd_WhenCallbackNotAdded_ReturnsZero) { + int result = mLooper->removeFd(1); + + EXPECT_EQ(0, result) + << "removeFd should return 0 because FD not registered"; +} + +TEST_F(LooperTest, RemoveFd_WhenCallbackAddedThenRemovedTwice_ReturnsOnceFirstTimeAndReturnsZeroSecondTime) { + Pipe pipe; + StubCallbackHandler handler(false); + handler.setCallback(mLooper, pipe.receiveFd, ALOOPER_EVENT_INPUT); + + // First time. + int result = mLooper->removeFd(pipe.receiveFd); + + EXPECT_EQ(1, result) + << "removeFd should return 1 first time because FD was registered"; + + // Second time. + result = mLooper->removeFd(pipe.receiveFd); + + EXPECT_EQ(0, result) + << "removeFd should return 0 second time because FD was no longer registered"; +} + +TEST_F(LooperTest, PollOnce_WhenCallbackAddedTwice_OnlySecondCallbackShouldBeInvoked) { + Pipe pipe; + StubCallbackHandler handler1(true); + StubCallbackHandler handler2(true); + + handler1.setCallback(mLooper, pipe.receiveFd, ALOOPER_EVENT_INPUT); + handler2.setCallback(mLooper, pipe.receiveFd, ALOOPER_EVENT_INPUT); // replace it + pipe.writeSignal(); // would cause FD to be considered signalled + + StopWatch stopWatch("pollOnce"); + int result = mLooper->pollOnce(100); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + ASSERT_EQ(OK, pipe.readSignal()) + << "signal should actually have been written"; + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. zero because FD was already signalled"; + EXPECT_EQ(ALOOPER_POLL_CALLBACK, result) + << "pollOnce result should be ALOOPER_POLL_CALLBACK because FD was signalled"; + EXPECT_EQ(0, handler1.callbackCount) + << "original handler callback should not be invoked because it was replaced"; + EXPECT_EQ(1, handler2.callbackCount) + << "replacement handler callback should be invoked"; +} + +TEST_F(LooperTest, SendMessage_WhenOneMessageIsEnqueue_ShouldInvokeHandlerDuringNextPoll) { + sp<StubMessageHandler> handler = new StubMessageHandler(); + mLooper->sendMessage(handler, Message(MSG_TEST1)); + + StopWatch stopWatch("pollOnce"); + int result = mLooper->pollOnce(100); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. zero because message was already sent"; + EXPECT_EQ(ALOOPER_POLL_CALLBACK, result) + << "pollOnce result should be ALOOPER_POLL_CALLBACK because message was sent"; + EXPECT_EQ(size_t(1), handler->messages.size()) + << "handled message"; + EXPECT_EQ(MSG_TEST1, handler->messages[0].what) + << "handled message"; +} + +TEST_F(LooperTest, SendMessage_WhenMultipleMessagesAreEnqueued_ShouldInvokeHandlersInOrderDuringNextPoll) { + sp<StubMessageHandler> handler1 = new StubMessageHandler(); + sp<StubMessageHandler> handler2 = new StubMessageHandler(); + mLooper->sendMessage(handler1, Message(MSG_TEST1)); + mLooper->sendMessage(handler2, Message(MSG_TEST2)); + mLooper->sendMessage(handler1, Message(MSG_TEST3)); + mLooper->sendMessage(handler1, Message(MSG_TEST4)); + + StopWatch stopWatch("pollOnce"); + int result = mLooper->pollOnce(1000); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. zero because message was already sent"; + EXPECT_EQ(ALOOPER_POLL_CALLBACK, result) + << "pollOnce result should be ALOOPER_POLL_CALLBACK because message was sent"; + EXPECT_EQ(size_t(3), handler1->messages.size()) + << "handled message"; + EXPECT_EQ(MSG_TEST1, handler1->messages[0].what) + << "handled message"; + EXPECT_EQ(MSG_TEST3, handler1->messages[1].what) + << "handled message"; + EXPECT_EQ(MSG_TEST4, handler1->messages[2].what) + << "handled message"; + EXPECT_EQ(size_t(1), handler2->messages.size()) + << "handled message"; + EXPECT_EQ(MSG_TEST2, handler2->messages[0].what) + << "handled message"; +} + +TEST_F(LooperTest, SendMessageDelayed_WhenSentToTheFuture_ShouldInvokeHandlerAfterDelayTime) { + sp<StubMessageHandler> handler = new StubMessageHandler(); + mLooper->sendMessageDelayed(ms2ns(100), handler, Message(MSG_TEST1)); + + StopWatch stopWatch("pollOnce"); + int result = mLooper->pollOnce(1000); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "first poll should end quickly because next message timeout was computed"; + EXPECT_EQ(ALOOPER_POLL_WAKE, result) + << "pollOnce result should be ALOOPER_POLL_WAKE due to wakeup"; + EXPECT_EQ(size_t(0), handler->messages.size()) + << "no message handled yet"; + + result = mLooper->pollOnce(1000); + elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_EQ(size_t(1), handler->messages.size()) + << "handled message"; + EXPECT_EQ(MSG_TEST1, handler->messages[0].what) + << "handled message"; + EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS) + << "second poll should end around the time of the delayed message dispatch"; + EXPECT_EQ(ALOOPER_POLL_CALLBACK, result) + << "pollOnce result should be ALOOPER_POLL_CALLBACK because message was sent"; + + result = mLooper->pollOnce(100); + elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(100 + 100, elapsedMillis, TIMING_TOLERANCE_MS) + << "third poll should timeout"; + EXPECT_EQ(ALOOPER_POLL_TIMEOUT, result) + << "pollOnce result should be ALOOPER_POLL_TIMEOUT because there were no messages left"; +} + +TEST_F(LooperTest, SendMessageDelayed_WhenSentToThePast_ShouldInvokeHandlerDuringNextPoll) { + sp<StubMessageHandler> handler = new StubMessageHandler(); + mLooper->sendMessageDelayed(ms2ns(-1000), handler, Message(MSG_TEST1)); + + StopWatch stopWatch("pollOnce"); + int result = mLooper->pollOnce(100); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. zero because message was already sent"; + EXPECT_EQ(ALOOPER_POLL_CALLBACK, result) + << "pollOnce result should be ALOOPER_POLL_CALLBACK because message was sent"; + EXPECT_EQ(size_t(1), handler->messages.size()) + << "handled message"; + EXPECT_EQ(MSG_TEST1, handler->messages[0].what) + << "handled message"; +} + +TEST_F(LooperTest, SendMessageDelayed_WhenSentToThePresent_ShouldInvokeHandlerDuringNextPoll) { + sp<StubMessageHandler> handler = new StubMessageHandler(); + mLooper->sendMessageDelayed(0, handler, Message(MSG_TEST1)); + + StopWatch stopWatch("pollOnce"); + int result = mLooper->pollOnce(100); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. zero because message was already sent"; + EXPECT_EQ(ALOOPER_POLL_CALLBACK, result) + << "pollOnce result should be ALOOPER_POLL_CALLBACK because message was sent"; + EXPECT_EQ(size_t(1), handler->messages.size()) + << "handled message"; + EXPECT_EQ(MSG_TEST1, handler->messages[0].what) + << "handled message"; +} + +TEST_F(LooperTest, SendMessageAtTime_WhenSentToTheFuture_ShouldInvokeHandlerAfterDelayTime) { + nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); + sp<StubMessageHandler> handler = new StubMessageHandler(); + mLooper->sendMessageAtTime(now + ms2ns(100), handler, Message(MSG_TEST1)); + + StopWatch stopWatch("pollOnce"); + int result = mLooper->pollOnce(1000); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "first poll should end quickly because next message timeout was computed"; + EXPECT_EQ(ALOOPER_POLL_WAKE, result) + << "pollOnce result should be ALOOPER_POLL_WAKE due to wakeup"; + EXPECT_EQ(size_t(0), handler->messages.size()) + << "no message handled yet"; + + result = mLooper->pollOnce(1000); + elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_EQ(size_t(1), handler->messages.size()) + << "handled message"; + EXPECT_EQ(MSG_TEST1, handler->messages[0].what) + << "handled message"; + EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS) + << "second poll should end around the time of the delayed message dispatch"; + EXPECT_EQ(ALOOPER_POLL_CALLBACK, result) + << "pollOnce result should be ALOOPER_POLL_CALLBACK because message was sent"; + + result = mLooper->pollOnce(100); + elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(100 + 100, elapsedMillis, TIMING_TOLERANCE_MS) + << "third poll should timeout"; + EXPECT_EQ(ALOOPER_POLL_TIMEOUT, result) + << "pollOnce result should be ALOOPER_POLL_TIMEOUT because there were no messages left"; +} + +TEST_F(LooperTest, SendMessageAtTime_WhenSentToThePast_ShouldInvokeHandlerDuringNextPoll) { + nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); + sp<StubMessageHandler> handler = new StubMessageHandler(); + mLooper->sendMessageAtTime(now - ms2ns(1000), handler, Message(MSG_TEST1)); + + StopWatch stopWatch("pollOnce"); + int result = mLooper->pollOnce(100); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. zero because message was already sent"; + EXPECT_EQ(ALOOPER_POLL_CALLBACK, result) + << "pollOnce result should be ALOOPER_POLL_CALLBACK because message was sent"; + EXPECT_EQ(size_t(1), handler->messages.size()) + << "handled message"; + EXPECT_EQ(MSG_TEST1, handler->messages[0].what) + << "handled message"; +} + +TEST_F(LooperTest, SendMessageAtTime_WhenSentToThePresent_ShouldInvokeHandlerDuringNextPoll) { + nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); + sp<StubMessageHandler> handler = new StubMessageHandler(); + mLooper->sendMessageAtTime(now, handler, Message(MSG_TEST1)); + + StopWatch stopWatch("pollOnce"); + int result = mLooper->pollOnce(100); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. zero because message was already sent"; + EXPECT_EQ(ALOOPER_POLL_CALLBACK, result) + << "pollOnce result should be ALOOPER_POLL_CALLBACK because message was sent"; + EXPECT_EQ(size_t(1), handler->messages.size()) + << "handled message"; + EXPECT_EQ(MSG_TEST1, handler->messages[0].what) + << "handled message"; +} + +TEST_F(LooperTest, RemoveMessage_WhenRemovingAllMessagesForHandler_ShouldRemoveThoseMessage) { + sp<StubMessageHandler> handler = new StubMessageHandler(); + mLooper->sendMessage(handler, Message(MSG_TEST1)); + mLooper->sendMessage(handler, Message(MSG_TEST2)); + mLooper->sendMessage(handler, Message(MSG_TEST3)); + mLooper->removeMessages(handler); + + StopWatch stopWatch("pollOnce"); + int result = mLooper->pollOnce(0); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. zero because message was sent so looper was awoken"; + EXPECT_EQ(ALOOPER_POLL_WAKE, result) + << "pollOnce result should be ALOOPER_POLL_WAKE because looper was awoken"; + EXPECT_EQ(size_t(0), handler->messages.size()) + << "no messages to handle"; + + result = mLooper->pollOnce(0); + + EXPECT_EQ(ALOOPER_POLL_TIMEOUT, result) + << "pollOnce result should be ALOOPER_POLL_TIMEOUT because there was nothing to do"; + EXPECT_EQ(size_t(0), handler->messages.size()) + << "no messages to handle"; +} + +TEST_F(LooperTest, RemoveMessage_WhenRemovingSomeMessagesForHandler_ShouldRemoveThoseMessage) { + sp<StubMessageHandler> handler = new StubMessageHandler(); + mLooper->sendMessage(handler, Message(MSG_TEST1)); + mLooper->sendMessage(handler, Message(MSG_TEST2)); + mLooper->sendMessage(handler, Message(MSG_TEST3)); + mLooper->sendMessage(handler, Message(MSG_TEST4)); + mLooper->removeMessages(handler, MSG_TEST3); + mLooper->removeMessages(handler, MSG_TEST1); + + StopWatch stopWatch("pollOnce"); + int result = mLooper->pollOnce(0); + int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); + + EXPECT_NEAR(0, elapsedMillis, TIMING_TOLERANCE_MS) + << "elapsed time should approx. zero because message was sent so looper was awoken"; + EXPECT_EQ(ALOOPER_POLL_CALLBACK, result) + << "pollOnce result should be ALOOPER_POLL_CALLBACK because two messages were sent"; + EXPECT_EQ(size_t(2), handler->messages.size()) + << "no messages to handle"; + EXPECT_EQ(MSG_TEST2, handler->messages[0].what) + << "handled message"; + EXPECT_EQ(MSG_TEST4, handler->messages[1].what) + << "handled message"; + + result = mLooper->pollOnce(0); + + EXPECT_EQ(ALOOPER_POLL_TIMEOUT, result) + << "pollOnce result should be ALOOPER_POLL_TIMEOUT because there was nothing to do"; + EXPECT_EQ(size_t(2), handler->messages.size()) + << "no more messages to handle"; +} + +} // namespace android diff --git a/libutils/tests/LruCache_test.cpp b/libutils/tests/LruCache_test.cpp new file mode 100644 index 0000000..e573952 --- /dev/null +++ b/libutils/tests/LruCache_test.cpp @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdlib.h> +#include <utils/JenkinsHash.h> +#include <utils/LruCache.h> +#include <cutils/log.h> +#include <gtest/gtest.h> + +namespace android { + +typedef int SimpleKey; +typedef const char* StringValue; + +struct ComplexKey { + int k; + + explicit ComplexKey(int k) : k(k) { + instanceCount += 1; + } + + ComplexKey(const ComplexKey& other) : k(other.k) { + instanceCount += 1; + } + + ~ComplexKey() { + instanceCount -= 1; + } + + bool operator ==(const ComplexKey& other) const { + return k == other.k; + } + + bool operator !=(const ComplexKey& other) const { + return k != other.k; + } + + static ssize_t instanceCount; +}; + +ssize_t ComplexKey::instanceCount = 0; + +template<> inline hash_t hash_type(const ComplexKey& value) { + return hash_type(value.k); +} + +struct ComplexValue { + int v; + + explicit ComplexValue(int v) : v(v) { + instanceCount += 1; + } + + ComplexValue(const ComplexValue& other) : v(other.v) { + instanceCount += 1; + } + + ~ComplexValue() { + instanceCount -= 1; + } + + static ssize_t instanceCount; +}; + +ssize_t ComplexValue::instanceCount = 0; + +typedef LruCache<ComplexKey, ComplexValue> ComplexCache; + +class EntryRemovedCallback : public OnEntryRemoved<SimpleKey, StringValue> { +public: + EntryRemovedCallback() : callbackCount(0), lastKey(-1), lastValue(NULL) { } + ~EntryRemovedCallback() {} + void operator()(SimpleKey& k, StringValue& v) { + callbackCount += 1; + lastKey = k; + lastValue = v; + } + ssize_t callbackCount; + SimpleKey lastKey; + StringValue lastValue; +}; + +class LruCacheTest : public testing::Test { +protected: + virtual void SetUp() { + ComplexKey::instanceCount = 0; + ComplexValue::instanceCount = 0; + } + + virtual void TearDown() { + ASSERT_NO_FATAL_FAILURE(assertInstanceCount(0, 0)); + } + + void assertInstanceCount(ssize_t keys, ssize_t values) { + if (keys != ComplexKey::instanceCount || values != ComplexValue::instanceCount) { + FAIL() << "Expected " << keys << " keys and " << values << " values " + "but there were actually " << ComplexKey::instanceCount << " keys and " + << ComplexValue::instanceCount << " values"; + } + } +}; + +TEST_F(LruCacheTest, Empty) { + LruCache<SimpleKey, StringValue> cache(100); + + EXPECT_EQ(NULL, cache.get(0)); + EXPECT_EQ(0u, cache.size()); +} + +TEST_F(LruCacheTest, Simple) { + LruCache<SimpleKey, StringValue> cache(100); + + cache.put(1, "one"); + cache.put(2, "two"); + cache.put(3, "three"); + EXPECT_STREQ("one", cache.get(1)); + EXPECT_STREQ("two", cache.get(2)); + EXPECT_STREQ("three", cache.get(3)); + EXPECT_EQ(3u, cache.size()); +} + +TEST_F(LruCacheTest, MaxCapacity) { + LruCache<SimpleKey, StringValue> cache(2); + + cache.put(1, "one"); + cache.put(2, "two"); + cache.put(3, "three"); + EXPECT_EQ(NULL, cache.get(1)); + EXPECT_STREQ("two", cache.get(2)); + EXPECT_STREQ("three", cache.get(3)); + EXPECT_EQ(2u, cache.size()); +} + +TEST_F(LruCacheTest, RemoveLru) { + LruCache<SimpleKey, StringValue> cache(100); + + cache.put(1, "one"); + cache.put(2, "two"); + cache.put(3, "three"); + cache.removeOldest(); + EXPECT_EQ(NULL, cache.get(1)); + EXPECT_STREQ("two", cache.get(2)); + EXPECT_STREQ("three", cache.get(3)); + EXPECT_EQ(2u, cache.size()); +} + +TEST_F(LruCacheTest, GetUpdatesLru) { + LruCache<SimpleKey, StringValue> cache(100); + + cache.put(1, "one"); + cache.put(2, "two"); + cache.put(3, "three"); + EXPECT_STREQ("one", cache.get(1)); + cache.removeOldest(); + EXPECT_STREQ("one", cache.get(1)); + EXPECT_EQ(NULL, cache.get(2)); + EXPECT_STREQ("three", cache.get(3)); + EXPECT_EQ(2u, cache.size()); +} + +uint32_t hash_int(int x) { + return JenkinsHashWhiten(JenkinsHashMix(0, x)); +} + +TEST_F(LruCacheTest, StressTest) { + const size_t kCacheSize = 512; + LruCache<SimpleKey, StringValue> cache(512); + const size_t kNumKeys = 16 * 1024; + const size_t kNumIters = 100000; + char* strings[kNumKeys]; + + for (size_t i = 0; i < kNumKeys; i++) { + strings[i] = (char *)malloc(16); + sprintf(strings[i], "%d", i); + } + + srandom(12345); + int hitCount = 0; + for (size_t i = 0; i < kNumIters; i++) { + int index = random() % kNumKeys; + uint32_t key = hash_int(index); + const char *val = cache.get(key); + if (val != NULL) { + EXPECT_EQ(strings[index], val); + hitCount++; + } else { + cache.put(key, strings[index]); + } + } + size_t expectedHitCount = kNumIters * kCacheSize / kNumKeys; + EXPECT_LT(int(expectedHitCount * 0.9), hitCount); + EXPECT_GT(int(expectedHitCount * 1.1), hitCount); + EXPECT_EQ(kCacheSize, cache.size()); + + for (size_t i = 0; i < kNumKeys; i++) { + free((void *)strings[i]); + } +} + +TEST_F(LruCacheTest, NoLeak) { + ComplexCache cache(100); + + cache.put(ComplexKey(0), ComplexValue(0)); + cache.put(ComplexKey(1), ComplexValue(1)); + EXPECT_EQ(2, cache.size()); + assertInstanceCount(2, 3); // the null value counts as an instance +} + +TEST_F(LruCacheTest, Clear) { + ComplexCache cache(100); + + cache.put(ComplexKey(0), ComplexValue(0)); + cache.put(ComplexKey(1), ComplexValue(1)); + EXPECT_EQ(2, cache.size()); + assertInstanceCount(2, 3); + cache.clear(); + assertInstanceCount(0, 1); +} + +TEST_F(LruCacheTest, ClearNoDoubleFree) { + { + ComplexCache cache(100); + + cache.put(ComplexKey(0), ComplexValue(0)); + cache.put(ComplexKey(1), ComplexValue(1)); + EXPECT_EQ(2, cache.size()); + assertInstanceCount(2, 3); + cache.removeOldest(); + cache.clear(); + assertInstanceCount(0, 1); + } + assertInstanceCount(0, 0); +} + +TEST_F(LruCacheTest, ClearReuseOk) { + ComplexCache cache(100); + + cache.put(ComplexKey(0), ComplexValue(0)); + cache.put(ComplexKey(1), ComplexValue(1)); + EXPECT_EQ(2, cache.size()); + assertInstanceCount(2, 3); + cache.clear(); + assertInstanceCount(0, 1); + cache.put(ComplexKey(0), ComplexValue(0)); + cache.put(ComplexKey(1), ComplexValue(1)); + EXPECT_EQ(2, cache.size()); + assertInstanceCount(2, 3); +} + +TEST_F(LruCacheTest, Callback) { + LruCache<SimpleKey, StringValue> cache(100); + EntryRemovedCallback callback; + cache.setOnEntryRemovedListener(&callback); + + cache.put(1, "one"); + cache.put(2, "two"); + cache.put(3, "three"); + EXPECT_EQ(3, cache.size()); + cache.removeOldest(); + EXPECT_EQ(1, callback.callbackCount); + EXPECT_EQ(1, callback.lastKey); + EXPECT_STREQ("one", callback.lastValue); +} + +TEST_F(LruCacheTest, CallbackOnClear) { + LruCache<SimpleKey, StringValue> cache(100); + EntryRemovedCallback callback; + cache.setOnEntryRemovedListener(&callback); + + cache.put(1, "one"); + cache.put(2, "two"); + cache.put(3, "three"); + EXPECT_EQ(3, cache.size()); + cache.clear(); + EXPECT_EQ(3, callback.callbackCount); +} + +} diff --git a/libutils/tests/String8_test.cpp b/libutils/tests/String8_test.cpp new file mode 100644 index 0000000..c42c68d --- /dev/null +++ b/libutils/tests/String8_test.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "String8_test" +#include <utils/Log.h> +#include <utils/String8.h> + +#include <gtest/gtest.h> + +namespace android { + +class String8Test : public testing::Test { +protected: + virtual void SetUp() { + } + + virtual void TearDown() { + } +}; + +TEST_F(String8Test, Cstr) { + String8 tmp("Hello, world!"); + + EXPECT_STREQ(tmp.string(), "Hello, world!"); +} + +TEST_F(String8Test, OperatorPlus) { + String8 src1("Hello, "); + + // Test adding String8 + const char* + const char* ccsrc2 = "world!"; + String8 dst1 = src1 + ccsrc2; + EXPECT_STREQ(dst1.string(), "Hello, world!"); + EXPECT_STREQ(src1.string(), "Hello, "); + EXPECT_STREQ(ccsrc2, "world!"); + + // Test adding String8 + String8 + String8 ssrc2("world!"); + String8 dst2 = src1 + ssrc2; + EXPECT_STREQ(dst2.string(), "Hello, world!"); + EXPECT_STREQ(src1.string(), "Hello, "); + EXPECT_STREQ(ssrc2.string(), "world!"); +} + +TEST_F(String8Test, OperatorPlusEquals) { + String8 src1("My voice"); + + // Testing String8 += String8 + String8 src2(" is my passport."); + src1 += src2; + EXPECT_STREQ(src1.string(), "My voice is my passport."); + EXPECT_STREQ(src2.string(), " is my passport."); + + // Adding const char* to the previous string. + const char* src3 = " Verify me."; + src1 += src3; + EXPECT_STREQ(src1.string(), "My voice is my passport. Verify me."); + EXPECT_STREQ(src2.string(), " is my passport."); + EXPECT_STREQ(src3, " Verify me."); +} + +} diff --git a/libutils/tests/TestHelpers.h b/libutils/tests/TestHelpers.h new file mode 100644 index 0000000..d8e985e --- /dev/null +++ b/libutils/tests/TestHelpers.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TESTHELPERS_H +#define TESTHELPERS_H + +#include <utils/threads.h> + +namespace android { + +class Pipe { +public: + int sendFd; + int receiveFd; + + Pipe() { + int fds[2]; + ::pipe(fds); + + receiveFd = fds[0]; + sendFd = fds[1]; + } + + ~Pipe() { + if (sendFd != -1) { + ::close(sendFd); + } + + if (receiveFd != -1) { + ::close(receiveFd); + } + } + + status_t writeSignal() { + ssize_t nWritten = ::write(sendFd, "*", 1); + return nWritten == 1 ? 0 : -errno; + } + + status_t readSignal() { + char buf[1]; + ssize_t nRead = ::read(receiveFd, buf, 1); + return nRead == 1 ? 0 : nRead == 0 ? -EPIPE : -errno; + } +}; + +class DelayedTask : public Thread { + int mDelayMillis; + +public: + DelayedTask(int delayMillis) : mDelayMillis(delayMillis) { } + +protected: + virtual ~DelayedTask() { } + + virtual void doTask() = 0; + + virtual bool threadLoop() { + usleep(mDelayMillis * 1000); + doTask(); + return false; + } +}; + +} // namespace android + +#endif // TESTHELPERS_H diff --git a/libutils/tests/Unicode_test.cpp b/libutils/tests/Unicode_test.cpp new file mode 100644 index 0000000..18c130c --- /dev/null +++ b/libutils/tests/Unicode_test.cpp @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "Unicode_test" +#include <utils/Log.h> +#include <utils/Unicode.h> + +#include <gtest/gtest.h> + +namespace android { + +class UnicodeTest : public testing::Test { +protected: + virtual void SetUp() { + } + + virtual void TearDown() { + } +}; + +TEST_F(UnicodeTest, UTF8toUTF16ZeroLength) { + ssize_t measured; + + const uint8_t str[] = { }; + + measured = utf8_to_utf16_length(str, 0); + EXPECT_EQ(0, measured) + << "Zero length input should return zero length output."; +} + +TEST_F(UnicodeTest, UTF8toUTF16ASCIILength) { + ssize_t measured; + + // U+0030 or ASCII '0' + const uint8_t str[] = { 0x30 }; + + measured = utf8_to_utf16_length(str, sizeof(str)); + EXPECT_EQ(1, measured) + << "ASCII glyphs should have a length of 1 char16_t"; +} + +TEST_F(UnicodeTest, UTF8toUTF16Plane1Length) { + ssize_t measured; + + // U+2323 SMILE + const uint8_t str[] = { 0xE2, 0x8C, 0xA3 }; + + measured = utf8_to_utf16_length(str, sizeof(str)); + EXPECT_EQ(1, measured) + << "Plane 1 glyphs should have a length of 1 char16_t"; +} + +TEST_F(UnicodeTest, UTF8toUTF16SurrogateLength) { + ssize_t measured; + + // U+10000 + const uint8_t str[] = { 0xF0, 0x90, 0x80, 0x80 }; + + measured = utf8_to_utf16_length(str, sizeof(str)); + EXPECT_EQ(2, measured) + << "Surrogate pairs should have a length of 2 char16_t"; +} + +TEST_F(UnicodeTest, UTF8toUTF16TruncatedUTF8) { + ssize_t measured; + + // Truncated U+2323 SMILE + // U+2323 SMILE + const uint8_t str[] = { 0xE2, 0x8C }; + + measured = utf8_to_utf16_length(str, sizeof(str)); + EXPECT_EQ(-1, measured) + << "Truncated UTF-8 should return -1 to indicate invalid"; +} + +TEST_F(UnicodeTest, UTF8toUTF16Normal) { + const uint8_t str[] = { + 0x30, // U+0030, 1 UTF-16 character + 0xC4, 0x80, // U+0100, 1 UTF-16 character + 0xE2, 0x8C, 0xA3, // U+2323, 1 UTF-16 character + 0xF0, 0x90, 0x80, 0x80, // U+10000, 2 UTF-16 character + }; + + char16_t output[1 + 1 + 1 + 2 + 1]; // Room for NULL + + utf8_to_utf16(str, sizeof(str), output); + + EXPECT_EQ(0x0030, output[0]) + << "should be U+0030"; + EXPECT_EQ(0x0100, output[1]) + << "should be U+0100"; + EXPECT_EQ(0x2323, output[2]) + << "should be U+2323"; + EXPECT_EQ(0xD800, output[3]) + << "should be first half of surrogate U+10000"; + EXPECT_EQ(0xDC00, output[4]) + << "should be second half of surrogate U+10000"; + EXPECT_EQ(NULL, output[5]) + << "should be NULL terminated"; +} + +} diff --git a/libutils/tests/Vector_test.cpp b/libutils/tests/Vector_test.cpp new file mode 100644 index 0000000..d29c054 --- /dev/null +++ b/libutils/tests/Vector_test.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "Vector_test" + +#include <utils/Vector.h> +#include <cutils/log.h> +#include <gtest/gtest.h> +#include <unistd.h> + +namespace android { + +class VectorTest : public testing::Test { +protected: + virtual void SetUp() { + } + + virtual void TearDown() { + } + +public: +}; + + +TEST_F(VectorTest, CopyOnWrite_CopyAndAddElements) { + + Vector<int> vector; + Vector<int> other; + vector.setCapacity(8); + + vector.add(1); + vector.add(2); + vector.add(3); + + EXPECT_EQ(vector.size(), 3); + + // copy the vector + other = vector; + + EXPECT_EQ(other.size(), 3); + + // add an element to the first vector + vector.add(4); + + // make sure the sizes are correct + EXPECT_EQ(vector.size(), 4); + EXPECT_EQ(other.size(), 3); + + // add an element to the copy + other.add(5); + + // make sure the sizes are correct + EXPECT_EQ(vector.size(), 4); + EXPECT_EQ(other.size(), 4); + + // make sure the content of both vectors are correct + EXPECT_EQ(vector[3], 4); + EXPECT_EQ(other[3], 5); +} + + +} // namespace android |