diff -ru mobile/android/base/sqlite/ByteBufferInputStream.java /tmp/mozilla-release/mobile/android/base/sqlite/ByteBufferInputStream.java --- a/mobile/android/base/sqlite/ByteBufferInputStream.java 2015-02-23 22:40:38.708934982 +0100 +++ b/mobile/android/base/sqlite/ByteBufferInputStream.java 2015-01-23 07:00:04.000000000 +0100 @@ -14,7 +14,7 @@ * easier to use. */ public class ByteBufferInputStream extends InputStream { - private ByteBuffer mByteBuffer; + private final ByteBuffer mByteBuffer; public ByteBufferInputStream(ByteBuffer aByteBuffer) { mByteBuffer = aByteBuffer; diff -ru mobile/android/base/sqlite/MatrixBlobCursor.java /tmp/mozilla-release/mobile/android/base/sqlite/MatrixBlobCursor.java --- a/mobile/android/base/sqlite/MatrixBlobCursor.java 2015-02-23 22:40:38.720934982 +0100 +++ b/mobile/android/base/sqlite/MatrixBlobCursor.java 2015-01-23 07:00:04.000000000 +0100 @@ -17,34 +17,37 @@ package org.mozilla.gecko.sqlite; +import java.nio.ByteBuffer; +import java.util.ArrayList; + +import org.mozilla.gecko.AppConstants; import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI; import android.database.AbstractCursor; import android.database.CursorIndexOutOfBoundsException; +import android.util.Log; -import java.nio.ByteBuffer; -import java.util.ArrayList; - -/* - * Android's AbstractCursor throws on getBlob() - * and MatrixCursor forgot to override it. This was fixed - * at some point but old devices are still SOL. - * Oh, and everything in MatrixCursor is private instead of - * protected, so we need to entirely duplicate it here, - * instad of just being able to add the missing method. - */ /** * A mutable cursor implementation backed by an array of {@code Object}s. Use * {@link #newRow()} to add rows. Automatically expands internal capacity * as needed. + * + * This class provides one missing feature from Android's MatrixCursor: + * the implementation of getBlob that was inadvertently omitted from API 9 (and + * perhaps later; it's present in 14). + * + * MatrixCursor is all private, so we entirely duplicate it here. */ public class MatrixBlobCursor extends AbstractCursor { + private static final String LOGTAG = "GeckoMatrixCursor"; private final String[] columnNames; - private Object[] data; - private int rowCount = 0; private final int columnCount; + private int rowCount; + + Object[] data; + /** * Constructs a new cursor with the given initial capacity. * @@ -140,17 +143,18 @@ */ @WrapElementForJNI public void addRow(Iterable columnValues) { - int start = rowCount * columnCount; - int end = start + columnCount; - ensureCapacity(end); + final int start = rowCount * columnCount; if (columnValues instanceof ArrayList) { addRow((ArrayList) columnValues, start); return; } + final int end = start + columnCount; int current = start; - Object[] localData = data; + + ensureCapacity(end); + final Object[] localData = data; for (Object columnValue : columnValues) { if (current == end) { // TODO: null out row? @@ -173,39 +177,47 @@ /** Optimization for {@link ArrayList}. */ @WrapElementForJNI private void addRow(ArrayList columnValues, int start) { - int size = columnValues.size(); + final int size = columnValues.size(); if (size != columnCount) { throw new IllegalArgumentException("columnNames.length = " + columnCount + ", columnValues.size() = " + size); } - rowCount++; - Object[] localData = data; + final int end = start + columnCount; + ensureCapacity(end); + + // Take a reference just in case someone calls ensureCapacity + // and `data` gets replaced by a new array! + final Object[] localData = data; for (int i = 0; i < size; i++) { localData[start + i] = columnValues.get(i); } + + rowCount++; } - /** Ensures that this cursor has enough capacity. */ - private void ensureCapacity(int size) { - if (size > data.length) { - Object[] oldData = this.data; - int newSize = data.length * 2; - if (newSize < size) { - newSize = size; - } - this.data = new Object[newSize]; - System.arraycopy(oldData, 0, this.data, 0, oldData.length); + /** + * Ensures that this cursor has enough capacity. If it needs to allocate + * a new array, the existing capacity will be at least doubled. + */ + private void ensureCapacity(final int size) { + if (size <= data.length) { + return; } + + final Object[] oldData = this.data; + this.data = new Object[Math.max(size, data.length * 2)]; + System.arraycopy(oldData, 0, this.data, 0, oldData.length); } /** * Builds a row, starting from the left-most column and adding one column * value at a time. Follows the same ordering as the column names specified * at cursor construction time. + * + * Not thread-safe. */ public class RowBuilder { - private int index; private final int endIndex; @@ -221,10 +233,9 @@ * values * @return this builder to support chaining */ - public RowBuilder add(Object columnValue) { + public RowBuilder add(final Object columnValue) { if (index == endIndex) { - throw new CursorIndexOutOfBoundsException( - "No more columns left."); + throw new CursorIndexOutOfBoundsException("No more columns left."); } data[index++] = columnValue; @@ -232,6 +243,9 @@ } } + /** + * Not thread safe. + */ public void set(int column, Object value) { if (column < 0 || column >= columnCount) { throw new CursorIndexOutOfBoundsException("Requested column: " @@ -266,7 +280,7 @@ @Override public short getShort(int column) { - Object value = get(column); + final Object value = get(column); if (value == null) return 0; if (value instanceof Number) return ((Number) value).shortValue(); return Short.parseShort(value.toString()); @@ -311,10 +325,11 @@ if (value instanceof byte[]) { return (byte[]) value; } + if (value instanceof ByteBuffer) { - ByteBuffer data = (ByteBuffer)value; - byte[] byteArray = new byte[data.remaining()]; - data.get(byteArray); + final ByteBuffer bytes = (ByteBuffer) value; + byte[] byteArray = new byte[bytes.remaining()]; + bytes.get(byteArray); return byteArray; } throw new UnsupportedOperationException("BLOB Object not of known type"); @@ -324,4 +339,15 @@ public boolean isNull(int column) { return get(column) == null; } + + @Override + protected void finalize() { + if (AppConstants.DEBUG_BUILD) { + if (!isClosed()) { + Log.e(LOGTAG, "Cursor finalized without being closed", new RuntimeException("stack")); + } + } + + super.finalize(); + } } diff -ru mobile/android/base/sqlite/SQLiteBridge.java /tmp/mozilla-release/mobile/android/base/sqlite/SQLiteBridge.java --- a/mobile/android/base/sqlite/SQLiteBridge.java 2015-02-23 22:40:38.716934982 +0100 +++ b/mobile/android/base/sqlite/SQLiteBridge.java 2015-01-23 07:00:04.000000000 +0100 @@ -26,16 +26,16 @@ private static final String LOGTAG = "SQLiteBridge"; // Path to the database. If this database was not opened with openDatabase, we reopen it every query. - private String mDb; + private final String mDb; // Pointer to the database if it was opened with openDatabase. 0 implies closed. - protected volatile long mDbPointer = 0L; + protected volatile long mDbPointer; // Values remembered after a query. private long[] mQueryResults; - private boolean mTransactionSuccess = false; - private boolean mInTransaction = false; + private boolean mTransactionSuccess; + private boolean mInTransaction; private static final int RESULT_INSERT_ROW_ID = 0; private static final int RESULT_ROWS_CHANGED = 1; @@ -227,6 +227,7 @@ cursor.moveToFirst(); String version = cursor.getString(0); ret = Integer.parseInt(version); + cursor.close(); } return ret; }