diff --git a/.classpath b/.classpath
index dcf4178c..06bdd110 100644
--- a/.classpath
+++ b/.classpath
@@ -3,7 +3,6 @@
-
diff --git a/jni/Android.mk b/jni/Android.mk
index 25109a1f..9488e6e0 100644
--- a/jni/Android.mk
+++ b/jni/Android.mk
@@ -26,7 +26,10 @@ LOCAL_SRC_FILES:= \
net_sqlcipher_database_SQLiteQuery.cpp \
net_sqlcipher_database_SQLiteStatement.cpp \
net_sqlcipher_CursorWindow.cpp \
- CursorWindow.cpp
+ CursorWindow.cpp \
+ Unicode.cpp \
+ net_sqlcipher_AshmemCursorWindow.cpp \
+ AshmemCursorWindow.cpp
# net_sqlcipher_database_sqlcipher_SQLiteDebug.cpp
LOCAL_C_INCLUDES += \
@@ -52,7 +55,7 @@ LOCAL_CFLAGS += -U__APPLE__
LOCAL_LDFLAGS += -L../external/android-libs/$(TARGET_ARCH_ABI) -L../external/libs/$(TARGET_ARCH_ABI)/
# libs from the NDK
-LOCAL_LDLIBS += -ldl -llog
+LOCAL_LDLIBS += -ldl -llog -lcutils
# libnativehelper and libandroid_runtime are included with Android but not the NDK
LOCAL_LDLIBS += -lnativehelper -landroid_runtime -lutils -lbinder
# these are build in the ../external section
diff --git a/jni/Application.mk b/jni/Application.mk
index ea9d6e28..68c67a66 100644
--- a/jni/Application.mk
+++ b/jni/Application.mk
@@ -4,3 +4,4 @@ APP_BUILD_SCRIPT := $(APP_PROJECT_PATH)/Android.mk
# fixes this error when building external/android-sqlite/android/sqlite3_android.cpp
# icu4c/common/unicode/std_string.h:39:18: error: string: No such file or directory
APP_STL := stlport_shared
+NDK_TOOLCHAIN_VERSION=4.4.3
diff --git a/jni/AshmemCursorWindow.cpp b/jni/AshmemCursorWindow.cpp
new file mode 100644
index 00000000..b750a902
--- /dev/null
+++ b/jni/AshmemCursorWindow.cpp
@@ -0,0 +1,355 @@
+/*
+ * Copyright (C) 2006-2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "AshmemCursorWindow"
+
+#include
+#include
+#include
+#include "AshmemCursorWindow.h"
+
+#include
+#include
+
+#include
+#include
+#include
+
+using namespace android;
+
+namespace sqlcipher {
+
+AshmemCursorWindow::AshmemCursorWindow(const android::String8& name, int ashmemFd,
+ void* data, size_t size, bool readOnly) :
+ mName(name), mAshmemFd(ashmemFd), mData(data), mSize(size), mReadOnly(readOnly) {
+ mHeader = static_cast(mData);
+}
+
+AshmemCursorWindow::~AshmemCursorWindow() {
+ ::munmap(mData, mSize);
+ ::close(mAshmemFd);
+}
+
+status_t AshmemCursorWindow::create(const android::String8& name, size_t size, AshmemCursorWindow** outAshmemCursorWindow) {
+ android::String8 ashmemName("AshmemCursorWindow: ");
+ ashmemName.append(name);
+
+ status_t result;
+ int ashmemFd = ashmem_create_region(ashmemName.string(), size);
+ if (ashmemFd < 0) {
+ result = -errno;
+ } else {
+ result = ashmem_set_prot_region(ashmemFd, PROT_READ | PROT_WRITE);
+ if (result >= 0) {
+ void* data = ::mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, ashmemFd, 0);
+ if (data == MAP_FAILED) {
+ result = -errno;
+ } else {
+ result = ashmem_set_prot_region(ashmemFd, PROT_READ);
+ if (result >= 0) {
+ AshmemCursorWindow* window = new AshmemCursorWindow(name, ashmemFd,
+ data, size, false /*readOnly*/);
+ result = window->clear();
+ if (!result) {
+ LOG_WINDOW("Created new AshmemCursorWindow: freeOffset=%d, "
+ "numRows=%d, numColumns=%d, mSize=%d, mData=%p",
+ window->mHeader->freeOffset,
+ window->mHeader->numRows,
+ window->mHeader->numColumns,
+ window->mSize, window->mData);
+ *outAshmemCursorWindow = window;
+ return OK;
+ }
+ delete window;
+ }
+ }
+ ::munmap(data, size);
+ }
+ ::close(ashmemFd);
+ }
+ *outAshmemCursorWindow = NULL;
+ return result;
+}
+
+status_t AshmemCursorWindow::createFromParcel(Parcel* parcel, AshmemCursorWindow** outCursorWindow) {
+ android::String8 name = parcel->readString8();
+
+ status_t result;
+ int ashmemFd = parcel->readFileDescriptor();
+ if (ashmemFd == int(BAD_TYPE)) {
+ result = BAD_TYPE;
+ } else {
+ ssize_t size = ashmem_get_size_region(ashmemFd);
+ if (size < 0) {
+ result = UNKNOWN_ERROR;
+ } else {
+ int dupAshmemFd = ::dup(ashmemFd);
+ if (dupAshmemFd < 0) {
+ result = -errno;
+ } else {
+ void* data = ::mmap(NULL, size, PROT_READ, MAP_SHARED, dupAshmemFd, 0);
+ if (data == MAP_FAILED) {
+ result = -errno;
+ } else {
+ AshmemCursorWindow* window = new AshmemCursorWindow(name, dupAshmemFd,
+ data, size, true /*readOnly*/);
+ LOG_WINDOW("Created AshmemCursorWindow from parcel: freeOffset=%d, "
+ "numRows=%d, numColumns=%d, mSize=%d, mData=%p",
+ window->mHeader->freeOffset,
+ window->mHeader->numRows,
+ window->mHeader->numColumns,
+ window->mSize, window->mData);
+ *outCursorWindow = window;
+ return OK;
+ }
+ ::close(dupAshmemFd);
+ }
+ }
+ }
+ *outCursorWindow = NULL;
+ return result;
+}
+
+status_t AshmemCursorWindow::writeToParcel(Parcel* parcel) {
+ status_t status = parcel->writeString8(mName);
+ if (!status) {
+ status = parcel->writeDupFileDescriptor(mAshmemFd);
+ }
+ return status;
+}
+
+status_t AshmemCursorWindow::clear() {
+ if (mReadOnly) {
+ return INVALID_OPERATION;
+ }
+
+ mHeader->freeOffset = sizeof(Header) + sizeof(RowSlotChunk);
+ mHeader->firstChunkOffset = sizeof(Header);
+ mHeader->numRows = 0;
+ mHeader->numColumns = 0;
+
+ RowSlotChunk* firstChunk = static_cast(offsetToPtr(mHeader->firstChunkOffset));
+ firstChunk->nextChunkOffset = 0;
+ return OK;
+}
+
+status_t AshmemCursorWindow::setNumColumns(uint32_t numColumns) {
+ if (mReadOnly) {
+ return INVALID_OPERATION;
+ }
+
+ uint32_t cur = mHeader->numColumns;
+ if ((cur > 0 || mHeader->numRows > 0) && cur != numColumns) {
+ LOG_WINDOW("Trying to go from %d columns to %d", cur, numColumns);
+ return INVALID_OPERATION;
+ }
+ mHeader->numColumns = numColumns;
+ return OK;
+}
+
+status_t AshmemCursorWindow::allocRow() {
+ if (mReadOnly) {
+ return INVALID_OPERATION;
+ }
+
+ // Fill in the row slot
+ RowSlot* rowSlot = allocRowSlot();
+ if (rowSlot == NULL) {
+ return NO_MEMORY;
+ }
+
+ // Allocate the slots for the field directory
+ size_t fieldDirSize = mHeader->numColumns * sizeof(FieldSlot);
+ uint32_t fieldDirOffset = alloc(fieldDirSize, true /*aligned*/);
+ if (!fieldDirOffset) {
+ mHeader->numRows--;
+ LOG_WINDOW("The row failed, so back out the new row accounting "
+ "from allocRowSlot %d", mHeader->numRows);
+ return NO_MEMORY;
+ }
+ FieldSlot* fieldDir = static_cast(offsetToPtr(fieldDirOffset));
+ memset(fieldDir, 0, fieldDirSize);
+
+ LOG_WINDOW("Allocated row %u, rowSlot is at offset %u, fieldDir is %d bytes at offset %u\n",
+ mHeader->numRows - 1, offsetFromPtr(rowSlot), fieldDirSize, fieldDirOffset);
+ rowSlot->offset = fieldDirOffset;
+ return OK;
+}
+
+status_t AshmemCursorWindow::freeLastRow() {
+ if (mReadOnly) {
+ return INVALID_OPERATION;
+ }
+
+ if (mHeader->numRows > 0) {
+ mHeader->numRows--;
+ }
+ return OK;
+}
+
+uint32_t AshmemCursorWindow::alloc(size_t size, bool aligned) {
+ uint32_t padding;
+ if (aligned) {
+ // 4 byte alignment
+ padding = (~mHeader->freeOffset + 1) & 3;
+ } else {
+ padding = 0;
+ }
+
+ uint32_t offset = mHeader->freeOffset + padding;
+ uint32_t nextFreeOffset = offset + size;
+ if (nextFreeOffset > mSize) {
+ LOG_WINDOW("Window is full: requested allocation %d bytes, "
+ "free space %d bytes, window size %d bytes",
+ size, freeSpace(), mSize);
+ return 0;
+ }
+
+ mHeader->freeOffset = nextFreeOffset;
+ return offset;
+}
+
+AshmemCursorWindow::RowSlot* AshmemCursorWindow::getRowSlot(uint32_t row) {
+ uint32_t chunkPos = row;
+ RowSlotChunk* chunk = static_cast(
+ offsetToPtr(mHeader->firstChunkOffset));
+ while (chunkPos >= NEW_ROW_SLOT_CHUNK_NUM_ROWS) {
+ chunk = static_cast(offsetToPtr(chunk->nextChunkOffset));
+ chunkPos -= NEW_ROW_SLOT_CHUNK_NUM_ROWS;
+ }
+ return &chunk->slots[chunkPos];
+}
+
+AshmemCursorWindow::RowSlot* AshmemCursorWindow::allocRowSlot() {
+ uint32_t chunkPos = mHeader->numRows;
+ RowSlotChunk* chunk = static_cast(
+ offsetToPtr(mHeader->firstChunkOffset));
+ while (chunkPos > NEW_ROW_SLOT_CHUNK_NUM_ROWS) {
+ chunk = static_cast(offsetToPtr(chunk->nextChunkOffset));
+ chunkPos -= NEW_ROW_SLOT_CHUNK_NUM_ROWS;
+ }
+ if (chunkPos == NEW_ROW_SLOT_CHUNK_NUM_ROWS) {
+ if (!chunk->nextChunkOffset) {
+ chunk->nextChunkOffset = alloc(sizeof(RowSlotChunk), true /*aligned*/);
+ if (!chunk->nextChunkOffset) {
+ return NULL;
+ }
+ }
+ chunk = static_cast(offsetToPtr(chunk->nextChunkOffset));
+ chunk->nextChunkOffset = 0;
+ chunkPos = 0;
+ }
+ mHeader->numRows += 1;
+ return &chunk->slots[chunkPos];
+}
+
+AshmemCursorWindow::FieldSlot* AshmemCursorWindow::getFieldSlot(uint32_t row, uint32_t column) {
+ if (row >= mHeader->numRows || column >= mHeader->numColumns) {
+ LOG_WINDOW("Failed to read row %d, column %d from a AshmemCursorWindow which "
+ "has %d rows, %d columns.",
+ row, column, mHeader->numRows, mHeader->numColumns);
+ return NULL;
+ }
+ RowSlot* rowSlot = getRowSlot(row);
+ if (!rowSlot) {
+ LOG_WINDOW("Failed to find rowSlot for row %d.", row);
+ return NULL;
+ }
+ FieldSlot* fieldDir = static_cast(offsetToPtr(rowSlot->offset));
+ return &fieldDir[column];
+}
+
+status_t AshmemCursorWindow::putBlob(uint32_t row, uint32_t column, const void* value, size_t size) {
+ return putBlobOrString(row, column, value, size, NEW_FIELD_TYPE_BLOB);
+}
+
+status_t AshmemCursorWindow::putString(uint32_t row, uint32_t column, const char* value,
+ size_t sizeIncludingNull) {
+ return putBlobOrString(row, column, value, sizeIncludingNull, NEW_FIELD_TYPE_STRING);
+}
+
+status_t AshmemCursorWindow::putBlobOrString(uint32_t row, uint32_t column,
+ const void* value, size_t size, int32_t type) {
+ if (mReadOnly) {
+ return INVALID_OPERATION;
+ }
+
+ FieldSlot* fieldSlot = getFieldSlot(row, column);
+ if (!fieldSlot) {
+ return BAD_VALUE;
+ }
+
+ uint32_t offset = alloc(size);
+ if (!offset) {
+ return NO_MEMORY;
+ }
+
+ memcpy(offsetToPtr(offset), value, size);
+
+ fieldSlot->type = type;
+ fieldSlot->data.buffer.offset = offset;
+ fieldSlot->data.buffer.size = size;
+ return OK;
+}
+
+status_t AshmemCursorWindow::putLong(uint32_t row, uint32_t column, int64_t value) {
+ if (mReadOnly) {
+ return INVALID_OPERATION;
+ }
+
+ FieldSlot* fieldSlot = getFieldSlot(row, column);
+ if (!fieldSlot) {
+ return BAD_VALUE;
+ }
+
+ fieldSlot->type = NEW_FIELD_TYPE_INTEGER;
+ fieldSlot->data.l = value;
+ return OK;
+}
+
+status_t AshmemCursorWindow::putDouble(uint32_t row, uint32_t column, double value) {
+ if (mReadOnly) {
+ return INVALID_OPERATION;
+ }
+
+ FieldSlot* fieldSlot = getFieldSlot(row, column);
+ if (!fieldSlot) {
+ return BAD_VALUE;
+ }
+
+ fieldSlot->type = NEW_FIELD_TYPE_FLOAT;
+ fieldSlot->data.d = value;
+ return OK;
+}
+
+status_t AshmemCursorWindow::putNull(uint32_t row, uint32_t column) {
+ if (mReadOnly) {
+ return INVALID_OPERATION;
+ }
+
+ FieldSlot* fieldSlot = getFieldSlot(row, column);
+ if (!fieldSlot) {
+ return BAD_VALUE;
+ }
+
+ fieldSlot->type = NEW_FIELD_TYPE_NULL;
+ fieldSlot->data.buffer.offset = 0;
+ fieldSlot->data.buffer.size = 0;
+ return OK;
+}
+
+}; // namespace android
diff --git a/jni/AshmemCursorWindow.h b/jni/AshmemCursorWindow.h
new file mode 100644
index 00000000..f0899318
--- /dev/null
+++ b/jni/AshmemCursorWindow.h
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID__DATABASE_NEWWINDOW_H
+#define _ANDROID__DATABASE_NEWWINDOW_H
+
+#include
+
+#include
+#include
+#include
+
+#include
+#include
+
+#if LOG_NDEBUG
+
+#define IF_LOG_WINDOW() if (false)
+#define LOG_WINDOW(...)
+
+#else
+
+#define IF_LOG_WINDOW() IF_LOG(LOG_DEBUG, "AshmemCursorWindow")
+#define LOG_WINDOW(...) LOG(LOG_DEBUG, "AshmemCursorWindow", __VA_ARGS__)
+
+#endif
+
+using namespace android;
+
+namespace sqlcipher {
+
+/**
+ * This class stores a set of rows from a database in a buffer. The begining of the
+ * window has first chunk of RowSlots, which are offsets to the row directory, followed by
+ * an offset to the next chunk in a linked-list of additional chunk of RowSlots in case
+ * the pre-allocated chunk isn't big enough to refer to all rows. Each row directory has a
+ * FieldSlot per column, which has the size, offset, and type of the data for that field.
+ * Note that the data types come from sqlite3.h.
+ *
+ * Strings are stored in UTF-8.
+ */
+class AshmemCursorWindow {
+ AshmemCursorWindow(const String8& name, int ashmemFd,
+ void* data, size_t size, bool readOnly);
+
+public:
+ /* Field types. */
+ enum {
+ NEW_FIELD_TYPE_NULL = 0,
+ NEW_FIELD_TYPE_INTEGER = 1,
+ NEW_FIELD_TYPE_FLOAT = 2,
+ NEW_FIELD_TYPE_STRING = 3,
+ NEW_FIELD_TYPE_BLOB = 4,
+ };
+
+ /* Opaque type that describes a field slot. */
+ struct FieldSlot {
+ private:
+ int32_t type;
+ union {
+ double d;
+ int64_t l;
+ struct {
+ uint32_t offset;
+ uint32_t size;
+ } buffer;
+ } data;
+
+ friend class AshmemCursorWindow;
+ } __attribute((packed));
+
+ ~AshmemCursorWindow();
+
+ static status_t create(const String8& name, size_t size, AshmemCursorWindow** outCursorWindow);
+ static status_t createFromParcel(Parcel* parcel, AshmemCursorWindow** outCursorWindow);
+
+ status_t writeToParcel(Parcel* parcel);
+
+ inline String8 name() { return mName; }
+ inline size_t size() { return mSize; }
+ inline size_t freeSpace() { return mSize - mHeader->freeOffset; }
+ inline uint32_t getNumRows() { return mHeader->numRows; }
+ inline uint32_t getNumColumns() { return mHeader->numColumns; }
+
+ status_t clear();
+ status_t setNumColumns(uint32_t numColumns);
+
+ /**
+ * Allocate a row slot and its directory.
+ * The row is initialized will null entries for each field.
+ */
+ status_t allocRow();
+ status_t freeLastRow();
+
+ status_t putBlob(uint32_t row, uint32_t column, const void* value, size_t size);
+ status_t putString(uint32_t row, uint32_t column, const char* value, size_t sizeIncludingNull);
+ status_t putLong(uint32_t row, uint32_t column, int64_t value);
+ status_t putDouble(uint32_t row, uint32_t column, double value);
+ status_t putNull(uint32_t row, uint32_t column);
+
+ /**
+ * Gets the field slot at the specified row and column.
+ * Returns null if the requested row or column is not in the window.
+ */
+ FieldSlot* getFieldSlot(uint32_t row, uint32_t column);
+
+ inline int32_t getFieldSlotType(FieldSlot* fieldSlot) {
+ return fieldSlot->type;
+ }
+
+ inline int64_t getFieldSlotValueLong(FieldSlot* fieldSlot) {
+ return fieldSlot->data.l;
+ }
+
+ inline double getFieldSlotValueDouble(FieldSlot* fieldSlot) {
+ return fieldSlot->data.d;
+ }
+
+ inline const char* getFieldSlotValueString(FieldSlot* fieldSlot,
+ size_t* outSizeIncludingNull) {
+ *outSizeIncludingNull = fieldSlot->data.buffer.size;
+ return static_cast(offsetToPtr(fieldSlot->data.buffer.offset));
+ }
+
+ inline const void* getFieldSlotValueBlob(FieldSlot* fieldSlot, size_t* outSize) {
+ *outSize = fieldSlot->data.buffer.size;
+ return offsetToPtr(fieldSlot->data.buffer.offset);
+ }
+
+private:
+ static const size_t NEW_ROW_SLOT_CHUNK_NUM_ROWS = 100;
+
+ struct Header {
+ // Offset of the lowest unused byte in the window.
+ uint32_t freeOffset;
+
+ // Offset of the first row slot chunk.
+ uint32_t firstChunkOffset;
+
+ uint32_t numRows;
+ uint32_t numColumns;
+ };
+
+ struct RowSlot {
+ uint32_t offset;
+ };
+
+ struct RowSlotChunk {
+ RowSlot slots[NEW_ROW_SLOT_CHUNK_NUM_ROWS];
+ uint32_t nextChunkOffset;
+ };
+
+ String8 mName;
+ int mAshmemFd;
+ void* mData;
+ size_t mSize;
+ bool mReadOnly;
+ Header* mHeader;
+
+ inline void* offsetToPtr(uint32_t offset) {
+ return static_cast(mData) + offset;
+ }
+
+ inline uint32_t offsetFromPtr(void* ptr) {
+ return static_cast(ptr) - static_cast(mData);
+ }
+
+ /**
+ * Allocate a portion of the window. Returns the offset
+ * of the allocation, or 0 if there isn't enough space.
+ * If aligned is true, the allocation gets 4 byte alignment.
+ */
+ uint32_t alloc(size_t size, bool aligned = false);
+
+ RowSlot* getRowSlot(uint32_t row);
+ RowSlot* allocRowSlot();
+
+ status_t putBlobOrString(uint32_t row, uint32_t column,
+ const void* value, size_t size, int32_t type);
+};
+
+}; // namespace android
+
+#endif
diff --git a/jni/Unicode.cpp b/jni/Unicode.cpp
new file mode 100644
index 00000000..711a7957
--- /dev/null
+++ b/jni/Unicode.cpp
@@ -0,0 +1,576 @@
+/*
+ * 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 "Unicode.h"
+
+#include
+
+#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
+#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 .
+
+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(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(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;
+}
+
+}
diff --git a/jni/Unicode.h b/jni/Unicode.h
new file mode 100644
index 00000000..5d6a29f7
--- /dev/null
+++ b/jni/Unicode.h
@@ -0,0 +1,161 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_UNICODE_H
+#define ANDROID_UNICODE_H
+
+#include
+#include
+
+extern "C" {
+
+typedef uint32_t char32_t;
+typedef uint16_t char16_t;
+
+// Standard string functions on char16_t strings.
+int strcmp16(const char16_t *, const char16_t *);
+int strncmp16(const char16_t *s1, const char16_t *s2, size_t n);
+size_t strlen16(const char16_t *);
+size_t strnlen16(const char16_t *, size_t);
+char16_t *strcpy16(char16_t *, const char16_t *);
+char16_t *strncpy16(char16_t *, const char16_t *, size_t);
+
+// Version of comparison that supports embedded nulls.
+// This is different than strncmp() because we don't stop
+// at a nul character and consider the strings to be different
+// if the lengths are different (thus we need to supply the
+// lengths of both strings). This can also be used when
+// your string is not nul-terminated as it will have the
+// equivalent result as strcmp16 (unlike strncmp16).
+int strzcmp16(const char16_t *s1, size_t n1, const char16_t *s2, size_t n2);
+
+// Version of strzcmp16 for comparing strings in different endianness.
+int strzcmp16_h_n(const char16_t *s1H, size_t n1, const char16_t *s2N, size_t n2);
+
+// Standard string functions on char32_t strings.
+size_t strlen32(const char32_t *);
+size_t strnlen32(const char32_t *, size_t);
+
+/**
+ * Measure the length of a UTF-32 string in UTF-8. If the string is invalid
+ * such as containing a surrogate character, -1 will be returned.
+ */
+ssize_t utf32_to_utf8_length(const char32_t *src, size_t src_len);
+
+/**
+ * Stores a UTF-8 string converted from "src" in "dst", if "dst_length" is not
+ * large enough to store the string, the part of the "src" string is stored
+ * into "dst" as much as possible. See the examples for more detail.
+ * Returns the size actually used for storing the string.
+ * dst" is not null-terminated when dst_len is fully used (like strncpy).
+ *
+ * Example 1
+ * "src" == \u3042\u3044 (\xE3\x81\x82\xE3\x81\x84)
+ * "src_len" == 2
+ * "dst_len" >= 7
+ * ->
+ * Returned value == 6
+ * "dst" becomes \xE3\x81\x82\xE3\x81\x84\0
+ * (note that "dst" is null-terminated)
+ *
+ * Example 2
+ * "src" == \u3042\u3044 (\xE3\x81\x82\xE3\x81\x84)
+ * "src_len" == 2
+ * "dst_len" == 5
+ * ->
+ * Returned value == 3
+ * "dst" becomes \xE3\x81\x82\0
+ * (note that "dst" is null-terminated, but \u3044 is not stored in "dst"
+ * since "dst" does not have enough size to store the character)
+ *
+ * Example 3
+ * "src" == \u3042\u3044 (\xE3\x81\x82\xE3\x81\x84)
+ * "src_len" == 2
+ * "dst_len" == 6
+ * ->
+ * Returned value == 6
+ * "dst" becomes \xE3\x81\x82\xE3\x81\x84
+ * (note that "dst" is NOT null-terminated, like strncpy)
+ */
+//void utf32_to_utf8(const char32_t* src, size_t src_len, char* dst);
+
+/**
+ * Returns the unicode value at "index".
+ * Returns -1 when the index is invalid (equals to or more than "src_len").
+ * If returned value is positive, it is able to be converted to char32_t, which
+ * is unsigned. Then, if "next_index" is not NULL, the next index to be used is
+ * stored in "next_index". "next_index" can be NULL.
+ */
+int32_t utf32_from_utf8_at(const char *src, size_t src_len, size_t index, size_t *next_index);
+
+
+/**
+ * Returns the UTF-8 length of UTF-16 string "src".
+ */
+ssize_t utf16_to_utf8_length(const char16_t *src, size_t src_len);
+
+/**
+ * Converts a UTF-16 string to UTF-8. The destination buffer must be large
+ * enough to fit the UTF-16 as measured by utf16_to_utf8_length with an added
+ * NULL terminator.
+ */
+//void utf16_to_utf8(const char16_t* src, size_t src_len, char* dst);
+
+/**
+ * Returns the length of "src" when "src" is valid UTF-8 string.
+ * Returns 0 if src is NULL or 0-length string. Returns -1 when the source
+ * is an invalid string.
+ *
+ * This function should be used to determine whether "src" is valid UTF-8
+ * characters with valid unicode codepoints. "src" must be null-terminated.
+ *
+ * If you are going to use other utf8_to_... functions defined in this header
+ * with string which may not be valid UTF-8 with valid codepoint (form 0 to
+ * 0x10FFFF), you should use this function before calling others, since the
+ * other functions do not check whether the string is valid UTF-8 or not.
+ *
+ * If you do not care whether "src" is valid UTF-8 or not, you should use
+ * strlen() as usual, which should be much faster.
+ */
+//ssize_t utf8_length(const char *src);
+
+/**
+ * Measure the length of a UTF-32 string.
+ */
+size_t utf8_to_utf32_length(const char *src, size_t src_len);
+
+/**
+ * Stores a UTF-32 string converted from "src" in "dst". "dst" must be large
+ * enough to store the entire converted string as measured by
+ * utf8_to_utf32_length plus space for a NULL terminator.
+ */
+//void utf8_to_utf32(const char* src, size_t src_len, char32_t* dst);
+
+/**
+ * Returns the UTF-16 length of UTF-8 string "src".
+ */
+ssize_t utf8_to_utf16_length(const uint8_t* src, size_t srcLen);
+
+/**
+ * Convert UTF-8 to UTF-16 including surrogate pairs.
+ * Returns a pointer to the end of the string (where a null terminator might go
+ * if you wanted to add one).
+ */
+char16_t* utf8_to_utf16_no_null_terminator(const uint8_t* src, size_t srcLen, char16_t* dst);
+
+}
+
+#endif
diff --git a/jni/net_sqlcipher_AshmemCursorWindow.cpp b/jni/net_sqlcipher_AshmemCursorWindow.cpp
new file mode 100644
index 00000000..bf4e4255
--- /dev/null
+++ b/jni/net_sqlcipher_AshmemCursorWindow.cpp
@@ -0,0 +1,562 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "AshmemCursorWindow"
+
+#include
+#include
+#include
+
+#include
+//#include
+#include
+
+#include
+#include
+#include
+
+#include "AshmemCursorWindow.h"
+#include "Unicode.h"
+#include "android_util_Binder.h"
+#include "sqlite3_exception.h"
+
+namespace sqlcipher {
+
+static struct {
+ jfieldID data;
+ jfieldID sizeCopied;
+} gCharArrayBufferClassInfo;
+
+static jstring gEmptyString;
+
+static void throwExceptionWithRowCol(JNIEnv* env, jint row, jint column) {
+ android::String8 msg;
+ char *str = new char[100];
+ sprintf(str, "Couldn't read row %d, col %d from AshmemCursorWindow. "
+ "Make sure the Cursor is initialized correctly before accessing data from it.", row, column);
+ //msg.appendFormat("Couldn't read row %d, col %d from AshmemCursorWindow. "
+ // "Make sure the Cursor is initialized correctly before accessing data from it.",
+ // row, column);
+ msg.append(str);
+ free(str);
+ jniThrowException(env, "java/lang/IllegalStateException", msg.string());
+}
+
+static void throwUnknownTypeException(JNIEnv * env, jint type) {
+ android::String8 msg;
+ char *str = new char[100];
+ sprintf(str, "UNKNOWN type %d", type);
+ msg.append(str);
+ free(str);
+ //msg.appendFormat("UNKNOWN type %d", type);
+ jniThrowException(env, "java/lang/IllegalStateException", msg.string());
+}
+
+static jint nativeCreate(JNIEnv* env, jclass clazz, jstring nameObj, jint cursorWindowSize) {
+ android::String8 name;
+ const char* nameStr = env->GetStringUTFChars(nameObj, NULL);
+ name.setTo(nameStr);
+ env->ReleaseStringUTFChars(nameObj, nameStr);
+
+ AshmemCursorWindow* window;
+ status_t status = AshmemCursorWindow::create(name, cursorWindowSize, &window);
+ if (status || !window) {
+ LOG_WINDOW("Could not allocate AshmemCursorWindow '%s' of size %d due to error %d.",
+ name.string(), cursorWindowSize, status);
+ return 0;
+ }
+
+ LOG_WINDOW("nativeInitializeEmpty: window = %p", window);
+ return reinterpret_cast(window);
+}
+
+static jint nativeCreateFromParcel(JNIEnv* env, jclass clazz, jobject parcelObj) {
+ Parcel* parcel = parcelForJavaObject(env, parcelObj);
+
+ AshmemCursorWindow* window;
+ status_t status = AshmemCursorWindow::createFromParcel(parcel, &window);
+ if (status || !window) {
+ LOG_WINDOW("Could not create AshmemCursorWindow from Parcel due to error %d.", status);
+ return 0;
+ }
+
+ LOG_WINDOW("nativeInitializeFromBinder: numRows = %d, numColumns = %d, window = %p",
+ window->getNumRows(), window->getNumColumns(), window);
+ return reinterpret_cast(window);
+}
+
+static void nativeDispose(JNIEnv* env, jclass clazz, jint windowPtr) {
+ AshmemCursorWindow* window = reinterpret_cast(windowPtr);
+ if (window) {
+ LOG_WINDOW("Closing window %p", window);
+ delete window;
+ }
+}
+
+static jstring nativeGetName(JNIEnv* env, jclass clazz, jint windowPtr) {
+ AshmemCursorWindow* window = reinterpret_cast(windowPtr);
+ return env->NewStringUTF(window->name().string());
+}
+
+static void nativeWriteToParcel(JNIEnv * env, jclass clazz, jint windowPtr,
+ jobject parcelObj) {
+ AshmemCursorWindow* window = reinterpret_cast(windowPtr);
+ Parcel* parcel = parcelForJavaObject(env, parcelObj);
+
+ status_t status = window->writeToParcel(parcel);
+ if (status) {
+ android::String8 msg;
+ char *str = new char[100];
+ sprintf(str, "Could not write AshmemCursorWindow to Parcel due to error %d.", status);
+ msg.append(str);
+ free(str);
+ //msg.appendFormat("Could not write AshmemCursorWindow to Parcel due to error %d.", status);
+ jniThrowRuntimeException(env, msg.string());
+ }
+}
+
+static void nativeClear(JNIEnv * env, jclass clazz, jint windowPtr) {
+ AshmemCursorWindow* window = reinterpret_cast(windowPtr);
+ LOG_WINDOW("Clearing window %p", window);
+ status_t status = window->clear();
+ if (status) {
+ LOG_WINDOW("Could not clear window. error=%d", status);
+ }
+}
+
+static jint nativeGetNumRows(JNIEnv* env, jclass clazz, jint windowPtr) {
+ AshmemCursorWindow* window = reinterpret_cast(windowPtr);
+ return window->getNumRows();
+}
+
+static jboolean nativeSetNumColumns(JNIEnv* env, jclass clazz, jint windowPtr,
+ jint columnNum) {
+ AshmemCursorWindow* window = reinterpret_cast(windowPtr);
+ status_t status = window->setNumColumns(columnNum);
+ return status == OK;
+}
+
+static jboolean nativeAllocRow(JNIEnv* env, jclass clazz, jint windowPtr) {
+ AshmemCursorWindow* window = reinterpret_cast(windowPtr);
+ status_t status = window->allocRow();
+ return status == OK;
+}
+
+static void nativeFreeLastRow(JNIEnv* env, jclass clazz, jint windowPtr) {
+ AshmemCursorWindow* window = reinterpret_cast(windowPtr);
+ window->freeLastRow();
+}
+
+static jint nativeGetType(JNIEnv* env, jclass clazz, jint windowPtr,
+ jint row, jint column) {
+ AshmemCursorWindow* window = reinterpret_cast(windowPtr);
+ LOG_WINDOW("returning column type affinity for %d,%d from %p", row, column, window);
+
+ AshmemCursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
+ if (!fieldSlot) {
+ // FIXME: This is really broken but we have CTS tests that depend
+ // on this legacy behavior.
+ //throwExceptionWithRowCol(env, row, column);
+ return AshmemCursorWindow::NEW_FIELD_TYPE_NULL;
+ }
+ return window->getFieldSlotType(fieldSlot);
+}
+
+static jbyteArray nativeGetBlob(JNIEnv* env, jclass clazz, jint windowPtr,
+ jint row, jint column) {
+ AshmemCursorWindow* window = reinterpret_cast(windowPtr);
+ LOG_WINDOW("Getting blob for %d,%d from %p", row, column, window);
+
+ AshmemCursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
+ if (!fieldSlot) {
+ throwExceptionWithRowCol(env, row, column);
+ return NULL;
+ }
+
+ int32_t type = window->getFieldSlotType(fieldSlot);
+ if (type == AshmemCursorWindow::NEW_FIELD_TYPE_BLOB || type == AshmemCursorWindow::NEW_FIELD_TYPE_STRING) {
+ size_t size;
+ const void* value = window->getFieldSlotValueBlob(fieldSlot, &size);
+ jbyteArray byteArray = env->NewByteArray(size);
+ if (!byteArray) {
+ env->ExceptionClear();
+ throw_sqlite3_exception(env, "Native could not create new byte[]");
+ return NULL;
+ }
+ env->SetByteArrayRegion(byteArray, 0, size, static_cast(value));
+ return byteArray;
+ } else if (type == AshmemCursorWindow::NEW_FIELD_TYPE_INTEGER) {
+ throw_sqlite3_exception(env, "INTEGER data in nativeGetBlob ");
+ } else if (type == AshmemCursorWindow::NEW_FIELD_TYPE_FLOAT) {
+ throw_sqlite3_exception(env, "FLOAT data in nativeGetBlob ");
+ } else if (type == AshmemCursorWindow::NEW_FIELD_TYPE_NULL) {
+ // do nothing
+ } else {
+ throwUnknownTypeException(env, type);
+ }
+ return NULL;
+}
+
+static jstring nativeGetString(JNIEnv* env, jclass clazz, jint windowPtr,
+ jint row, jint column) {
+ AshmemCursorWindow* window = reinterpret_cast(windowPtr);
+ LOG_WINDOW("Getting string for %d,%d from %p", row, column, window);
+
+ AshmemCursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
+ if (!fieldSlot) {
+ throwExceptionWithRowCol(env, row, column);
+ return NULL;
+ }
+
+ int32_t type = window->getFieldSlotType(fieldSlot);
+ if (type == AshmemCursorWindow::NEW_FIELD_TYPE_STRING) {
+ size_t sizeIncludingNull;
+ const char* value = window->getFieldSlotValueString(fieldSlot, &sizeIncludingNull);
+ if (sizeIncludingNull <= 1) {
+ return gEmptyString;
+ }
+ // Convert to UTF-16 here instead of calling NewStringUTF. NewStringUTF
+ // doesn't like UTF-8 strings with high codepoints. It actually expects
+ // Modified UTF-8 with encoded surrogate pairs.
+ String16 utf16(value, sizeIncludingNull - 1);
+ return env->NewString(reinterpret_cast(utf16.string()), utf16.size());
+ } else if (type == AshmemCursorWindow::NEW_FIELD_TYPE_INTEGER) {
+ int64_t value = window->getFieldSlotValueLong(fieldSlot);
+ char buf[32];
+ snprintf(buf, sizeof(buf), "%lld", value);
+ return env->NewStringUTF(buf);
+ } else if (type == AshmemCursorWindow::NEW_FIELD_TYPE_FLOAT) {
+ double value = window->getFieldSlotValueDouble(fieldSlot);
+ char buf[32];
+ snprintf(buf, sizeof(buf), "%g", value);
+ return env->NewStringUTF(buf);
+ } else if (type == AshmemCursorWindow::NEW_FIELD_TYPE_NULL) {
+ return NULL;
+ } else if (type == AshmemCursorWindow::NEW_FIELD_TYPE_BLOB) {
+ throw_sqlite3_exception(env, "Unable to convert BLOB to string");
+ return NULL;
+ } else {
+ throwUnknownTypeException(env, type);
+ return NULL;
+ }
+}
+
+static jcharArray allocCharArrayBuffer(JNIEnv* env, jobject bufferObj, size_t size) {
+ jcharArray dataObj = jcharArray(env->GetObjectField(bufferObj,
+ gCharArrayBufferClassInfo.data));
+ if (dataObj && size) {
+ jsize capacity = env->GetArrayLength(dataObj);
+ if (size_t(capacity) < size) {
+ env->DeleteLocalRef(dataObj);
+ dataObj = NULL;
+ }
+ }
+ if (!dataObj) {
+ jsize capacity = size;
+ if (capacity < 64) {
+ capacity = 64;
+ }
+ dataObj = env->NewCharArray(capacity); // might throw OOM
+ if (dataObj) {
+ env->SetObjectField(bufferObj, gCharArrayBufferClassInfo.data, dataObj);
+ }
+ }
+ return dataObj;
+}
+
+static void fillCharArrayBufferUTF(JNIEnv* env, jobject bufferObj,
+ const char* str, size_t len) {
+ ssize_t size = utf8_to_utf16_length(reinterpret_cast(str), len);
+ if (size < 0) {
+ size = 0; // invalid UTF8 string
+ }
+ jcharArray dataObj = allocCharArrayBuffer(env, bufferObj, size);
+ if (dataObj) {
+ if (size) {
+ jchar* data = static_cast(env->GetPrimitiveArrayCritical(dataObj, NULL));
+ utf8_to_utf16_no_null_terminator(reinterpret_cast(str), len,
+ reinterpret_cast(data));
+ env->ReleasePrimitiveArrayCritical(dataObj, data, 0);
+ }
+ env->SetIntField(bufferObj, gCharArrayBufferClassInfo.sizeCopied, size);
+ }
+}
+
+static void clearCharArrayBuffer(JNIEnv* env, jobject bufferObj) {
+ jcharArray dataObj = allocCharArrayBuffer(env, bufferObj, 0);
+ if (dataObj) {
+ env->SetIntField(bufferObj, gCharArrayBufferClassInfo.sizeCopied, 0);
+ }
+}
+
+static void nativeCopyStringToBuffer(JNIEnv* env, jclass clazz, jint windowPtr,
+ jint row, jint column, jobject bufferObj) {
+ AshmemCursorWindow* window = reinterpret_cast(windowPtr);
+ LOG_WINDOW("Copying string for %d,%d from %p", row, column, window);
+
+ AshmemCursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
+ if (!fieldSlot) {
+ throwExceptionWithRowCol(env, row, column);
+ return;
+ }
+
+ int32_t type = window->getFieldSlotType(fieldSlot);
+ if (type == AshmemCursorWindow::NEW_FIELD_TYPE_STRING) {
+ size_t sizeIncludingNull;
+ const char* value = window->getFieldSlotValueString(fieldSlot, &sizeIncludingNull);
+ if (sizeIncludingNull > 1) {
+ fillCharArrayBufferUTF(env, bufferObj, value, sizeIncludingNull - 1);
+ } else {
+ clearCharArrayBuffer(env, bufferObj);
+ }
+ } else if (type == AshmemCursorWindow::NEW_FIELD_TYPE_INTEGER) {
+ int64_t value = window->getFieldSlotValueLong(fieldSlot);
+ char buf[32];
+ snprintf(buf, sizeof(buf), "%lld", value);
+ fillCharArrayBufferUTF(env, bufferObj, buf, strlen(buf));
+ } else if (type == AshmemCursorWindow::NEW_FIELD_TYPE_FLOAT) {
+ double value = window->getFieldSlotValueDouble(fieldSlot);
+ char buf[32];
+ snprintf(buf, sizeof(buf), "%g", value);
+ fillCharArrayBufferUTF(env, bufferObj, buf, strlen(buf));
+ } else if (type == AshmemCursorWindow::NEW_FIELD_TYPE_NULL) {
+ clearCharArrayBuffer(env, bufferObj);
+ } else if (type == AshmemCursorWindow::NEW_FIELD_TYPE_BLOB) {
+ throw_sqlite3_exception(env, "Unable to convert BLOB to string");
+ } else {
+ throwUnknownTypeException(env, type);
+ }
+}
+
+static jlong nativeGetLong(JNIEnv* env, jclass clazz, jint windowPtr,
+ jint row, jint column) {
+ AshmemCursorWindow* window = reinterpret_cast(windowPtr);
+ LOG_WINDOW("Getting long for %d,%d from %p", row, column, window);
+
+ AshmemCursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
+ if (!fieldSlot) {
+ throwExceptionWithRowCol(env, row, column);
+ return 0;
+ }
+
+ int32_t type = window->getFieldSlotType(fieldSlot);
+ if (type == AshmemCursorWindow::NEW_FIELD_TYPE_INTEGER) {
+ return window->getFieldSlotValueLong(fieldSlot);
+ } else if (type == AshmemCursorWindow::NEW_FIELD_TYPE_STRING) {
+ size_t sizeIncludingNull;
+ const char* value = window->getFieldSlotValueString(fieldSlot, &sizeIncludingNull);
+ return sizeIncludingNull > 1 ? strtoll(value, NULL, 0) : 0L;
+ } else if (type == AshmemCursorWindow::NEW_FIELD_TYPE_FLOAT) {
+ return jlong(window->getFieldSlotValueDouble(fieldSlot));
+ } else if (type == AshmemCursorWindow::NEW_FIELD_TYPE_NULL) {
+ return 0;
+ } else if (type == AshmemCursorWindow::NEW_FIELD_TYPE_BLOB) {
+ throw_sqlite3_exception(env, "Unable to convert BLOB to long");
+ return 0;
+ } else {
+ throwUnknownTypeException(env, type);
+ return 0;
+ }
+}
+
+static jdouble nativeGetDouble(JNIEnv* env, jclass clazz, jint windowPtr,
+ jint row, jint column) {
+ AshmemCursorWindow* window = reinterpret_cast(windowPtr);
+ LOG_WINDOW("Getting double for %d,%d from %p", row, column, window);
+
+ AshmemCursorWindow::FieldSlot* fieldSlot = window->getFieldSlot(row, column);
+ if (!fieldSlot) {
+ throwExceptionWithRowCol(env, row, column);
+ return 0.0;
+ }
+
+ int32_t type = window->getFieldSlotType(fieldSlot);
+ if (type == AshmemCursorWindow::NEW_FIELD_TYPE_FLOAT) {
+ return window->getFieldSlotValueDouble(fieldSlot);
+ } else if (type == AshmemCursorWindow::NEW_FIELD_TYPE_STRING) {
+ size_t sizeIncludingNull;
+ const char* value = window->getFieldSlotValueString(fieldSlot, &sizeIncludingNull);
+ return sizeIncludingNull > 1 ? strtod(value, NULL) : 0.0;
+ } else if (type == AshmemCursorWindow::NEW_FIELD_TYPE_INTEGER) {
+ return jdouble(window->getFieldSlotValueLong(fieldSlot));
+ } else if (type == AshmemCursorWindow::NEW_FIELD_TYPE_NULL) {
+ return 0.0;
+ } else if (type == AshmemCursorWindow::NEW_FIELD_TYPE_BLOB) {
+ throw_sqlite3_exception(env, "Unable to convert BLOB to double");
+ return 0.0;
+ } else {
+ throwUnknownTypeException(env, type);
+ return 0.0;
+ }
+}
+
+static jboolean nativePutBlob(JNIEnv* env, jclass clazz, jint windowPtr,
+ jbyteArray valueObj, jint row, jint column) {
+ AshmemCursorWindow* window = reinterpret_cast(windowPtr);
+ jsize len = env->GetArrayLength(valueObj);
+
+ void* value = env->GetPrimitiveArrayCritical(valueObj, NULL);
+ status_t status = window->putBlob(row, column, value, len);
+ env->ReleasePrimitiveArrayCritical(valueObj, value, JNI_ABORT);
+
+ if (status) {
+ LOG_WINDOW("Failed to put blob. error=%d", status);
+ return false;
+ }
+
+ LOG_WINDOW("%d,%d is BLOB with %u bytes", row, column, len);
+ return true;
+}
+
+static jboolean nativePutString(JNIEnv* env, jclass clazz, jint windowPtr,
+ jstring valueObj, jint row, jint column) {
+ AshmemCursorWindow* window = reinterpret_cast(windowPtr);
+
+ size_t sizeIncludingNull = env->GetStringUTFLength(valueObj) + 1;
+ const char* valueStr = env->GetStringUTFChars(valueObj, NULL);
+ if (!valueStr) {
+ LOG_WINDOW("value can't be transferred to UTFChars");
+ return false;
+ }
+ status_t status = window->putString(row, column, valueStr, sizeIncludingNull);
+ env->ReleaseStringUTFChars(valueObj, valueStr);
+
+ if (status) {
+ LOG_WINDOW("Failed to put string. error=%d", status);
+ return false;
+ }
+
+ LOG_WINDOW("%d,%d is TEXT with %u bytes", row, column, sizeIncludingNull);
+ return true;
+}
+
+static jboolean nativePutLong(JNIEnv* env, jclass clazz, jint windowPtr,
+ jlong value, jint row, jint column) {
+ AshmemCursorWindow* window = reinterpret_cast(windowPtr);
+ status_t status = window->putLong(row, column, value);
+
+ if (status) {
+ LOG_WINDOW("Failed to put long. error=%d", status);
+ return false;
+ }
+
+ LOG_WINDOW("%d,%d is INTEGER 0x%016llx", row, column, value);
+ return true;
+}
+
+static jboolean nativePutDouble(JNIEnv* env, jclass clazz, jint windowPtr,
+ jdouble value, jint row, jint column) {
+ AshmemCursorWindow* window = reinterpret_cast(windowPtr);
+ status_t status = window->putDouble(row, column, value);
+
+ if (status) {
+ LOG_WINDOW("Failed to put double. error=%d", status);
+ return false;
+ }
+
+ LOG_WINDOW("%d,%d is FLOAT %lf", row, column, value);
+ return true;
+}
+
+static jboolean nativePutNull(JNIEnv* env, jclass clazz, jint windowPtr,
+ jint row, jint column) {
+ AshmemCursorWindow* window = reinterpret_cast(windowPtr);
+ status_t status = window->putNull(row, column);
+
+ if (status) {
+ LOG_WINDOW("Failed to put null. error=%d", status);
+ return false;
+ }
+
+ LOG_WINDOW("%d,%d is NULL", row, column);
+ return true;
+}
+
+static JNINativeMethod sMethods[] =
+{
+ /* name, signature, funcPtr */
+ { "nativeCreate", "(Ljava/lang/String;I)I",
+ (void*)nativeCreate },
+ { "nativeCreateFromParcel", "(Landroid/os/Parcel;)I",
+ (void*)nativeCreateFromParcel },
+ { "nativeDispose", "(I)V",
+ (void*)nativeDispose },
+ { "nativeWriteToParcel", "(ILandroid/os/Parcel;)V",
+ (void*)nativeWriteToParcel },
+ { "nativeGetName", "(I)Ljava/lang/String;",
+ (void*)nativeGetName },
+ { "nativeClear", "(I)V",
+ (void*)nativeClear },
+ { "nativeGetNumRows", "(I)I",
+ (void*)nativeGetNumRows },
+ { "nativeSetNumColumns", "(II)Z",
+ (void*)nativeSetNumColumns },
+ { "nativeAllocRow", "(I)Z",
+ (void*)nativeAllocRow },
+ { "nativeFreeLastRow", "(I)V",
+ (void*)nativeFreeLastRow },
+ { "nativeGetType", "(III)I",
+ (void*)nativeGetType },
+ { "nativeGetBlob", "(III)[B",
+ (void*)nativeGetBlob },
+ { "nativeGetString", "(III)Ljava/lang/String;",
+ (void*)nativeGetString },
+ { "nativeGetLong", "(III)J",
+ (void*)nativeGetLong },
+ { "nativeGetDouble", "(III)D",
+ (void*)nativeGetDouble },
+ { "nativeCopyStringToBuffer", "(IIILandroid/database/CharArrayBuffer;)V",
+ (void*)nativeCopyStringToBuffer },
+ { "nativePutBlob", "(I[BII)Z",
+ (void*)nativePutBlob },
+ { "nativePutString", "(ILjava/lang/String;II)Z",
+ (void*)nativePutString },
+ { "nativePutLong", "(IJII)Z",
+ (void*)nativePutLong },
+ { "nativePutDouble", "(IDII)Z",
+ (void*)nativePutDouble },
+ { "nativePutNull", "(III)Z",
+ (void*)nativePutNull },
+};
+
+#define FIND_CLASS(var, className) \
+ var = env->FindClass(className); \
+ LOG_FATAL_IF(! var, "Unable to find class " className);
+
+#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
+ var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
+ LOG_FATAL_IF(! var, "Unable to find field " fieldName);
+
+int register_android_database_AshmemCursorWindow(JNIEnv * env)
+{
+ LOGE("Registering AshmemCursorWindow methods \n");
+ jclass clazz;
+ FIND_CLASS(clazz, "android/database/CharArrayBuffer");
+
+ GET_FIELD_ID(gCharArrayBufferClassInfo.data, clazz,
+ "data", "[C");
+ GET_FIELD_ID(gCharArrayBufferClassInfo.sizeCopied, clazz,
+ "sizeCopied", "I");
+
+ gEmptyString = jstring(env->NewGlobalRef(env->NewStringUTF("")));
+ LOG_FATAL_IF(!gEmptyString, "Unable to create empty string");
+
+ return AndroidRuntime::registerNativeMethods(env, "net/sqlcipher/AshmemCursorWindow",
+ sMethods, NELEM(sMethods));
+}
+
+} // namespace android
diff --git a/jni/net_sqlcipher_database_SQLiteDatabase.cpp b/jni/net_sqlcipher_database_SQLiteDatabase.cpp
index b8095689..d344b06f 100644
--- a/jni/net_sqlcipher_database_SQLiteDatabase.cpp
+++ b/jni/net_sqlcipher_database_SQLiteDatabase.cpp
@@ -608,6 +608,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
register_android_database_SQLiteStatement(env);
register_android_database_CursorWindow(env);
+ register_android_database_AshmemCursorWindow(env);
//register_android_database_SQLiteDebug(env);
diff --git a/jni/net_sqlcipher_database_SQLiteQuery.cpp b/jni/net_sqlcipher_database_SQLiteQuery.cpp
index 39eb2e29..51af0a86 100644
--- a/jni/net_sqlcipher_database_SQLiteQuery.cpp
+++ b/jni/net_sqlcipher_database_SQLiteQuery.cpp
@@ -30,6 +30,7 @@
#include
#include "CursorWindow.h"
+#include "AshmemCursorWindow.h"
#include "sqlite3_exception.h"
@@ -314,6 +315,237 @@ static jint native_fill_window(JNIEnv* env, jobject object, jobject javaWindow,
}
}
+enum CopyRowResult {
+ CPR_OK,
+ CPR_FULL,
+ CPR_ERROR,
+};
+
+static CopyRowResult copyRow(JNIEnv* env, AshmemCursorWindow* window,
+ sqlite3_stmt* statement, int numColumns, int startPos, int addedRows) {
+ // Allocate a new field directory for the row.
+ status_t status = window->allocRow();
+ if (status) {
+ LOG_WINDOW("Failed allocating fieldDir at startPos %d row %d, error=%d",
+ startPos, addedRows, status);
+ return CPR_FULL;
+ }
+
+ // Pack the row into the window.
+ CopyRowResult result = CPR_OK;
+ for (int i = 0; i < numColumns; i++) {
+ int type = sqlite3_column_type(statement, i);
+ if (type == SQLITE_TEXT) {
+ // TEXT data
+ const char* text = reinterpret_cast(
+ sqlite3_column_text(statement, i));
+
+ // SQLite does not include the NULL terminator in size, but does
+ // ensure all strings are NULL terminated, so increase size by
+ // one to make sure we store the terminator.
+ size_t sizeIncludingNull = sqlite3_column_bytes(statement, i) + 1;
+ status = window->putString(addedRows, i, text, sizeIncludingNull);
+ if (status) {
+ LOG_WINDOW("Failed allocating %u bytes for text at %d,%d, error=%d",
+ sizeIncludingNull, startPos + addedRows, i, status);
+ result = CPR_FULL;
+ break;
+ }
+ LOG_WINDOW("%d,%d is TEXT with %u bytes",
+ startPos + addedRows, i, sizeIncludingNull);
+ } else if (type == SQLITE_INTEGER) {
+ // INTEGER data
+ int64_t value = sqlite3_column_int64(statement, i);
+ status = window->putLong(addedRows, i, value);
+ if (status) {
+ LOG_WINDOW("Failed allocating space for a long in column %d, error=%d",
+ i, status);
+ result = CPR_FULL;
+ break;
+ }
+ LOG_WINDOW("%d,%d is INTEGER 0x%016llx", startPos + addedRows, i, value);
+ } else if (type == SQLITE_FLOAT) {
+ // FLOAT data
+ double value = sqlite3_column_double(statement, i);
+ status = window->putDouble(addedRows, i, value);
+ if (status) {
+ LOG_WINDOW("Failed allocating space for a double in column %d, error=%d",
+ i, status);
+ result = CPR_FULL;
+ break;
+ }
+ LOG_WINDOW("%d,%d is FLOAT %lf", startPos + addedRows, i, value);
+ } else if (type == SQLITE_BLOB) {
+ // BLOB data
+ const void* blob = sqlite3_column_blob(statement, i);
+ size_t size = sqlite3_column_bytes(statement, i);
+ status = window->putBlob(addedRows, i, blob, size);
+ if (status) {
+ LOG_WINDOW("Failed allocating %u bytes for blob at %d,%d, error=%d",
+ size, startPos + addedRows, i, status);
+ result = CPR_FULL;
+ break;
+ }
+ LOG_WINDOW("%d,%d is Blob with %u bytes",
+ startPos + addedRows, i, size);
+ } else if (type == SQLITE_NULL) {
+ // NULL field
+ status = window->putNull(addedRows, i);
+ if (status) {
+ LOG_WINDOW("Failed allocating space for a null in column %d, error=%d",
+ i, status);
+ result = CPR_FULL;
+ break;
+ }
+ LOG_WINDOW("%d,%d is NULL", startPos + addedRows, i);
+ } else {
+ // Unknown data
+ LOGE("Unknown column type when filling database window");
+ throw_sqlite3_exception(env, "Unknown column type when filling window");
+ result = CPR_ERROR;
+ break;
+ }
+ }
+ // Free the last row if if was not successfully copied.
+ if (result != CPR_OK) {
+ window->freeLastRow();
+ }
+
+ return result;
+}
+
+static jint native_fill_ashmem_window(JNIEnv* env, jobject object, jint windowPtr,
+ jint startPos, jint offsetParam, jint maxRead, jint lastPos)
+{
+ sqlite3_stmt * statement = GET_STATEMENT(env, object);
+ int numRows = lastPos;
+ maxRead += lastPos;
+ int numColumns;
+ int boundParams;
+ int retryCount = 0;
+ int totalRows = 0;
+ int addedRows = 0;
+ bool windowFull = false;
+ bool gotException = false;
+ AshmemCursorWindow *window;
+
+ if (statement == NULL) {
+ LOGE("Invalid statement in fillWindow()");
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Attempting to access a deactivated, closed, or empty cursor");
+ return 0;
+ }
+
+ // Only do the binding if there is a valid offsetParam. If no binding needs to be done
+ // offsetParam will be set to 0, an invliad value.
+ if(offsetParam > 0) {
+ // Bind the offset parameter, telling the program which row to start with
+ int err = sqlite3_bind_int(statement, offsetParam, startPos);
+ if (err != SQLITE_OK) {
+ LOGE("Unable to bind offset position, offsetParam = %d", offsetParam);
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ sqlite3_errmsg(GET_HANDLE(env, object)));
+ return 0;
+ }
+ LOG_WINDOW("Bound to startPos %d", startPos);
+ } else {
+ LOG_WINDOW("Not binding to startPos %d", startPos);
+ }
+
+ // Get the native window
+ window = reinterpret_cast(windowPtr);
+ if (!window) {
+ LOGE("Invalid CursorWindow");
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ "Bad CursorWindow");
+ return 0;
+ }
+ LOG_WINDOW("Window: numRows = %d, size = %d, freeSpace = %d", window->getNumRows(), window->size(), window->freeSpace());
+
+ status_t status = window->clear();
+ if (status) {
+ LOGE("Failed to clear the cursor window, status=%d", status);
+ throw_sqlite3_exception(env, GET_HANDLE(env, object), "window->clear() failed");
+ return 0;
+ }
+
+ numColumns = sqlite3_column_count(statement);
+ status = window->setNumColumns(numColumns);
+ if (status) {
+ LOGE("Failed to change column count from %d to %d", window->getNumColumns(), numColumns);
+ throw_sqlite3_exception(env, GET_HANDLE(env, object), "numColumns mismatch");
+ return 0;
+ }
+
+ retryCount = 0;
+ if (startPos > 0) {
+ int num = skip_rows(statement, startPos);
+ if (num < 0) {
+ throw_sqlite3_exception(env, GET_HANDLE(env, object));
+ return 0;
+ } else if (num < startPos) {
+ LOGE("startPos %d > actual rows %d", startPos, num);
+ return num;
+ }
+ }
+
+ while (!gotException && (!windowFull)) {
+ int err = sqlite3_step(statement);
+ if (err == SQLITE_ROW) {
+ LOG_WINDOW("Stepped statement %p to row %d", statement, totalRows);
+ retryCount = 0;
+ totalRows += 1;
+
+ // Skip the row if the window is full or we haven't reached the start position yet.
+ if (startPos >= totalRows || windowFull) {
+ continue;
+ }
+
+ CopyRowResult cpr = copyRow(env, window, statement, numColumns, startPos, addedRows);
+ if (cpr == CPR_OK) {
+ addedRows += 1;
+ } else if (cpr == CPR_FULL) {
+ windowFull = true;
+ } else {
+ gotException = true;
+ }
+ } else if (err == SQLITE_DONE) {
+ // All rows processed, bail
+ LOG_WINDOW("Processed all rows");
+ break;
+ } else if (err == SQLITE_LOCKED || err == SQLITE_BUSY) {
+ // The table is locked, retry
+ LOG_WINDOW("Database locked, retrying");
+ if (retryCount > 50) {
+ LOGE("Bailing on database busy rety");
+ break;
+ }
+
+ // Sleep to give the thread holding the lock a chance to finish
+ usleep(1000);
+
+ retryCount++;
+ continue;
+ } else {
+ throw_sqlite3_exception(env, GET_HANDLE(env, object));
+ break;
+ }
+ }
+
+ LOG_WINDOW("Resetting statement %p after fetching %d rows and adding %d rows"
+ "to the window in %d bytes",
+ statement, totalRows, addedRows, window->size() - window->freeSpace());
+ sqlite3_reset(statement);
+
+ // Report the total number of rows on request.
+ if (startPos > totalRows) {
+ LOGE("startPos %d > actual rows %d", startPos, totalRows);
+ }
+
+ jlong result = jlong(startPos) << 32 | jlong(totalRows);
+ return result;
+}
+
static jint native_column_count(JNIEnv* env, jobject object)
{
sqlite3_stmt * statement = GET_STATEMENT(env, object);
@@ -336,6 +568,7 @@ static JNINativeMethod sMethods[] =
{
/* name, signature, funcPtr */
{"native_fill_window", "(Lnet/sqlcipher/CursorWindow;IIII)I", (void *)native_fill_window},
+ {"native_fill_ashmem_window", "(IIIII)I", (void *)native_fill_ashmem_window},
{"native_column_count", "()I", (void*)native_column_count},
{"native_column_name", "(I)Ljava/lang/String;", (void *)native_column_name},
};
diff --git a/jni/sqlcipher_loading.h b/jni/sqlcipher_loading.h
index a057fa94..21ea94e8 100644
--- a/jni/sqlcipher_loading.h
+++ b/jni/sqlcipher_loading.h
@@ -38,5 +38,7 @@ int register_android_database_SQLiteDebug(JNIEnv *env);
int register_android_database_CursorWindow(JNIEnv *env);
+int register_android_database_AshmemCursorWindow(JNIEnv *env);
+
}
diff --git a/libs/guava-r09.jar b/libs/guava-r09.jar
deleted file mode 100644
index f8da8b1c..00000000
Binary files a/libs/guava-r09.jar and /dev/null differ
diff --git a/project.properties b/project.properties
index 5a709453..ea89160e 100644
--- a/project.properties
+++ b/project.properties
@@ -8,4 +8,4 @@
# project structure.
# Project target.
-target=android-7
+target=android-8
diff --git a/src/com/google/common/collect/Maps.java b/src/com/google/common/collect/Maps.java
new file mode 100644
index 00000000..bb653a68
--- /dev/null
+++ b/src/com/google/common/collect/Maps.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.common.collect;
+
+import java.util.HashMap;
+
+/**
+ * Provides static methods for creating mutable {@code Maps} instances easily.
+ */
+// Copied file from froyo/frameworks/base/core/java/com/google/android/collect/Maps.java
+public class Maps {
+ /**
+ * Creates a {@code HashMap} instance.
+ *
+ * @return a newly-created, initially-empty {@code HashMap}
+ */
+ public static HashMap newHashMap() {
+ return new HashMap();
+ }
+}
diff --git a/src/net/sqlcipher/AbstractCursor.java b/src/net/sqlcipher/AbstractCursor.java
index 45cae57a..361f92d6 100644
--- a/src/net/sqlcipher/AbstractCursor.java
+++ b/src/net/sqlcipher/AbstractCursor.java
@@ -68,7 +68,7 @@ public byte[] getBlob(int column) {
/**
* returns a pre-filled window, return NULL if no such window
*/
- public CursorWindow getWindow() {
+ public AbstractCursorWindow getWindow() {
return null;
}
diff --git a/src/net/sqlcipher/AbstractCursorWindow.java b/src/net/sqlcipher/AbstractCursorWindow.java
new file mode 100644
index 00000000..f2eb87a3
--- /dev/null
+++ b/src/net/sqlcipher/AbstractCursorWindow.java
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+package net.sqlcipher;
+
+import android.os.Parcelable;
+
+/**
+ * A buffer containing multiple cursor rows.
+ */
+public abstract class AbstractCursorWindow extends android.database.CursorWindow implements Parcelable {
+ public static int BINDER_CURSOR_WINDOW = 1;
+ public static int ASHMEM_CURSOR_WINDOW = 1;
+
+ /**
+ * Creates a new empty window.
+ *
+ * @param localWindow true if this window will be used in this process only
+ */
+ public AbstractCursorWindow(boolean localWindow) {
+ super(localWindow);
+ }
+
+ abstract public int getType(int row, int column);
+ abstract public int getCursorWindowType();
+}
diff --git a/src/net/sqlcipher/AbstractWindowedCursor.java b/src/net/sqlcipher/AbstractWindowedCursor.java
index ec48e709..1bd1d841 100644
--- a/src/net/sqlcipher/AbstractWindowedCursor.java
+++ b/src/net/sqlcipher/AbstractWindowedCursor.java
@@ -227,7 +227,7 @@ protected void checkPosition()
}
@Override
- public CursorWindow getWindow() {
+ public AbstractCursorWindow getWindow() {
return mWindow;
}
@@ -235,7 +235,7 @@ public CursorWindow getWindow() {
* Set a new cursor window to cursor, usually set a remote cursor window
* @param window cursor window
*/
- public void setWindow(CursorWindow window) {
+ public void setWindow(AbstractCursorWindow window) {
if (mWindow != null) {
mWindow.close();
}
@@ -250,5 +250,5 @@ public boolean hasWindow() {
* This needs be updated in {@link #onMove} by subclasses, and
* needs to be set to NULL when the contents of the cursor change.
*/
- protected CursorWindow mWindow;
+ protected AbstractCursorWindow mWindow;
}
diff --git a/src/net/sqlcipher/AshmemCursorWindow.java b/src/net/sqlcipher/AshmemCursorWindow.java
new file mode 100644
index 00000000..40959182
--- /dev/null
+++ b/src/net/sqlcipher/AshmemCursorWindow.java
@@ -0,0 +1,783 @@
+/*
+ * 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.
+ */
+
+package net.sqlcipher;
+
+import android.database.CharArrayBuffer;
+import android.database.sqlite.SQLiteException;
+import android.os.Binder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.Process;
+import android.util.Log;
+import android.util.SparseIntArray;
+
+import net.sqlcipher.Cursor;
+
+/**
+ * A buffer containing multiple cursor rows.
+ *
+ * A {@link AbstractCursorWindow} is read-write when initially created and used locally.
+ * When sent to a remote process (by writing it to a {@link Parcel}), the remote process
+ * receives a read-only view of the cursor window. Typically the cursor window
+ * will be allocated by the producer, filled with data, and then sent to the
+ * consumer for reading.
+ *
+ */
+public class AshmemCursorWindow extends AbstractCursorWindow implements Parcelable {
+ private static final String STATS_TAG = "CursorWindowStats";
+
+ /** The cursor window size. resource xml file specifies the value in kB.
+ * convert it to bytes here by multiplying with 1024.
+ */
+ // FIXME currently using default value of 2048
+ private static final int sCursorWindowSize = 2048 * 1024;
+ /*Resources.getSystem().getInteger(
+ com.android.internal.R.integer.config_cursorWindowSize) * 1024;*/
+
+ /**
+ * The native CursorWindow object pointer. (FOR INTERNAL USE ONLY)
+ * @hide
+ */
+ public int mWindowPtr;
+
+ private int mStartPos;
+ private final String mName;
+
+ // FIXME enable CloseGurad
+ //private final CloseGuard mCloseGuard = CloseGuard.get();
+
+ private static native int nativeCreate(String name, int cursorWindowSize);
+ private static native int nativeCreateFromParcel(Parcel parcel);
+ private static native void nativeDispose(int windowPtr);
+ private static native void nativeWriteToParcel(int windowPtr, Parcel parcel);
+
+ private static native void nativeClear(int windowPtr);
+
+ private static native int nativeGetNumRows(int windowPtr);
+ private static native boolean nativeSetNumColumns(int windowPtr, int columnNum);
+ private static native boolean nativeAllocRow(int windowPtr);
+ private static native void nativeFreeLastRow(int windowPtr);
+
+ private static native int nativeGetType(int windowPtr, int row, int column);
+ private static native byte[] nativeGetBlob(int windowPtr, int row, int column);
+ private static native String nativeGetString(int windowPtr, int row, int column);
+ private static native long nativeGetLong(int windowPtr, int row, int column);
+ private static native double nativeGetDouble(int windowPtr, int row, int column);
+ private static native void nativeCopyStringToBuffer(int windowPtr, int row, int column,
+ CharArrayBuffer buffer);
+
+ private static native boolean nativePutBlob(int windowPtr, byte[] value, int row, int column);
+ private static native boolean nativePutString(int windowPtr, String value, int row, int column);
+ private static native boolean nativePutLong(int windowPtr, long value, int row, int column);
+ private static native boolean nativePutDouble(int windowPtr, double value, int row, int column);
+ private static native boolean nativePutNull(int windowPtr, int row, int column);
+
+ private static native String nativeGetName(int windowPtr);
+
+ /**
+ * Creates a new empty cursor window and gives it a name.
+ *
+ * The cursor initially has no rows or columns. Call {@link #setNumColumns(int)} to
+ * set the number of columns before adding any rows to the cursor.
+ *
+ *
+ * @param name The name of the cursor window, or null if none.
+ */
+ public AshmemCursorWindow(String name) {
+ super(false /*using deprecated localWindow super constructor*/);
+ mStartPos = 0;
+ mName = name != null && name.length() != 0 ? name : "";
+ mWindowPtr = nativeCreate(mName, sCursorWindowSize);
+ if (mWindowPtr == 0) {
+ throw new SQLiteException("Cursor window allocation of " +
+ (sCursorWindowSize / 1024) + " kb failed. " + printStats());
+ }
+ //mCloseGuard.open("close");
+ recordNewWindow(Binder.getCallingPid(), mWindowPtr);
+ }
+
+ /**
+ * Creates a new empty cursor window.
+ *
+ * The cursor initially has no rows or columns. Call {@link #setNumColumns(int)} to
+ * set the number of columns before adding any rows to the cursor.
+ *
+ *
+ * @param localWindow True if this window will be used in this process only,
+ * false if it might be sent to another processes. This argument is ignored.
+ *
+ * @deprecated There is no longer a distinction between local and remote
+ * cursor windows. Use the {@link #CursorWindow(String)} constructor instead.
+ */
+ @Deprecated
+ public AshmemCursorWindow(boolean localWindow) {
+ this((String)null);
+ }
+
+ private AshmemCursorWindow(Parcel source) {
+ super(false /*using deprecated localWindow super constructor*/);
+ mStartPos = source.readInt();
+ mWindowPtr = nativeCreateFromParcel(source);
+ if (mWindowPtr == 0) {
+ throw new SQLiteException("Cursor window could not be "
+ + "created from binder.");
+ }
+ mName = nativeGetName(mWindowPtr);
+ //mCloseGuard.open("close");
+ }
+
+ @Override
+ protected void finalize() {
+ try {
+ //if (mCloseGuard != null) {
+ // mCloseGuard.warnIfOpen();
+ //}
+ dispose();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private void dispose() {
+ //if (mCloseGuard != null) {
+ // mCloseGuard.close();
+ //}
+ if (mWindowPtr != 0) {
+ recordClosingOfWindow(mWindowPtr);
+ nativeDispose(mWindowPtr);
+ mWindowPtr = 0;
+ }
+ }
+
+ /**
+ * Gets the name of this cursor window, never null.
+ * @hide
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Clears out the existing contents of the window, making it safe to reuse
+ * for new data.
+ *
+ * The start position ({@link #getStartPosition()}), number of rows ({@link #getNumRows()}),
+ * and number of columns in the cursor are all reset to zero.
+ *
+ */
+ public void clear() {
+ acquireReference();
+ try {
+ mStartPos = 0;
+ nativeClear(mWindowPtr);
+ } finally {
+ releaseReference();
+ }
+ }
+
+ /**
+ * Gets the start position of this cursor window.
+ *
+ * The start position is the zero-based index of the first row that this window contains
+ * relative to the entire result set of the {@link Cursor}.
+ *
+ *
+ * @return The zero-based start position.
+ */
+ public int getStartPosition() {
+ return mStartPos;
+ }
+
+ /**
+ * Sets the start position of this cursor window.
+ *
+ * The start position is the zero-based index of the first row that this window contains
+ * relative to the entire result set of the {@link Cursor}.
+ *
+ *
+ * @param pos The new zero-based start position.
+ */
+ public void setStartPosition(int pos) {
+ mStartPos = pos;
+ }
+
+ /**
+ * Gets the number of rows in this window.
+ *
+ * @return The number of rows in this cursor window.
+ */
+ public int getNumRows() {
+ acquireReference();
+ try {
+ return nativeGetNumRows(mWindowPtr);
+ } finally {
+ releaseReference();
+ }
+ }
+
+ /**
+ * Sets the number of columns in this window.
+ *
+ * This method must be called before any rows are added to the window, otherwise
+ * it will fail to set the number of columns if it differs from the current number
+ * of columns.
+ *
+ *
+ * @param columnNum The new number of columns.
+ * @return True if successful.
+ */
+ public boolean setNumColumns(int columnNum) {
+ acquireReference();
+ try {
+ return nativeSetNumColumns(mWindowPtr, columnNum);
+ } finally {
+ releaseReference();
+ }
+ }
+
+ /**
+ * Allocates a new row at the end of this cursor window.
+ *
+ * @return True if successful, false if the cursor window is out of memory.
+ */
+ public boolean allocRow(){
+ acquireReference();
+ try {
+ return nativeAllocRow(mWindowPtr);
+ } finally {
+ releaseReference();
+ }
+ }
+
+ /**
+ * Frees the last row in this cursor window.
+ */
+ public void freeLastRow(){
+ acquireReference();
+ try {
+ nativeFreeLastRow(mWindowPtr);
+ } finally {
+ releaseReference();
+ }
+ }
+
+ /**
+ * Returns true if the field at the specified row and column index
+ * has type {@link Cursor#FIELD_TYPE_NULL}.
+ *
+ * @param row The zero-based row index.
+ * @param column The zero-based column index.
+ * @return True if the field has type {@link Cursor#FIELD_TYPE_NULL}.
+ * @deprecated Use {@link #getType(int, int)} instead.
+ */
+ @Deprecated
+ public boolean isNull(int row, int column) {
+ return getType(row, column) == Cursor.FIELD_TYPE_NULL;
+ }
+
+ /**
+ * Returns true if the field at the specified row and column index
+ * has type {@link Cursor#FIELD_TYPE_BLOB} or {@link Cursor#FIELD_TYPE_NULL}.
+ *
+ * @param row The zero-based row index.
+ * @param column The zero-based column index.
+ * @return True if the field has type {@link Cursor#FIELD_TYPE_BLOB} or
+ * {@link Cursor#FIELD_TYPE_NULL}.
+ * @deprecated Use {@link #getType(int, int)} instead.
+ */
+ @Deprecated
+ public boolean isBlob(int row, int column) {
+ int type = getType(row, column);
+ return type == Cursor.FIELD_TYPE_BLOB || type == Cursor.FIELD_TYPE_NULL;
+ }
+
+ /**
+ * Returns true if the field at the specified row and column index
+ * has type {@link Cursor#FIELD_TYPE_INTEGER}.
+ *
+ * @param row The zero-based row index.
+ * @param column The zero-based column index.
+ * @return True if the field has type {@link Cursor#FIELD_TYPE_INTEGER}.
+ * @deprecated Use {@link #getType(int, int)} instead.
+ */
+ @Deprecated
+ public boolean isLong(int row, int column) {
+ return getType(row, column) == Cursor.FIELD_TYPE_INTEGER;
+ }
+
+ /**
+ * Returns true if the field at the specified row and column index
+ * has type {@link Cursor#FIELD_TYPE_FLOAT}.
+ *
+ * @param row The zero-based row index.
+ * @param column The zero-based column index.
+ * @return True if the field has type {@link Cursor#FIELD_TYPE_FLOAT}.
+ * @deprecated Use {@link #getType(int, int)} instead.
+ */
+ @Deprecated
+ public boolean isFloat(int row, int column) {
+ return getType(row, column) == Cursor.FIELD_TYPE_FLOAT;
+ }
+
+ /**
+ * Returns true if the field at the specified row and column index
+ * has type {@link Cursor#FIELD_TYPE_STRING} or {@link Cursor#FIELD_TYPE_NULL}.
+ *
+ * @param row The zero-based row index.
+ * @param column The zero-based column index.
+ * @return True if the field has type {@link Cursor#FIELD_TYPE_STRING}
+ * or {@link Cursor#FIELD_TYPE_NULL}.
+ * @deprecated Use {@link #getType(int, int)} instead.
+ */
+ @Deprecated
+ public boolean isString(int row, int column) {
+ int type = getType(row, column);
+ return type == Cursor.FIELD_TYPE_STRING || type == Cursor.FIELD_TYPE_NULL;
+ }
+
+ /**
+ * Returns the type of the field at the specified row and column index.
+ *
+ * The returned field types are:
+ *
+ * - {@link Cursor#FIELD_TYPE_NULL}
+ * - {@link Cursor#FIELD_TYPE_INTEGER}
+ * - {@link Cursor#FIELD_TYPE_FLOAT}
+ * - {@link Cursor#FIELD_TYPE_STRING}
+ * - {@link Cursor#FIELD_TYPE_BLOB}
+ *
+ *
+ *
+ * @param row The zero-based row index.
+ * @param column The zero-based column index.
+ * @return The field type.
+ */
+ public int getType(int row, int column) {
+ acquireReference();
+ try {
+ return nativeGetType(mWindowPtr, row - mStartPos, column);
+ } finally {
+ releaseReference();
+ }
+ }
+
+ /**
+ * Gets the value of the field at the specified row and column index as a byte array.
+ *
+ * The result is determined as follows:
+ *
+ * - If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result
+ * is
null
.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then the result
+ * is the blob value.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result
+ * is the array of bytes that make up the internal representation of the
+ * string value.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_INTEGER} or
+ * {@link Cursor#FIELD_TYPE_FLOAT}, then a {@link SQLiteException} is thrown.
+ *
+ *
+ *
+ * @param row The zero-based row index.
+ * @param column The zero-based column index.
+ * @return The value of the field as a byte array.
+ */
+ public byte[] getBlob(int row, int column) {
+ acquireReference();
+ try {
+ return nativeGetBlob(mWindowPtr, row - mStartPos, column);
+ } finally {
+ releaseReference();
+ }
+ }
+
+ /**
+ * Gets the value of the field at the specified row and column index as a string.
+ *
+ * The result is determined as follows:
+ *
+ * - If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result
+ * is
null
.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result
+ * is the string value.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the result
+ * is a string representation of the integer in decimal, obtained by formatting the
+ * value with the
printf
family of functions using
+ * format specifier %lld
.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the result
+ * is a string representation of the floating-point value in decimal, obtained by
+ * formatting the value with the
printf
family of functions using
+ * format specifier %g
.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a
+ * {@link SQLiteException} is thrown.
+ *
+ *
+ *
+ * @param row The zero-based row index.
+ * @param column The zero-based column index.
+ * @return The value of the field as a string.
+ */
+ public String getString(int row, int column) {
+ acquireReference();
+ try {
+ return nativeGetString(mWindowPtr, row - mStartPos, column);
+ } finally {
+ releaseReference();
+ }
+ }
+
+ /**
+ * Copies the text of the field at the specified row and column index into
+ * a {@link CharArrayBuffer}.
+ *
+ * The buffer is populated as follows:
+ *
+ * - If the buffer is too small for the value to be copied, then it is
+ * automatically resized.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the buffer
+ * is set to an empty string.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the buffer
+ * is set to the contents of the string.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the buffer
+ * is set to a string representation of the integer in decimal, obtained by formatting the
+ * value with the
printf
family of functions using
+ * format specifier %lld
.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the buffer is
+ * set to a string representation of the floating-point value in decimal, obtained by
+ * formatting the value with the
printf
family of functions using
+ * format specifier %g
.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a
+ * {@link SQLiteException} is thrown.
+ *
+ *
+ *
+ * @param row The zero-based row index.
+ * @param column The zero-based column index.
+ * @param buffer The {@link CharArrayBuffer} to hold the string. It is automatically
+ * resized if the requested string is larger than the buffer's current capacity.
+ */
+ public void copyStringToBuffer(int row, int column, CharArrayBuffer buffer) {
+ if (buffer == null) {
+ throw new IllegalArgumentException("CharArrayBuffer should not be null");
+ }
+ acquireReference();
+ try {
+ nativeCopyStringToBuffer(mWindowPtr, row - mStartPos, column, buffer);
+ } finally {
+ releaseReference();
+ }
+ }
+
+ /**
+ * Gets the value of the field at the specified row and column index as a long
.
+ *
+ * The result is determined as follows:
+ *
+ * - If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result
+ * is
0L
.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result
+ * is the value obtained by parsing the string value with
strtoll
.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the result
+ * is the
long
value.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the result
+ * is the floating-point value converted to a
long
.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a
+ * {@link SQLiteException} is thrown.
+ *
+ *
+ *
+ * @param row The zero-based row index.
+ * @param column The zero-based column index.
+ * @return The value of the field as a long
.
+ */
+ public long getLong(int row, int column) {
+ acquireReference();
+ try {
+ return nativeGetLong(mWindowPtr, row - mStartPos, column);
+ } finally {
+ releaseReference();
+ }
+ }
+
+ /**
+ * Gets the value of the field at the specified row and column index as a
+ * double
.
+ *
+ * The result is determined as follows:
+ *
+ * - If the field is of type {@link Cursor#FIELD_TYPE_NULL}, then the result
+ * is
0.0
.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_STRING}, then the result
+ * is the value obtained by parsing the string value with
strtod
.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_INTEGER}, then the result
+ * is the integer value converted to a
double
.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_FLOAT}, then the result
+ * is the
double
value.
+ * - If the field is of type {@link Cursor#FIELD_TYPE_BLOB}, then a
+ * {@link SQLiteException} is thrown.
+ *
+ *
+ *
+ * @param row The zero-based row index.
+ * @param column The zero-based column index.
+ * @return The value of the field as a double
.
+ */
+ public double getDouble(int row, int column) {
+ acquireReference();
+ try {
+ return nativeGetDouble(mWindowPtr, row - mStartPos, column);
+ } finally {
+ releaseReference();
+ }
+ }
+
+ /**
+ * Gets the value of the field at the specified row and column index as a
+ * short
.
+ *
+ * The result is determined by invoking {@link #getLong} and converting the
+ * result to short
.
+ *
+ *
+ * @param row The zero-based row index.
+ * @param column The zero-based column index.
+ * @return The value of the field as a short
.
+ */
+ public short getShort(int row, int column) {
+ return (short) getLong(row, column);
+ }
+
+ /**
+ * Gets the value of the field at the specified row and column index as an
+ * int
.
+ *
+ * The result is determined by invoking {@link #getLong} and converting the
+ * result to int
.
+ *
+ *
+ * @param row The zero-based row index.
+ * @param column The zero-based column index.
+ * @return The value of the field as an int
.
+ */
+ public int getInt(int row, int column) {
+ return (int) getLong(row, column);
+ }
+
+ /**
+ * Gets the value of the field at the specified row and column index as a
+ * float
.
+ *
+ * The result is determined by invoking {@link #getDouble} and converting the
+ * result to float
.
+ *
+ *
+ * @param row The zero-based row index.
+ * @param column The zero-based column index.
+ * @return The value of the field as an float
.
+ */
+ public float getFloat(int row, int column) {
+ return (float) getDouble(row, column);
+ }
+
+ /**
+ * Copies a byte array into the field at the specified row and column index.
+ *
+ * @param value The value to store.
+ * @param row The zero-based row index.
+ * @param column The zero-based column index.
+ * @return True if successful.
+ */
+ public boolean putBlob(byte[] value, int row, int column) {
+ acquireReference();
+ try {
+ return nativePutBlob(mWindowPtr, value, row - mStartPos, column);
+ } finally {
+ releaseReference();
+ }
+ }
+
+ /**
+ * Copies a string into the field at the specified row and column index.
+ *
+ * @param value The value to store.
+ * @param row The zero-based row index.
+ * @param column The zero-based column index.
+ * @return True if successful.
+ */
+ public boolean putString(String value, int row, int column) {
+ acquireReference();
+ try {
+ return nativePutString(mWindowPtr, value, row - mStartPos, column);
+ } finally {
+ releaseReference();
+ }
+ }
+
+ /**
+ * Puts a long integer into the field at the specified row and column index.
+ *
+ * @param value The value to store.
+ * @param row The zero-based row index.
+ * @param column The zero-based column index.
+ * @return True if successful.
+ */
+ public boolean putLong(long value, int row, int column) {
+ acquireReference();
+ try {
+ return nativePutLong(mWindowPtr, value, row - mStartPos, column);
+ } finally {
+ releaseReference();
+ }
+ }
+
+ /**
+ * Puts a double-precision floating point value into the field at the
+ * specified row and column index.
+ *
+ * @param value The value to store.
+ * @param row The zero-based row index.
+ * @param column The zero-based column index.
+ * @return True if successful.
+ */
+ public boolean putDouble(double value, int row, int column) {
+ acquireReference();
+ try {
+ return nativePutDouble(mWindowPtr, value, row - mStartPos, column);
+ } finally {
+ releaseReference();
+ }
+ }
+
+ /**
+ * Puts a null value into the field at the specified row and column index.
+ *
+ * @param row The zero-based row index.
+ * @param column The zero-based column index.
+ * @return True if successful.
+ */
+ public boolean putNull(int row, int column) {
+ acquireReference();
+ try {
+ return nativePutNull(mWindowPtr, row - mStartPos, column);
+ } finally {
+ releaseReference();
+ }
+ }
+
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+ public AshmemCursorWindow createFromParcel(Parcel source) {
+ return new AshmemCursorWindow(source);
+ }
+
+ public AshmemCursorWindow[] newArray(int size) {
+ return new AshmemCursorWindow[size];
+ }
+ };
+
+ public static AshmemCursorWindow newFromParcel(Parcel p) {
+ return CREATOR.createFromParcel(p);
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ acquireReference();
+ try {
+ dest.writeInt(mStartPos);
+ nativeWriteToParcel(mWindowPtr, dest);
+ } finally {
+ releaseReference();
+ }
+
+ if ((flags & Parcelable.PARCELABLE_WRITE_RETURN_VALUE) != 0) {
+ releaseReference();
+ }
+ }
+
+ @Override
+ protected void onAllReferencesReleased() {
+ dispose();
+ }
+
+ private static final SparseIntArray sWindowToPidMap = new SparseIntArray();
+
+ private void recordNewWindow(int pid, int window) {
+ synchronized (sWindowToPidMap) {
+ sWindowToPidMap.put(window, pid);
+ if (Log.isLoggable(STATS_TAG, Log.VERBOSE)) {
+ Log.i(STATS_TAG, "Created a new Cursor. " + printStats());
+ }
+ }
+ }
+
+ private void recordClosingOfWindow(int window) {
+ synchronized (sWindowToPidMap) {
+ if (sWindowToPidMap.size() == 0) {
+ // this means we are not in the ContentProvider.
+ return;
+ }
+ sWindowToPidMap.delete(window);
+ }
+ }
+
+ private String printStats() {
+ StringBuilder buff = new StringBuilder();
+ int myPid = Process.myPid();
+ int total = 0;
+ SparseIntArray pidCounts = new SparseIntArray();
+ synchronized (sWindowToPidMap) {
+ int size = sWindowToPidMap.size();
+ if (size == 0) {
+ // this means we are not in the ContentProvider.
+ return "";
+ }
+ for (int indx = 0; indx < size; indx++) {
+ int pid = sWindowToPidMap.valueAt(indx);
+ int value = pidCounts.get(pid);
+ pidCounts.put(pid, ++value);
+ }
+ }
+ int numPids = pidCounts.size();
+ for (int i = 0; i < numPids;i++) {
+ buff.append(" (# cursors opened by ");
+ int pid = pidCounts.keyAt(i);
+ if (pid == myPid) {
+ buff.append("this proc=");
+ } else {
+ buff.append("pid " + pid + "=");
+ }
+ int num = pidCounts.get(pid);
+ buff.append(num + ")");
+ total += num;
+ }
+ // limit the returned string size to 1000
+ String s = (buff.length() > 980) ? buff.substring(0, 980) : buff.toString();
+ return "# Open Cursors=" + total + s;
+ }
+
+ @Override
+ public int getCursorWindowType() {
+ return ASHMEM_CURSOR_WINDOW;
+ }
+
+ @Override
+ public String toString() {
+ return getName() + " {" + Integer.toHexString(mWindowPtr) + "}";
+ }
+}
diff --git a/src/net/sqlcipher/CursorWindow.java b/src/net/sqlcipher/CursorWindow.java
index 8ab6dbfc..f020662e 100644
--- a/src/net/sqlcipher/CursorWindow.java
+++ b/src/net/sqlcipher/CursorWindow.java
@@ -31,7 +31,7 @@
/**
* A buffer containing multiple cursor rows.
*/
-public class CursorWindow extends android.database.CursorWindow implements Parcelable {
+public class CursorWindow extends net.sqlcipher.AbstractCursorWindow implements Parcelable {
/** The pointer to the native window class */
@SuppressWarnings("unused")
@@ -366,7 +366,6 @@ public boolean isString(int row, int col) {
private native boolean isString_native(int row, int col);
private native boolean isInteger_native(int row, int col);
private native boolean isFloat_native(int row, int col);
-
private native int getType_native(int row, int col);
/**
@@ -627,4 +626,9 @@ protected void onAllReferencesReleased() {
super.onAllReferencesReleased();
}
+
+ @Override
+ public int getCursorWindowType() {
+ return BINDER_CURSOR_WINDOW;
+ }
}
diff --git a/src/net/sqlcipher/database/SQLiteCursor.java b/src/net/sqlcipher/database/SQLiteCursor.java
index 4f293519..314f3879 100644
--- a/src/net/sqlcipher/database/SQLiteCursor.java
+++ b/src/net/sqlcipher/database/SQLiteCursor.java
@@ -17,6 +17,8 @@
package net.sqlcipher.database;
import net.sqlcipher.AbstractWindowedCursor;
+import net.sqlcipher.AbstractCursorWindow;
+import net.sqlcipher.AshmemCursorWindow;
import net.sqlcipher.CursorWindow;
import net.sqlcipher.SQLException;
@@ -127,7 +129,7 @@ private void sendMessage() {
}
public void run() {
// use cached mWindow, to avoid get null mWindow
- CursorWindow cw = mWindow;
+ AbstractCursorWindow cw = mWindow;
Process.setThreadPriority(Process.myTid(), Process.THREAD_PRIORITY_BACKGROUND);
// the cursor's state doesn't change
while (true) {
@@ -275,8 +277,13 @@ public int getCount() {
private void fillWindow (int startPos) {
if (mWindow == null) {
- // If there isn't a window set already it will only be accessed locally
- mWindow = new CursorWindow(true /* the window is local only */);
+ // Use Ashmem Cursor Window for ICS or higher versions
+ if (android.os.Build.VERSION.SDK_INT >= 14 /*android.os.Build.VERSION_CODES.ICS*/) {
+ mWindow = new AshmemCursorWindow("SQLiteCursor");
+ } else {
+ // If there isn't a window set already it will only be accessed locally
+ mWindow = new CursorWindow(true /* the window is local only */);
+ }
} else {
mCursorState++;
queryThreadLock();
@@ -558,7 +565,7 @@ public boolean requery() {
}
@Override
- public void setWindow(CursorWindow window) {
+ public void setWindow(AbstractCursorWindow window) {
if (mWindow != null) {
mCursorState++;
queryThreadLock();
@@ -621,8 +628,13 @@ public void fillWindow(int startPos, android.database.CursorWindow window) {
} */
if (mWindow == null) {
- // If there isn't a window set already it will only be accessed locally
- mWindow = new CursorWindow(true /* the window is local only */);
+ // Use Ashmem Cursor Window for ICS or higher version
+ if (android.os.Build.VERSION.SDK_INT >= 14 /*android.os.Build.VERSION_CODES.ICS*/) {
+ mWindow = new AshmemCursorWindow("SQLiteCursor");
+ } else {
+ // If there isn't a window set already it will only be accessed locally
+ mWindow = new CursorWindow(true /* the window is local only */);
+ }
} else {
mCursorState++;
queryThreadLock();
diff --git a/src/net/sqlcipher/database/SQLiteQuery.java b/src/net/sqlcipher/database/SQLiteQuery.java
index e0bb0d47..d7dbce64 100644
--- a/src/net/sqlcipher/database/SQLiteQuery.java
+++ b/src/net/sqlcipher/database/SQLiteQuery.java
@@ -58,7 +58,7 @@ public class SQLiteQuery extends SQLiteProgram {
* @param window The window to fill into
* @return number of total rows in the query
*/
- /* package */ int fillWindow(CursorWindow window,
+ /* package */ int fillWindow(AbstractCursorWindow window,
int maxRead, int lastPos) {
long timeStart = SystemClock.uptimeMillis();
mDatabase.lock();
@@ -70,8 +70,14 @@ public class SQLiteQuery extends SQLiteProgram {
// if the start pos is not equal to 0, then most likely window is
// too small for the data set, loading by another thread
// is not safe in this situation. the native code will ignore maxRead
- int numRows = native_fill_window(window, window.getStartPosition(), mOffsetIndex,
+ int numRows;
+ if (window.getCursorWindowType() == AbstractCursorWindow.ASHMEM_CURSOR_WINDOW) {
+ numRows = native_fill_ashmem_window(((AshmemCursorWindow) window).mWindowPtr, window.getStartPosition(), mOffsetIndex,
+ maxRead, lastPos);
+ } else {
+ numRows = native_fill_window((CursorWindow)window, window.getStartPosition(), mOffsetIndex,
maxRead, lastPos);
+ }
// Logging
if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
@@ -188,6 +194,9 @@ public void bindString(int index, String value) {
private final native int native_fill_window(CursorWindow window,
int startPos, int offsetParam, int maxRead, int lastPos);
+ private final native int native_fill_ashmem_window(int windowPtr,
+ int startPos, int offsetParam, int maxRead, int lastPos);
+
private final native int native_column_count();
private final native String native_column_name(int columnIndex);