M7350v1_en_gpl

This commit is contained in:
T
2024-09-09 08:52:07 +00:00
commit f9cc65cfda
65988 changed files with 26357421 additions and 0 deletions
@@ -0,0 +1,638 @@
/*
* 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 android.database;
import android.content.ContentResolver;
import android.net.Uri;
import android.util.Config;
import android.util.Log;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import java.lang.ref.WeakReference;
import java.lang.UnsupportedOperationException;
import java.util.HashMap;
import java.util.Map;
/**
* This is an abstract cursor class that handles a lot of the common code
* that all cursors need to deal with and is provided for convenience reasons.
*/
public abstract class AbstractCursor implements CrossProcessCursor {
private static final String TAG = "Cursor";
DataSetObservable mDataSetObservable = new DataSetObservable();
ContentObservable mContentObservable = new ContentObservable();
/* -------------------------------------------------------- */
/* These need to be implemented by subclasses */
abstract public int getCount();
abstract public String[] getColumnNames();
abstract public String getString(int column);
abstract public short getShort(int column);
abstract public int getInt(int column);
abstract public long getLong(int column);
abstract public float getFloat(int column);
abstract public double getDouble(int column);
abstract public boolean isNull(int column);
// TODO implement getBlob in all cursor types
public byte[] getBlob(int column) {
throw new UnsupportedOperationException("getBlob is not supported");
}
/* -------------------------------------------------------- */
/* Methods that may optionally be implemented by subclasses */
/**
* returns a pre-filled window, return NULL if no such window
*/
public CursorWindow getWindow() {
return null;
}
public int getColumnCount() {
return getColumnNames().length;
}
public void deactivate() {
deactivateInternal();
}
/**
* @hide
*/
public void deactivateInternal() {
if (mSelfObserver != null) {
mContentResolver.unregisterContentObserver(mSelfObserver);
mSelfObserverRegistered = false;
}
mDataSetObservable.notifyInvalidated();
}
public boolean requery() {
if (mSelfObserver != null && mSelfObserverRegistered == false) {
mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver);
mSelfObserverRegistered = true;
}
mDataSetObservable.notifyChanged();
return true;
}
public boolean isClosed() {
return mClosed;
}
public void close() {
mClosed = true;
mContentObservable.unregisterAll();
deactivateInternal();
}
/**
* @hide
* @deprecated
*/
public boolean commitUpdates(Map<? extends Long,? extends Map<String,Object>> values) {
return false;
}
/**
* @hide
* @deprecated
*/
public boolean deleteRow() {
return false;
}
/**
* This function is called every time the cursor is successfully scrolled
* to a new position, giving the subclass a chance to update any state it
* may have. If it returns false the move function will also do so and the
* cursor will scroll to the beforeFirst position.
*
* @param oldPosition the position that we're moving from
* @param newPosition the position that we're moving to
* @return true if the move is successful, false otherwise
*/
public boolean onMove(int oldPosition, int newPosition) {
return true;
}
public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
// Default implementation, uses getString
String result = getString(columnIndex);
if (result != null) {
char[] data = buffer.data;
if (data == null || data.length < result.length()) {
buffer.data = result.toCharArray();
} else {
result.getChars(0, result.length(), data, 0);
}
buffer.sizeCopied = result.length();
}
}
/* -------------------------------------------------------- */
/* Implementation */
public AbstractCursor() {
mPos = -1;
mRowIdColumnIndex = -1;
mCurrentRowID = null;
mUpdatedRows = new HashMap<Long, Map<String, Object>>();
}
public final int getPosition() {
return mPos;
}
public final boolean moveToPosition(int position) {
// Make sure position isn't past the end of the cursor
final int count = getCount();
if (position >= count) {
mPos = count;
return false;
}
// Make sure position isn't before the beginning of the cursor
if (position < 0) {
mPos = -1;
return false;
}
// Check for no-op moves, and skip the rest of the work for them
if (position == mPos) {
return true;
}
boolean result = onMove(mPos, position);
if (result == false) {
mPos = -1;
} else {
mPos = position;
if (mRowIdColumnIndex != -1) {
mCurrentRowID = Long.valueOf(getLong(mRowIdColumnIndex));
}
}
return result;
}
/**
* Copy data from cursor to CursorWindow
* @param position start position of data
* @param window
*/
public void fillWindow(int position, CursorWindow window) {
if (position < 0 || position > getCount()) {
return;
}
window.acquireReference();
try {
int oldpos = mPos;
mPos = position - 1;
window.clear();
window.setStartPosition(position);
int columnNum = getColumnCount();
window.setNumColumns(columnNum);
while (moveToNext() && window.allocRow()) {
for (int i = 0; i < columnNum; i++) {
String field = getString(i);
if (field != null) {
if (!window.putString(field, mPos, i)) {
window.freeLastRow();
break;
}
} else {
if (!window.putNull(mPos, i)) {
window.freeLastRow();
break;
}
}
}
}
mPos = oldpos;
} catch (IllegalStateException e){
// simply ignore it
} finally {
window.releaseReference();
}
}
public final boolean move(int offset) {
return moveToPosition(mPos + offset);
}
public final boolean moveToFirst() {
return moveToPosition(0);
}
public final boolean moveToLast() {
return moveToPosition(getCount() - 1);
}
public final boolean moveToNext() {
return moveToPosition(mPos + 1);
}
public final boolean moveToPrevious() {
return moveToPosition(mPos - 1);
}
public final boolean isFirst() {
return mPos == 0 && getCount() != 0;
}
public final boolean isLast() {
int cnt = getCount();
return mPos == (cnt - 1) && cnt != 0;
}
public final boolean isBeforeFirst() {
if (getCount() == 0) {
return true;
}
return mPos == -1;
}
public final boolean isAfterLast() {
if (getCount() == 0) {
return true;
}
return mPos == getCount();
}
public int getColumnIndex(String columnName) {
// Hack according to bug 903852
final int periodIndex = columnName.lastIndexOf('.');
if (periodIndex != -1) {
Exception e = new Exception();
Log.e(TAG, "requesting column name with table name -- " + columnName, e);
columnName = columnName.substring(periodIndex + 1);
}
String columnNames[] = getColumnNames();
int length = columnNames.length;
for (int i = 0; i < length; i++) {
if (columnNames[i].equalsIgnoreCase(columnName)) {
return i;
}
}
if (Config.LOGV) {
if (getCount() > 0) {
Log.w("AbstractCursor", "Unknown column " + columnName);
}
}
return -1;
}
public int getColumnIndexOrThrow(String columnName) {
final int index = getColumnIndex(columnName);
if (index < 0) {
throw new IllegalArgumentException("column '" + columnName + "' does not exist");
}
return index;
}
public String getColumnName(int columnIndex) {
return getColumnNames()[columnIndex];
}
/**
* @hide
* @deprecated
*/
public boolean updateBlob(int columnIndex, byte[] value) {
return update(columnIndex, value);
}
/**
* @hide
* @deprecated
*/
public boolean updateString(int columnIndex, String value) {
return update(columnIndex, value);
}
/**
* @hide
* @deprecated
*/
public boolean updateShort(int columnIndex, short value) {
return update(columnIndex, Short.valueOf(value));
}
/**
* @hide
* @deprecated
*/
public boolean updateInt(int columnIndex, int value) {
return update(columnIndex, Integer.valueOf(value));
}
/**
* @hide
* @deprecated
*/
public boolean updateLong(int columnIndex, long value) {
return update(columnIndex, Long.valueOf(value));
}
/**
* @hide
* @deprecated
*/
public boolean updateFloat(int columnIndex, float value) {
return update(columnIndex, Float.valueOf(value));
}
/**
* @hide
* @deprecated
*/
public boolean updateDouble(int columnIndex, double value) {
return update(columnIndex, Double.valueOf(value));
}
/**
* @hide
* @deprecated
*/
public boolean updateToNull(int columnIndex) {
return update(columnIndex, null);
}
/**
* @hide
* @deprecated
*/
public boolean update(int columnIndex, Object obj) {
if (!supportsUpdates()) {
return false;
}
// Long.valueOf() returns null sometimes!
// Long rowid = Long.valueOf(getLong(mRowIdColumnIndex));
Long rowid = new Long(getLong(mRowIdColumnIndex));
if (rowid == null) {
throw new IllegalStateException("null rowid. mRowIdColumnIndex = " + mRowIdColumnIndex);
}
synchronized(mUpdatedRows) {
Map<String, Object> row = mUpdatedRows.get(rowid);
if (row == null) {
row = new HashMap<String, Object>();
mUpdatedRows.put(rowid, row);
}
row.put(getColumnNames()[columnIndex], obj);
}
return true;
}
/**
* Returns <code>true</code> if there are pending updates that have not yet been committed.
*
* @return <code>true</code> if there are pending updates that have not yet been committed.
* @hide
* @deprecated
*/
public boolean hasUpdates() {
synchronized(mUpdatedRows) {
return mUpdatedRows.size() > 0;
}
}
/**
* @hide
* @deprecated
*/
public void abortUpdates() {
synchronized(mUpdatedRows) {
mUpdatedRows.clear();
}
}
/**
* @hide
* @deprecated
*/
public boolean commitUpdates() {
return commitUpdates(null);
}
/**
* @hide
* @deprecated
*/
public boolean supportsUpdates() {
return mRowIdColumnIndex != -1;
}
public void registerContentObserver(ContentObserver observer) {
mContentObservable.registerObserver(observer);
}
public void unregisterContentObserver(ContentObserver observer) {
// cursor will unregister all observers when it close
if (!mClosed) {
mContentObservable.unregisterObserver(observer);
}
}
/**
* This is hidden until the data set change model has been re-evaluated.
* @hide
*/
protected void notifyDataSetChange() {
mDataSetObservable.notifyChanged();
}
/**
* This is hidden until the data set change model has been re-evaluated.
* @hide
*/
protected DataSetObservable getDataSetObservable() {
return mDataSetObservable;
}
public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
}
public void unregisterDataSetObserver(DataSetObserver observer) {
mDataSetObservable.unregisterObserver(observer);
}
/**
* Subclasses must call this method when they finish committing updates to notify all
* observers.
*
* @param selfChange
*/
protected void onChange(boolean selfChange) {
synchronized (mSelfObserverLock) {
mContentObservable.dispatchChange(selfChange);
if (mNotifyUri != null && selfChange) {
mContentResolver.notifyChange(mNotifyUri, mSelfObserver);
}
}
}
/**
* Specifies a content URI to watch for changes.
*
* @param cr The content resolver from the caller's context.
* @param notifyUri The URI to watch for changes. This can be a
* specific row URI, or a base URI for a whole class of content.
*/
public void setNotificationUri(ContentResolver cr, Uri notifyUri) {
synchronized (mSelfObserverLock) {
mNotifyUri = notifyUri;
mContentResolver = cr;
if (mSelfObserver != null) {
mContentResolver.unregisterContentObserver(mSelfObserver);
}
mSelfObserver = new SelfContentObserver(this);
mContentResolver.registerContentObserver(mNotifyUri, true, mSelfObserver);
mSelfObserverRegistered = true;
}
}
public boolean getWantsAllOnMoveCalls() {
return false;
}
public Bundle getExtras() {
return Bundle.EMPTY;
}
public Bundle respond(Bundle extras) {
return Bundle.EMPTY;
}
/**
* This function returns true if the field has been updated and is
* used in conjunction with {@link #getUpdatedField} to allow subclasses to
* support reading uncommitted updates. NOTE: This function and
* {@link #getUpdatedField} should be called together inside of a
* block synchronized on mUpdatedRows.
*
* @param columnIndex the column index of the field to check
* @return true if the field has been updated, false otherwise
*/
protected boolean isFieldUpdated(int columnIndex) {
if (mRowIdColumnIndex != -1 && mUpdatedRows.size() > 0) {
Map<String, Object> updates = mUpdatedRows.get(mCurrentRowID);
if (updates != null && updates.containsKey(getColumnNames()[columnIndex])) {
return true;
}
}
return false;
}
/**
* This function returns the uncommitted updated value for the field
* at columnIndex. NOTE: This function and {@link #isFieldUpdated} should
* be called together inside of a block synchronized on mUpdatedRows.
*
* @param columnIndex the column index of the field to retrieve
* @return the updated value
*/
protected Object getUpdatedField(int columnIndex) {
Map<String, Object> updates = mUpdatedRows.get(mCurrentRowID);
return updates.get(getColumnNames()[columnIndex]);
}
/**
* This function throws CursorIndexOutOfBoundsException if
* the cursor position is out of bounds. Subclass implementations of
* the get functions should call this before attempting
* to retrieve data.
*
* @throws CursorIndexOutOfBoundsException
*/
protected void checkPosition() {
if (-1 == mPos || getCount() == mPos) {
throw new CursorIndexOutOfBoundsException(mPos, getCount());
}
}
@Override
protected void finalize() {
if (mSelfObserver != null && mSelfObserverRegistered == true) {
mContentResolver.unregisterContentObserver(mSelfObserver);
}
}
/**
* Cursors use this class to track changes others make to their URI.
*/
protected static class SelfContentObserver extends ContentObserver {
WeakReference<AbstractCursor> mCursor;
public SelfContentObserver(AbstractCursor cursor) {
super(null);
mCursor = new WeakReference<AbstractCursor>(cursor);
}
@Override
public boolean deliverSelfNotifications() {
return false;
}
@Override
public void onChange(boolean selfChange) {
AbstractCursor cursor = mCursor.get();
if (cursor != null) {
cursor.onChange(false);
}
}
}
/**
* This HashMap contains a mapping from Long rowIDs to another Map
* that maps from String column names to new values. A NULL value means to
* remove an existing value, and all numeric values are in their class
* forms, i.e. Integer, Long, Float, etc.
*/
protected HashMap<Long, Map<String, Object>> mUpdatedRows;
/**
* This must be set to the index of the row ID column by any
* subclass that wishes to support updates.
*/
protected int mRowIdColumnIndex;
protected int mPos;
protected Long mCurrentRowID;
protected ContentResolver mContentResolver;
protected boolean mClosed = false;
private Uri mNotifyUri;
private ContentObserver mSelfObserver;
final private Object mSelfObserverLock = new Object();
private boolean mSelfObserverRegistered;
}
@@ -0,0 +1,246 @@
/*
* 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 android.database;
/**
* A base class for Cursors that store their data in {@link CursorWindow}s.
*/
public abstract class AbstractWindowedCursor extends AbstractCursor
{
@Override
public byte[] getBlob(int columnIndex)
{
checkPosition();
synchronized(mUpdatedRows) {
if (isFieldUpdated(columnIndex)) {
return (byte[])getUpdatedField(columnIndex);
}
}
return mWindow.getBlob(mPos, columnIndex);
}
@Override
public String getString(int columnIndex)
{
checkPosition();
synchronized(mUpdatedRows) {
if (isFieldUpdated(columnIndex)) {
return (String)getUpdatedField(columnIndex);
}
}
return mWindow.getString(mPos, columnIndex);
}
@Override
public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer)
{
checkPosition();
synchronized(mUpdatedRows) {
if (isFieldUpdated(columnIndex)) {
super.copyStringToBuffer(columnIndex, buffer);
}
}
mWindow.copyStringToBuffer(mPos, columnIndex, buffer);
}
@Override
public short getShort(int columnIndex)
{
checkPosition();
synchronized(mUpdatedRows) {
if (isFieldUpdated(columnIndex)) {
Number value = (Number)getUpdatedField(columnIndex);
return value.shortValue();
}
}
return mWindow.getShort(mPos, columnIndex);
}
@Override
public int getInt(int columnIndex)
{
checkPosition();
synchronized(mUpdatedRows) {
if (isFieldUpdated(columnIndex)) {
Number value = (Number)getUpdatedField(columnIndex);
return value.intValue();
}
}
return mWindow.getInt(mPos, columnIndex);
}
@Override
public long getLong(int columnIndex)
{
checkPosition();
synchronized(mUpdatedRows) {
if (isFieldUpdated(columnIndex)) {
Number value = (Number)getUpdatedField(columnIndex);
return value.longValue();
}
}
return mWindow.getLong(mPos, columnIndex);
}
@Override
public float getFloat(int columnIndex)
{
checkPosition();
synchronized(mUpdatedRows) {
if (isFieldUpdated(columnIndex)) {
Number value = (Number)getUpdatedField(columnIndex);
return value.floatValue();
}
}
return mWindow.getFloat(mPos, columnIndex);
}
@Override
public double getDouble(int columnIndex)
{
checkPosition();
synchronized(mUpdatedRows) {
if (isFieldUpdated(columnIndex)) {
Number value = (Number)getUpdatedField(columnIndex);
return value.doubleValue();
}
}
return mWindow.getDouble(mPos, columnIndex);
}
@Override
public boolean isNull(int columnIndex)
{
checkPosition();
synchronized(mUpdatedRows) {
if (isFieldUpdated(columnIndex)) {
return getUpdatedField(columnIndex) == null;
}
}
return mWindow.isNull(mPos, columnIndex);
}
public boolean isBlob(int columnIndex)
{
checkPosition();
synchronized(mUpdatedRows) {
if (isFieldUpdated(columnIndex)) {
Object object = getUpdatedField(columnIndex);
return object == null || object instanceof byte[];
}
}
return mWindow.isBlob(mPos, columnIndex);
}
public boolean isString(int columnIndex)
{
checkPosition();
synchronized(mUpdatedRows) {
if (isFieldUpdated(columnIndex)) {
Object object = getUpdatedField(columnIndex);
return object == null || object instanceof String;
}
}
return mWindow.isString(mPos, columnIndex);
}
public boolean isLong(int columnIndex)
{
checkPosition();
synchronized(mUpdatedRows) {
if (isFieldUpdated(columnIndex)) {
Object object = getUpdatedField(columnIndex);
return object != null && (object instanceof Integer || object instanceof Long);
}
}
return mWindow.isLong(mPos, columnIndex);
}
public boolean isFloat(int columnIndex)
{
checkPosition();
synchronized(mUpdatedRows) {
if (isFieldUpdated(columnIndex)) {
Object object = getUpdatedField(columnIndex);
return object != null && (object instanceof Float || object instanceof Double);
}
}
return mWindow.isFloat(mPos, columnIndex);
}
@Override
protected void checkPosition()
{
super.checkPosition();
if (mWindow == null) {
throw new StaleDataException("Access closed cursor");
}
}
@Override
public CursorWindow getWindow() {
return mWindow;
}
/**
* Set a new cursor window to cursor, usually set a remote cursor window
* @param window cursor window
*/
public void setWindow(CursorWindow window) {
if (mWindow != null) {
mWindow.close();
}
mWindow = window;
}
public boolean hasWindow() {
return mWindow != null;
}
/**
* 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;
}
@@ -0,0 +1,440 @@
/*
* 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 android.database;
import android.os.Binder;
import android.os.RemoteException;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Bundle;
import java.util.HashMap;
import java.util.Map;
/**
* Native implementation of the bulk cursor. This is only for use in implementing
* IPC, application code should use the Cursor interface.
*
* {@hide}
*/
public abstract class BulkCursorNative extends Binder implements IBulkCursor
{
public BulkCursorNative()
{
attachInterface(this, descriptor);
}
/**
* Cast a Binder object into a content resolver interface, generating
* a proxy if needed.
*/
static public IBulkCursor asInterface(IBinder obj)
{
if (obj == null) {
return null;
}
IBulkCursor in = (IBulkCursor)obj.queryLocalInterface(descriptor);
if (in != null) {
return in;
}
return new BulkCursorProxy(obj);
}
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
try {
switch (code) {
case GET_CURSOR_WINDOW_TRANSACTION: {
data.enforceInterface(IBulkCursor.descriptor);
int startPos = data.readInt();
CursorWindow window = getWindow(startPos);
if (window == null) {
reply.writeInt(0);
return true;
}
reply.writeNoException();
reply.writeInt(1);
window.writeToParcel(reply, 0);
return true;
}
case COUNT_TRANSACTION: {
data.enforceInterface(IBulkCursor.descriptor);
int count = count();
reply.writeNoException();
reply.writeInt(count);
return true;
}
case GET_COLUMN_NAMES_TRANSACTION: {
data.enforceInterface(IBulkCursor.descriptor);
String[] columnNames = getColumnNames();
reply.writeNoException();
reply.writeInt(columnNames.length);
int length = columnNames.length;
for (int i = 0; i < length; i++) {
reply.writeString(columnNames[i]);
}
return true;
}
case DEACTIVATE_TRANSACTION: {
data.enforceInterface(IBulkCursor.descriptor);
deactivate();
reply.writeNoException();
return true;
}
case CLOSE_TRANSACTION: {
data.enforceInterface(IBulkCursor.descriptor);
close();
reply.writeNoException();
return true;
}
case REQUERY_TRANSACTION: {
data.enforceInterface(IBulkCursor.descriptor);
IContentObserver observer =
IContentObserver.Stub.asInterface(data.readStrongBinder());
CursorWindow window = CursorWindow.CREATOR.createFromParcel(data);
int count = requery(observer, window);
reply.writeNoException();
reply.writeInt(count);
reply.writeBundle(getExtras());
return true;
}
case UPDATE_ROWS_TRANSACTION: {
data.enforceInterface(IBulkCursor.descriptor);
// TODO - what ClassLoader should be passed to readHashMap?
// TODO - switch to Bundle
HashMap<Long, Map<String, Object>> values = data.readHashMap(null);
boolean result = updateRows(values);
reply.writeNoException();
reply.writeInt((result == true ? 1 : 0));
return true;
}
case DELETE_ROW_TRANSACTION: {
data.enforceInterface(IBulkCursor.descriptor);
int position = data.readInt();
boolean result = deleteRow(position);
reply.writeNoException();
reply.writeInt((result == true ? 1 : 0));
return true;
}
case ON_MOVE_TRANSACTION: {
data.enforceInterface(IBulkCursor.descriptor);
int position = data.readInt();
onMove(position);
reply.writeNoException();
return true;
}
case WANTS_ON_MOVE_TRANSACTION: {
data.enforceInterface(IBulkCursor.descriptor);
boolean result = getWantsAllOnMoveCalls();
reply.writeNoException();
reply.writeInt(result ? 1 : 0);
return true;
}
case GET_EXTRAS_TRANSACTION: {
data.enforceInterface(IBulkCursor.descriptor);
Bundle extras = getExtras();
reply.writeNoException();
reply.writeBundle(extras);
return true;
}
case RESPOND_TRANSACTION: {
data.enforceInterface(IBulkCursor.descriptor);
Bundle extras = data.readBundle();
Bundle returnExtras = respond(extras);
reply.writeNoException();
reply.writeBundle(returnExtras);
return true;
}
}
} catch (Exception e) {
DatabaseUtils.writeExceptionToParcel(reply, e);
return true;
}
return super.onTransact(code, data, reply, flags);
}
public IBinder asBinder()
{
return this;
}
}
final class BulkCursorProxy implements IBulkCursor {
private IBinder mRemote;
private Bundle mExtras;
public BulkCursorProxy(IBinder remote)
{
mRemote = remote;
mExtras = null;
}
public IBinder asBinder()
{
return mRemote;
}
public CursorWindow getWindow(int startPos) throws RemoteException
{
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IBulkCursor.descriptor);
data.writeInt(startPos);
mRemote.transact(GET_CURSOR_WINDOW_TRANSACTION, data, reply, 0);
DatabaseUtils.readExceptionFromParcel(reply);
CursorWindow window = null;
if (reply.readInt() == 1) {
window = CursorWindow.newFromParcel(reply);
}
data.recycle();
reply.recycle();
return window;
}
public void onMove(int position) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IBulkCursor.descriptor);
data.writeInt(position);
mRemote.transact(ON_MOVE_TRANSACTION, data, reply, 0);
DatabaseUtils.readExceptionFromParcel(reply);
data.recycle();
reply.recycle();
}
public int count() throws RemoteException
{
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IBulkCursor.descriptor);
boolean result = mRemote.transact(COUNT_TRANSACTION, data, reply, 0);
DatabaseUtils.readExceptionFromParcel(reply);
int count;
if (result == false) {
count = -1;
} else {
count = reply.readInt();
}
data.recycle();
reply.recycle();
return count;
}
public String[] getColumnNames() throws RemoteException
{
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IBulkCursor.descriptor);
mRemote.transact(GET_COLUMN_NAMES_TRANSACTION, data, reply, 0);
DatabaseUtils.readExceptionFromParcel(reply);
String[] columnNames = null;
int numColumns = reply.readInt();
columnNames = new String[numColumns];
for (int i = 0; i < numColumns; i++) {
columnNames[i] = reply.readString();
}
data.recycle();
reply.recycle();
return columnNames;
}
public void deactivate() throws RemoteException
{
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IBulkCursor.descriptor);
mRemote.transact(DEACTIVATE_TRANSACTION, data, reply, 0);
DatabaseUtils.readExceptionFromParcel(reply);
data.recycle();
reply.recycle();
}
public void close() throws RemoteException
{
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IBulkCursor.descriptor);
mRemote.transact(CLOSE_TRANSACTION, data, reply, 0);
DatabaseUtils.readExceptionFromParcel(reply);
data.recycle();
reply.recycle();
}
public int requery(IContentObserver observer, CursorWindow window) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IBulkCursor.descriptor);
data.writeStrongInterface(observer);
window.writeToParcel(data, 0);
boolean result = mRemote.transact(REQUERY_TRANSACTION, data, reply, 0);
DatabaseUtils.readExceptionFromParcel(reply);
int count;
if (!result) {
count = -1;
} else {
count = reply.readInt();
mExtras = reply.readBundle();
}
data.recycle();
reply.recycle();
return count;
}
public boolean updateRows(Map values) throws RemoteException
{
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IBulkCursor.descriptor);
data.writeMap(values);
mRemote.transact(UPDATE_ROWS_TRANSACTION, data, reply, 0);
DatabaseUtils.readExceptionFromParcel(reply);
boolean result = (reply.readInt() == 1 ? true : false);
data.recycle();
reply.recycle();
return result;
}
public boolean deleteRow(int position) throws RemoteException
{
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IBulkCursor.descriptor);
data.writeInt(position);
mRemote.transact(DELETE_ROW_TRANSACTION, data, reply, 0);
DatabaseUtils.readExceptionFromParcel(reply);
boolean result = (reply.readInt() == 1 ? true : false);
data.recycle();
reply.recycle();
return result;
}
public boolean getWantsAllOnMoveCalls() throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IBulkCursor.descriptor);
mRemote.transact(WANTS_ON_MOVE_TRANSACTION, data, reply, 0);
DatabaseUtils.readExceptionFromParcel(reply);
int result = reply.readInt();
data.recycle();
reply.recycle();
return result != 0;
}
public Bundle getExtras() throws RemoteException {
if (mExtras == null) {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IBulkCursor.descriptor);
mRemote.transact(GET_EXTRAS_TRANSACTION, data, reply, 0);
DatabaseUtils.readExceptionFromParcel(reply);
mExtras = reply.readBundle();
data.recycle();
reply.recycle();
}
return mExtras;
}
public Bundle respond(Bundle extras) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IBulkCursor.descriptor);
data.writeBundle(extras);
mRemote.transact(RESPOND_TRANSACTION, data, reply, 0);
DatabaseUtils.readExceptionFromParcel(reply);
Bundle returnExtras = reply.readBundle();
data.recycle();
reply.recycle();
return returnExtras;
}
}
@@ -0,0 +1,283 @@
/*
* 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 android.database;
import android.os.RemoteException;
import android.os.Bundle;
import android.util.Log;
import java.util.Map;
/**
* Adapts an {@link IBulkCursor} to a {@link Cursor} for use in the local
* process.
*
* {@hide}
*/
public final class BulkCursorToCursorAdaptor extends AbstractWindowedCursor {
private static final String TAG = "BulkCursor";
private SelfContentObserver mObserverBridge;
private IBulkCursor mBulkCursor;
private int mCount;
private String[] mColumns;
private boolean mWantsAllOnMoveCalls;
public void set(IBulkCursor bulkCursor) {
mBulkCursor = bulkCursor;
try {
mCount = mBulkCursor.count();
mWantsAllOnMoveCalls = mBulkCursor.getWantsAllOnMoveCalls();
// Search for the rowID column index and set it for our parent
mColumns = mBulkCursor.getColumnNames();
mRowIdColumnIndex = findRowIdColumnIndex(mColumns);
} catch (RemoteException ex) {
Log.e(TAG, "Setup failed because the remote process is dead");
}
}
/**
* Version of set() that does fewer Binder calls if the caller
* already knows BulkCursorToCursorAdaptor's properties.
*/
public void set(IBulkCursor bulkCursor, int count, int idIndex) {
mBulkCursor = bulkCursor;
mColumns = null; // lazily retrieved
mCount = count;
mRowIdColumnIndex = idIndex;
}
/**
* Returns column index of "_id" column, or -1 if not found.
*/
public static int findRowIdColumnIndex(String[] columnNames) {
int length = columnNames.length;
for (int i = 0; i < length; i++) {
if (columnNames[i].equals("_id")) {
return i;
}
}
return -1;
}
/**
* Gets a SelfDataChangeOberserver that can be sent to a remote
* process to receive change notifications over IPC.
*
* @return A SelfContentObserver hooked up to this Cursor
*/
public synchronized IContentObserver getObserver() {
if (mObserverBridge == null) {
mObserverBridge = new SelfContentObserver(this);
}
return mObserverBridge.getContentObserver();
}
@Override
public int getCount() {
return mCount;
}
@Override
public boolean onMove(int oldPosition, int newPosition) {
try {
// Make sure we have the proper window
if (mWindow != null) {
if (newPosition < mWindow.getStartPosition() ||
newPosition >= (mWindow.getStartPosition() + mWindow.getNumRows())) {
mWindow = mBulkCursor.getWindow(newPosition);
} else if (mWantsAllOnMoveCalls) {
mBulkCursor.onMove(newPosition);
}
} else {
mWindow = mBulkCursor.getWindow(newPosition);
}
} catch (RemoteException ex) {
// We tried to get a window and failed
Log.e(TAG, "Unable to get window because the remote process is dead");
return false;
}
// Couldn't obtain a window, something is wrong
if (mWindow == null) {
return false;
}
return true;
}
@Override
public void deactivate() {
// This will call onInvalidated(), so make sure to do it before calling release,
// which is what actually makes the data set invalid.
super.deactivate();
try {
mBulkCursor.deactivate();
} catch (RemoteException ex) {
Log.w(TAG, "Remote process exception when deactivating");
}
mWindow = null;
}
@Override
public void close() {
super.close();
try {
mBulkCursor.close();
} catch (RemoteException ex) {
Log.w(TAG, "Remote process exception when closing");
}
mWindow = null;
}
@Override
public boolean requery() {
try {
int oldCount = mCount;
//TODO get the window from a pool somewhere to avoid creating the memory dealer
mCount = mBulkCursor.requery(getObserver(), new CursorWindow(
false /* the window will be accessed across processes */));
if (mCount != -1) {
mPos = -1;
mWindow = null;
// super.requery() will call onChanged. Do it here instead of relying on the
// observer from the far side so that observers can see a correct value for mCount
// when responding to onChanged.
super.requery();
return true;
} else {
deactivate();
return false;
}
} catch (Exception ex) {
Log.e(TAG, "Unable to requery because the remote process exception " + ex.getMessage());
deactivate();
return false;
}
}
/**
* @hide
* @deprecated
*/
@Override
public boolean deleteRow() {
try {
boolean result = mBulkCursor.deleteRow(mPos);
if (result != false) {
// The window contains the old value, discard it
mWindow = null;
// Fix up the position
mCount = mBulkCursor.count();
if (mPos < mCount) {
int oldPos = mPos;
mPos = -1;
moveToPosition(oldPos);
} else {
mPos = mCount;
}
// Send the change notification
onChange(true);
}
return result;
} catch (RemoteException ex) {
Log.e(TAG, "Unable to delete row because the remote process is dead");
return false;
}
}
@Override
public String[] getColumnNames() {
if (mColumns == null) {
try {
mColumns = mBulkCursor.getColumnNames();
} catch (RemoteException ex) {
Log.e(TAG, "Unable to fetch column names because the remote process is dead");
return null;
}
}
return mColumns;
}
/**
* @hide
* @deprecated
*/
@Override
public boolean commitUpdates(Map<? extends Long,
? extends Map<String,Object>> additionalValues) {
if (!supportsUpdates()) {
Log.e(TAG, "commitUpdates not supported on this cursor, did you include the _id column?");
return false;
}
synchronized(mUpdatedRows) {
if (additionalValues != null) {
mUpdatedRows.putAll(additionalValues);
}
if (mUpdatedRows.size() <= 0) {
return false;
}
try {
boolean result = mBulkCursor.updateRows(mUpdatedRows);
if (result == true) {
mUpdatedRows.clear();
// Send the change notification
onChange(true);
}
return result;
} catch (RemoteException ex) {
Log.e(TAG, "Unable to commit updates because the remote process is dead");
return false;
}
}
}
@Override
public Bundle getExtras() {
try {
return mBulkCursor.getExtras();
} catch (RemoteException e) {
// This should never happen because the system kills processes that are using remote
// cursors when the provider process is killed.
throw new RuntimeException(e);
}
}
@Override
public Bundle respond(Bundle extras) {
try {
return mBulkCursor.respond(extras);
} catch (RemoteException e) {
// the system kills processes that are using remote cursors when the provider process
// is killed, but this can still happen if this is being called from the system process,
// so, better to log and return an empty bundle.
Log.w(TAG, "respond() threw RemoteException, returning an empty bundle.", e);
return Bundle.EMPTY;
}
}
}
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.database;
/**
* This is used for {@link Cursor#copyStringToBuffer}
*/
public final class CharArrayBuffer {
public CharArrayBuffer(int size) {
data = new char[size];
}
public CharArrayBuffer(char[] buf) {
data = buf;
}
public char[] data; // In and out parameter
public int sizeCopied; // Out parameter
}
@@ -0,0 +1,56 @@
/*
* 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 android.database;
/**
* A specialization of Observable for ContentObserver that provides methods for
* invoking the various callback methods of ContentObserver.
*/
public class ContentObservable extends Observable<ContentObserver> {
@Override
public void registerObserver(ContentObserver observer) {
super.registerObserver(observer);
}
/**
* invokes dispatchUpdate on each observer, unless the observer doesn't want
* self-notifications and the update is from a self-notification
* @param selfChange
*/
public void dispatchChange(boolean selfChange) {
synchronized(mObservers) {
for (ContentObserver observer : mObservers) {
if (!selfChange || observer.deliverSelfNotifications()) {
observer.dispatchChange(selfChange);
}
}
}
}
/**
* invokes onChange on each observer
* @param selfChange
*/
public void notifyChange(boolean selfChange) {
synchronized(mObservers) {
for (ContentObserver observer : mObservers) {
observer.onChange(selfChange);
}
}
}
}
@@ -0,0 +1,138 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.database;
import android.os.Handler;
/**
* Receives call backs for changes to content. Must be implemented by objects which are added
* to a {@link ContentObservable}.
*/
public abstract class ContentObserver {
private Transport mTransport;
// Protects mTransport
private Object lock = new Object();
/* package */ Handler mHandler;
private final class NotificationRunnable implements Runnable {
private boolean mSelf;
public NotificationRunnable(boolean self) {
mSelf = self;
}
public void run() {
ContentObserver.this.onChange(mSelf);
}
}
private static final class Transport extends IContentObserver.Stub {
ContentObserver mContentObserver;
public Transport(ContentObserver contentObserver) {
mContentObserver = contentObserver;
}
public boolean deliverSelfNotifications() {
ContentObserver contentObserver = mContentObserver;
if (contentObserver != null) {
return contentObserver.deliverSelfNotifications();
}
return false;
}
public void onChange(boolean selfChange) {
ContentObserver contentObserver = mContentObserver;
if (contentObserver != null) {
contentObserver.dispatchChange(selfChange);
}
}
public void releaseContentObserver() {
mContentObserver = null;
}
}
/**
* onChange() will happen on the provider Handler.
*
* @param handler The handler to run {@link #onChange} on.
*/
public ContentObserver(Handler handler) {
mHandler = handler;
}
/**
* Gets access to the binder transport object. Not for public consumption.
*
* {@hide}
*/
public IContentObserver getContentObserver() {
synchronized(lock) {
if (mTransport == null) {
mTransport = new Transport(this);
}
return mTransport;
}
}
/**
* Gets access to the binder transport object, and unlinks the transport object
* from the ContentObserver. Not for public consumption.
*
* {@hide}
*/
public IContentObserver releaseContentObserver() {
synchronized(lock) {
Transport oldTransport = mTransport;
if (oldTransport != null) {
oldTransport.releaseContentObserver();
mTransport = null;
}
return oldTransport;
}
}
/**
* Returns true if this observer is interested in notifications for changes
* made through the cursor the observer is registered with.
*/
public boolean deliverSelfNotifications() {
return false;
}
/**
* This method is called when a change occurs to the cursor that
* is being observed.
*
* @param selfChange true if the update was caused by a call to <code>commit</code> on the
* cursor that is being observed.
*/
public void onChange(boolean selfChange) {}
public final void dispatchChange(boolean selfChange) {
if (mHandler == null) {
onChange(selfChange);
} else {
mHandler.post(new NotificationRunnable(selfChange));
}
}
}
@@ -0,0 +1,42 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.database;
public interface CrossProcessCursor extends Cursor{
/**
* returns a pre-filled window, return NULL if no such window
*/
CursorWindow getWindow();
/**
* copies cursor data into the window start at pos
*/
void fillWindow(int pos, CursorWindow winow);
/**
* This function is called every time the cursor is successfully scrolled
* to a new position, giving the subclass a chance to update any state it
* may have. If it returns false the move function will also do so and the
* cursor will scroll to the beforeFirst position.
*
* @param oldPosition the position that we're moving from
* @param newPosition the position that we're moving to
* @return true if the move is successful, false otherwise
*/
boolean onMove(int oldPosition, int newPosition);
}
+590
View File
@@ -0,0 +1,590 @@
/*
* 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 android.database;
import android.content.ContentResolver;
import android.net.Uri;
import android.os.Bundle;
import java.util.Map;
/**
* This interface provides random read-write access to the result set returned
* by a database query.
*
* Cursor implementations are not required to be synchronized so code using a Cursor from multiple
* threads should perform its own synchronization when using the Cursor.
*/
public interface Cursor {
/**
* Returns the numbers of rows in the cursor.
*
* @return the number of rows in the cursor.
*/
int getCount();
/**
* Returns the current position of the cursor in the row set.
* The value is zero-based. When the row set is first returned the cursor
* will be at positon -1, which is before the first row. After the
* last row is returned another call to next() will leave the cursor past
* the last entry, at a position of count().
*
* @return the current cursor position.
*/
int getPosition();
/**
* Move the cursor by a relative amount, forward or backward, from the
* current position. Positive offsets move forwards, negative offsets move
* backwards. If the final position is outside of the bounds of the result
* set then the resultant position will be pinned to -1 or count() depending
* on whether the value is off the front or end of the set, respectively.
*
* <p>This method will return true if the requested destination was
* reachable, otherwise, it returns false. For example, if the cursor is at
* currently on the second entry in the result set and move(-5) is called,
* the position will be pinned at -1, and false will be returned.
*
* @param offset the offset to be applied from the current position.
* @return whether the requested move fully succeeded.
*/
boolean move(int offset);
/**
* Move the cursor to an absolute position. The valid
* range of values is -1 &lt;= position &lt;= count.
*
* <p>This method will return true if the request destination was reachable,
* otherwise, it returns false.
*
* @param position the zero-based position to move to.
* @return whether the requested move fully succeeded.
*/
boolean moveToPosition(int position);
/**
* Move the cursor to the first row.
*
* <p>This method will return false if the cursor is empty.
*
* @return whether the move succeeded.
*/
boolean moveToFirst();
/**
* Move the cursor to the last row.
*
* <p>This method will return false if the cursor is empty.
*
* @return whether the move succeeded.
*/
boolean moveToLast();
/**
* Move the cursor to the next row.
*
* <p>This method will return false if the cursor is already past the
* last entry in the result set.
*
* @return whether the move succeeded.
*/
boolean moveToNext();
/**
* Move the cursor to the previous row.
*
* <p>This method will return false if the cursor is already before the
* first entry in the result set.
*
* @return whether the move succeeded.
*/
boolean moveToPrevious();
/**
* Returns whether the cursor is pointing to the first row.
*
* @return whether the cursor is pointing at the first entry.
*/
boolean isFirst();
/**
* Returns whether the cursor is pointing to the last row.
*
* @return whether the cursor is pointing at the last entry.
*/
boolean isLast();
/**
* Returns whether the cursor is pointing to the position before the first
* row.
*
* @return whether the cursor is before the first result.
*/
boolean isBeforeFirst();
/**
* Returns whether the cursor is pointing to the position after the last
* row.
*
* @return whether the cursor is after the last result.
*/
boolean isAfterLast();
/**
* Removes the row at the current cursor position from the underlying data
* store. After this method returns the cursor will be pointing to the row
* after the row that is deleted. This has the side effect of decrementing
* the result of count() by one.
* <p>
* The query must have the row ID column in its selection, otherwise this
* call will fail.
*
* @hide
* @return whether the record was successfully deleted.
* @deprecated use {@link ContentResolver#delete(Uri, String, String[])}
*/
@Deprecated
boolean deleteRow();
/**
* Returns the zero-based index for the given column name, or -1 if the column doesn't exist.
* If you expect the column to exist use {@link #getColumnIndexOrThrow(String)} instead, which
* will make the error more clear.
*
* @param columnName the name of the target column.
* @return the zero-based column index for the given column name, or -1 if
* the column name does not exist.
* @see #getColumnIndexOrThrow(String)
*/
int getColumnIndex(String columnName);
/**
* Returns the zero-based index for the given column name, or throws
* {@link IllegalArgumentException} if the column doesn't exist. If you're not sure if
* a column will exist or not use {@link #getColumnIndex(String)} and check for -1, which
* is more efficient than catching the exceptions.
*
* @param columnName the name of the target column.
* @return the zero-based column index for the given column name
* @see #getColumnIndex(String)
* @throws IllegalArgumentException if the column does not exist
*/
int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException;
/**
* Returns the column name at the given zero-based column index.
*
* @param columnIndex the zero-based index of the target column.
* @return the column name for the given column index.
*/
String getColumnName(int columnIndex);
/**
* Returns a string array holding the names of all of the columns in the
* result set in the order in which they were listed in the result.
*
* @return the names of the columns returned in this query.
*/
String[] getColumnNames();
/**
* Return total number of columns
* @return number of columns
*/
int getColumnCount();
/**
* Returns the value of the requested column as a byte array.
*
* <p>If the native content of that column is not blob exception may throw
*
* @param columnIndex the zero-based index of the target column.
* @return the value of that column as a byte array.
*/
byte[] getBlob(int columnIndex);
/**
* Returns the value of the requested column as a String.
*
* <p>If the native content of that column is not text the result will be
* the result of passing the column value to String.valueOf(x).
*
* @param columnIndex the zero-based index of the target column.
* @return the value of that column as a String.
*/
String getString(int columnIndex);
/**
* Retrieves the requested column text and stores it in the buffer provided.
* If the buffer size is not sufficient, a new char buffer will be allocated
* and assigned to CharArrayBuffer.data
* @param columnIndex the zero-based index of the target column.
* if the target column is null, return buffer
* @param buffer the buffer to copy the text into.
*/
void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer);
/**
* Returns the value of the requested column as a short.
*
* <p>If the native content of that column is not numeric the result will be
* the result of passing the column value to Short.valueOf(x).
*
* @param columnIndex the zero-based index of the target column.
* @return the value of that column as a short.
*/
short getShort(int columnIndex);
/**
* Returns the value of the requested column as an int.
*
* <p>If the native content of that column is not numeric the result will be
* the result of passing the column value to Integer.valueOf(x).
*
* @param columnIndex the zero-based index of the target column.
* @return the value of that column as an int.
*/
int getInt(int columnIndex);
/**
* Returns the value of the requested column as a long.
*
* <p>If the native content of that column is not numeric the result will be
* the result of passing the column value to Long.valueOf(x).
*
* @param columnIndex the zero-based index of the target column.
* @return the value of that column as a long.
*/
long getLong(int columnIndex);
/**
* Returns the value of the requested column as a float.
*
* <p>If the native content of that column is not numeric the result will be
* the result of passing the column value to Float.valueOf(x).
*
* @param columnIndex the zero-based index of the target column.
* @return the value of that column as a float.
*/
float getFloat(int columnIndex);
/**
* Returns the value of the requested column as a double.
*
* <p>If the native content of that column is not numeric the result will be
* the result of passing the column value to Double.valueOf(x).
*
* @param columnIndex the zero-based index of the target column.
* @return the value of that column as a double.
*/
double getDouble(int columnIndex);
/**
* Returns <code>true</code> if the value in the indicated column is null.
*
* @param columnIndex the zero-based index of the target column.
* @return whether the column value is null.
*/
boolean isNull(int columnIndex);
/**
* Returns <code>true</code> if the cursor supports updates.
*
* @return whether the cursor supports updates.
* @hide
* @deprecated use the {@link ContentResolver} update methods instead of the Cursor
* update methods
*/
@Deprecated
boolean supportsUpdates();
/**
* Returns <code>true</code> if there are pending updates that have not yet been committed.
*
* @return <code>true</code> if there are pending updates that have not yet been committed.
* @hide
* @deprecated use the {@link ContentResolver} update methods instead of the Cursor
* update methods
*/
@Deprecated
boolean hasUpdates();
/**
* Updates the value for the given column in the row the cursor is
* currently pointing at. Updates are not committed to the backing store
* until {@link #commitUpdates()} is called.
*
* @param columnIndex the zero-based index of the target column.
* @param value the new value.
* @return whether the operation succeeded.
* @hide
* @deprecated use the {@link ContentResolver} update methods instead of the Cursor
* update methods
*/
@Deprecated
boolean updateBlob(int columnIndex, byte[] value);
/**
* Updates the value for the given column in the row the cursor is
* currently pointing at. Updates are not committed to the backing store
* until {@link #commitUpdates()} is called.
*
* @param columnIndex the zero-based index of the target column.
* @param value the new value.
* @return whether the operation succeeded.
* @hide
* @deprecated use the {@link ContentResolver} update methods instead of the Cursor
* update methods
*/
@Deprecated
boolean updateString(int columnIndex, String value);
/**
* Updates the value for the given column in the row the cursor is
* currently pointing at. Updates are not committed to the backing store
* until {@link #commitUpdates()} is called.
*
* @param columnIndex the zero-based index of the target column.
* @param value the new value.
* @return whether the operation succeeded.
* @hide
* @deprecated use the {@link ContentResolver} update methods instead of the Cursor
* update methods
*/
@Deprecated
boolean updateShort(int columnIndex, short value);
/**
* Updates the value for the given column in the row the cursor is
* currently pointing at. Updates are not committed to the backing store
* until {@link #commitUpdates()} is called.
*
* @param columnIndex the zero-based index of the target column.
* @param value the new value.
* @return whether the operation succeeded.
* @hide
* @deprecated use the {@link ContentResolver} update methods instead of the Cursor
* update methods
*/
@Deprecated
boolean updateInt(int columnIndex, int value);
/**
* Updates the value for the given column in the row the cursor is
* currently pointing at. Updates are not committed to the backing store
* until {@link #commitUpdates()} is called.
*
* @param columnIndex the zero-based index of the target column.
* @param value the new value.
* @return whether the operation succeeded.
* @hide
* @deprecated use the {@link ContentResolver} update methods instead of the Cursor
* update methods
*/
@Deprecated
boolean updateLong(int columnIndex, long value);
/**
* Updates the value for the given column in the row the cursor is
* currently pointing at. Updates are not committed to the backing store
* until {@link #commitUpdates()} is called.
*
* @param columnIndex the zero-based index of the target column.
* @param value the new value.
* @return whether the operation succeeded.
* @hide
* @deprecated use the {@link ContentResolver} update methods instead of the Cursor
* update methods
*/
@Deprecated
boolean updateFloat(int columnIndex, float value);
/**
* Updates the value for the given column in the row the cursor is
* currently pointing at. Updates are not committed to the backing store
* until {@link #commitUpdates()} is called.
*
* @param columnIndex the zero-based index of the target column.
* @param value the new value.
* @return whether the operation succeeded.
* @hide
* @deprecated use the {@link ContentResolver} update methods instead of the Cursor
* update methods
*/
@Deprecated
boolean updateDouble(int columnIndex, double value);
/**
* Removes the value for the given column in the row the cursor is
* currently pointing at. Updates are not committed to the backing store
* until {@link #commitUpdates()} is called.
*
* @param columnIndex the zero-based index of the target column.
* @return whether the operation succeeded.
* @hide
* @deprecated use the {@link ContentResolver} update methods instead of the Cursor
* update methods
*/
@Deprecated
boolean updateToNull(int columnIndex);
/**
* Atomically commits all updates to the backing store. After completion,
* this method leaves the data in an inconsistent state and you should call
* {@link #requery} before reading data from the cursor again.
*
* @return whether the operation succeeded.
* @hide
* @deprecated use the {@link ContentResolver} update methods instead of the Cursor
* update methods
*/
@Deprecated
boolean commitUpdates();
/**
* Atomically commits all updates to the backing store, as well as the
* updates included in values. After completion,
* this method leaves the data in an inconsistent state and you should call
* {@link #requery} before reading data from the cursor again.
*
* @param values A map from row IDs to Maps associating column names with
* updated values. A null value indicates the field should be
removed.
* @return whether the operation succeeded.
* @hide
* @deprecated use the {@link ContentResolver} update methods instead of the Cursor
* update methods
*/
@Deprecated
boolean commitUpdates(Map<? extends Long,
? extends Map<String,Object>> values);
/**
* Reverts all updates made to the cursor since the last call to
* commitUpdates.
* @hide
* @deprecated use the {@link ContentResolver} update methods instead of the Cursor
* update methods
*/
@Deprecated
void abortUpdates();
/**
* Deactivates the Cursor, making all calls on it fail until {@link #requery} is called.
* Inactive Cursors use fewer resources than active Cursors.
* Calling {@link #requery} will make the cursor active again.
*/
void deactivate();
/**
* Performs the query that created the cursor again, refreshing its
* contents. This may be done at any time, including after a call to {@link
* #deactivate}.
*
* @return true if the requery succeeded, false if not, in which case the
* cursor becomes invalid.
*/
boolean requery();
/**
* Closes the Cursor, releasing all of its resources and making it completely invalid.
* Unlike {@link #deactivate()} a call to {@link #requery()} will not make the Cursor valid
* again.
*/
void close();
/**
* return true if the cursor is closed
* @return true if the cursor is closed.
*/
boolean isClosed();
/**
* Register an observer that is called when changes happen to the content backing this cursor.
* Typically the data set won't change until {@link #requery()} is called.
*
* @param observer the object that gets notified when the content backing the cursor changes.
* @see #unregisterContentObserver(ContentObserver)
*/
void registerContentObserver(ContentObserver observer);
/**
* Unregister an observer that has previously been registered with this
* cursor via {@link #registerContentObserver}.
*
* @param observer the object to unregister.
* @see #registerContentObserver(ContentObserver)
*/
void unregisterContentObserver(ContentObserver observer);
/**
* Register an observer that is called when changes happen to the contents
* of the this cursors data set, for example, when the data set is changed via
* {@link #requery()}, {@link #deactivate()}, or {@link #close()}.
*
* @param observer the object that gets notified when the cursors data set changes.
* @see #unregisterDataSetObserver(DataSetObserver)
*/
void registerDataSetObserver(DataSetObserver observer);
/**
* Unregister an observer that has previously been registered with this
* cursor via {@link #registerContentObserver}.
*
* @param observer the object to unregister.
* @see #registerDataSetObserver(DataSetObserver)
*/
void unregisterDataSetObserver(DataSetObserver observer);
/**
* Register to watch a content URI for changes. This can be the URI of a specific data row (for
* example, "content://my_provider_type/23"), or a a generic URI for a content type.
*
* @param cr The content resolver from the caller's context. The listener attached to
* this resolver will be notified.
* @param uri The content URI to watch.
*/
void setNotificationUri(ContentResolver cr, Uri uri);
/**
* onMove() will only be called across processes if this method returns true.
* @return whether all cursor movement should result in a call to onMove().
*/
boolean getWantsAllOnMoveCalls();
/**
* Returns a bundle of extra values. This is an optional way for cursors to provide out-of-band
* metadata to their users. One use of this is for reporting on the progress of network requests
* that are required to fetch data for the cursor.
*
* <p>These values may only change when requery is called.
* @return cursor-defined values, or Bundle.EMTPY if there are no values. Never null.
*/
Bundle getExtras();
/**
* This is an out-of-band way for the the user of a cursor to communicate with the cursor. The
* structure of each bundle is entirely defined by the cursor.
*
* <p>One use of this is to tell a cursor that it should retry its network request after it
* reported an error.
* @param extras extra values, or Bundle.EMTPY. Never null.
* @return extra values, or Bundle.EMTPY. Never null.
*/
Bundle respond(Bundle extras);
}
@@ -0,0 +1,31 @@
/*
* 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 android.database;
/**
* An exception indicating that a cursor is out of bounds.
*/
public class CursorIndexOutOfBoundsException extends IndexOutOfBoundsException {
public CursorIndexOutOfBoundsException(int index, int size) {
super("Index " + index + " requested, with a size of " + size);
}
public CursorIndexOutOfBoundsException(String message) {
super(message);
}
}
@@ -0,0 +1,265 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.database;
import java.util.Iterator;
/**
* Does a join on two cursors using the specified columns. The cursors must already
* be sorted on each of the specified columns in ascending order. This joiner only
* supports the case where the tuple of key column values is unique.
* <p>
* Typical usage:
*
* <pre>
* CursorJoiner joiner = new CursorJoiner(cursorA, keyColumnsofA, cursorB, keyColumnsofB);
* for (CursorJointer.Result joinerResult : joiner) {
* switch (joinerResult) {
* case LEFT:
* // handle case where a row in cursorA is unique
* break;
* case RIGHT:
* // handle case where a row in cursorB is unique
* break;
* case BOTH:
* // handle case where a row with the same key is in both cursors
* break;
* }
* }
* </pre>
*/
public final class CursorJoiner
implements Iterator<CursorJoiner.Result>, Iterable<CursorJoiner.Result> {
private Cursor mCursorLeft;
private Cursor mCursorRight;
private boolean mCompareResultIsValid;
private Result mCompareResult;
private int[] mColumnsLeft;
private int[] mColumnsRight;
private String[] mValues;
/**
* The result of a call to next().
*/
public enum Result {
/** The row currently pointed to by the left cursor is unique */
RIGHT,
/** The row currently pointed to by the right cursor is unique */
LEFT,
/** The rows pointed to by both cursors are the same */
BOTH
}
/**
* Initializes the CursorJoiner and resets the cursors to the first row. The left and right
* column name arrays must have the same number of columns.
* @param cursorLeft The left cursor to compare
* @param columnNamesLeft The column names to compare from the left cursor
* @param cursorRight The right cursor to compare
* @param columnNamesRight The column names to compare from the right cursor
*/
public CursorJoiner(
Cursor cursorLeft, String[] columnNamesLeft,
Cursor cursorRight, String[] columnNamesRight) {
if (columnNamesLeft.length != columnNamesRight.length) {
throw new IllegalArgumentException(
"you must have the same number of columns on the left and right, "
+ columnNamesLeft.length + " != " + columnNamesRight.length);
}
mCursorLeft = cursorLeft;
mCursorRight = cursorRight;
mCursorLeft.moveToFirst();
mCursorRight.moveToFirst();
mCompareResultIsValid = false;
mColumnsLeft = buildColumnIndiciesArray(cursorLeft, columnNamesLeft);
mColumnsRight = buildColumnIndiciesArray(cursorRight, columnNamesRight);
mValues = new String[mColumnsLeft.length * 2];
}
public Iterator<Result> iterator() {
return this;
}
/**
* Lookup the indicies of the each column name and return them in an array.
* @param cursor the cursor that contains the columns
* @param columnNames the array of names to lookup
* @return an array of column indices
*/
private int[] buildColumnIndiciesArray(Cursor cursor, String[] columnNames) {
int[] columns = new int[columnNames.length];
for (int i = 0; i < columnNames.length; i++) {
columns[i] = cursor.getColumnIndexOrThrow(columnNames[i]);
}
return columns;
}
/**
* Returns whether or not there are more rows to compare using next().
* @return true if there are more rows to compare
*/
public boolean hasNext() {
if (mCompareResultIsValid) {
switch (mCompareResult) {
case BOTH:
return !mCursorLeft.isLast() || !mCursorRight.isLast();
case LEFT:
return !mCursorLeft.isLast() || !mCursorRight.isAfterLast();
case RIGHT:
return !mCursorLeft.isAfterLast() || !mCursorRight.isLast();
default:
throw new IllegalStateException("bad value for mCompareResult, "
+ mCompareResult);
}
} else {
return !mCursorLeft.isAfterLast() || !mCursorRight.isAfterLast();
}
}
/**
* Returns the comparison result of the next row from each cursor. If one cursor
* has no more rows but the other does then subsequent calls to this will indicate that
* the remaining rows are unique.
* <p>
* The caller must check that hasNext() returns true before calling this.
* <p>
* Once next() has been called the cursors specified in the result of the call to
* next() are guaranteed to point to the row that was indicated. Reading values
* from the cursor that was not indicated in the call to next() will result in
* undefined behavior.
* @return LEFT, if the row pointed to by the left cursor is unique, RIGHT
* if the row pointed to by the right cursor is unique, BOTH if the rows in both
* cursors are the same.
*/
public Result next() {
if (!hasNext()) {
throw new IllegalStateException("you must only call next() when hasNext() is true");
}
incrementCursors();
assert hasNext();
boolean hasLeft = !mCursorLeft.isAfterLast();
boolean hasRight = !mCursorRight.isAfterLast();
if (hasLeft && hasRight) {
populateValues(mValues, mCursorLeft, mColumnsLeft, 0 /* start filling at index 0 */);
populateValues(mValues, mCursorRight, mColumnsRight, 1 /* start filling at index 1 */);
switch (compareStrings(mValues)) {
case -1:
mCompareResult = Result.LEFT;
break;
case 0:
mCompareResult = Result.BOTH;
break;
case 1:
mCompareResult = Result.RIGHT;
break;
}
} else if (hasLeft) {
mCompareResult = Result.LEFT;
} else {
assert hasRight;
mCompareResult = Result.RIGHT;
}
mCompareResultIsValid = true;
return mCompareResult;
}
public void remove() {
throw new UnsupportedOperationException("not implemented");
}
/**
* Reads the strings from the cursor that are specifed in the columnIndicies
* array and saves them in values beginning at startingIndex, skipping a slot
* for each value. If columnIndicies has length 3 and startingIndex is 1, the
* values will be stored in slots 1, 3, and 5.
* @param values the String[] to populate
* @param cursor the cursor from which to read
* @param columnIndicies the indicies of the values to read from the cursor
* @param startingIndex the slot in which to start storing values, and must be either 0 or 1.
*/
private static void populateValues(String[] values, Cursor cursor, int[] columnIndicies,
int startingIndex) {
assert startingIndex == 0 || startingIndex == 1;
for (int i = 0; i < columnIndicies.length; i++) {
values[startingIndex + i*2] = cursor.getString(columnIndicies[i]);
}
}
/**
* Increment the cursors past the rows indicated in the most recent call to next().
* This will only have an affect once per call to next().
*/
private void incrementCursors() {
if (mCompareResultIsValid) {
switch (mCompareResult) {
case LEFT:
mCursorLeft.moveToNext();
break;
case RIGHT:
mCursorRight.moveToNext();
break;
case BOTH:
mCursorLeft.moveToNext();
mCursorRight.moveToNext();
break;
}
mCompareResultIsValid = false;
}
}
/**
* Compare the values. Values contains n pairs of strings. If all the pairs of strings match
* then returns 0. Otherwise returns the comparison result of the first non-matching pair
* of values, -1 if the first of the pair is less than the second of the pair or 1 if it
* is greater.
* @param values the n pairs of values to compare
* @return -1, 0, or 1 as described above.
*/
private static int compareStrings(String... values) {
if ((values.length % 2) != 0) {
throw new IllegalArgumentException("you must specify an even number of values");
}
for (int index = 0; index < values.length; index+=2) {
if (values[index] == null) {
if (values[index+1] == null) continue;
return -1;
}
if (values[index+1] == null) {
return 1;
}
int comp = values[index].compareTo(values[index+1]);
if (comp != 0) {
return comp < 0 ? -1 : 1;
}
}
return 0;
}
}
@@ -0,0 +1,232 @@
/*
* 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 android.database;
import android.database.sqlite.SQLiteMisuseException;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Config;
import android.util.Log;
import java.util.Map;
/**
* Wraps a BulkCursor around an existing Cursor making it remotable.
*
* {@hide}
*/
public final class CursorToBulkCursorAdaptor extends BulkCursorNative
implements IBinder.DeathRecipient {
private static final String TAG = "Cursor";
private final CrossProcessCursor mCursor;
private CursorWindow mWindow;
private final String mProviderName;
private final boolean mReadOnly;
private ContentObserverProxy mObserver;
private static final class ContentObserverProxy extends ContentObserver
{
protected IContentObserver mRemote;
public ContentObserverProxy(IContentObserver remoteObserver, DeathRecipient recipient) {
super(null);
mRemote = remoteObserver;
try {
remoteObserver.asBinder().linkToDeath(recipient, 0);
} catch (RemoteException e) {
// Do nothing, the far side is dead
}
}
public boolean unlinkToDeath(DeathRecipient recipient) {
return mRemote.asBinder().unlinkToDeath(recipient, 0);
}
@Override
public boolean deliverSelfNotifications() {
// The far side handles the self notifications.
return false;
}
@Override
public void onChange(boolean selfChange) {
try {
mRemote.onChange(selfChange);
} catch (RemoteException ex) {
// Do nothing, the far side is dead
}
}
}
public CursorToBulkCursorAdaptor(Cursor cursor, IContentObserver observer, String providerName,
boolean allowWrite, CursorWindow window) {
try {
mCursor = (CrossProcessCursor) cursor;
if (mCursor instanceof AbstractWindowedCursor) {
AbstractWindowedCursor windowedCursor = (AbstractWindowedCursor) cursor;
if (windowedCursor.hasWindow()) {
if (Log.isLoggable(TAG, Log.VERBOSE) || Config.LOGV) {
Log.v(TAG, "Cross process cursor has a local window before setWindow in "
+ providerName, new RuntimeException());
}
}
windowedCursor.setWindow(window);
} else {
mWindow = window;
mCursor.fillWindow(0, window);
}
} catch (ClassCastException e) {
// TODO Implement this case.
throw new UnsupportedOperationException(
"Only CrossProcessCursor cursors are supported across process for now", e);
}
mProviderName = providerName;
mReadOnly = !allowWrite;
createAndRegisterObserverProxy(observer);
}
public void binderDied() {
mCursor.close();
if (mWindow != null) {
mWindow.close();
}
}
public CursorWindow getWindow(int startPos) {
mCursor.moveToPosition(startPos);
if (mWindow != null) {
if (startPos < mWindow.getStartPosition() ||
startPos >= (mWindow.getStartPosition() + mWindow.getNumRows())) {
mCursor.fillWindow(startPos, mWindow);
}
return mWindow;
} else {
return ((AbstractWindowedCursor)mCursor).getWindow();
}
}
public void onMove(int position) {
mCursor.onMove(mCursor.getPosition(), position);
}
public int count() {
return mCursor.getCount();
}
public String[] getColumnNames() {
return mCursor.getColumnNames();
}
public void deactivate() {
maybeUnregisterObserverProxy();
mCursor.deactivate();
}
public void close() {
maybeUnregisterObserverProxy();
mCursor.close();
}
public int requery(IContentObserver observer, CursorWindow window) {
if (mWindow == null) {
((AbstractWindowedCursor)mCursor).setWindow(window);
}
try {
if (!mCursor.requery()) {
return -1;
}
} catch (IllegalStateException e) {
IllegalStateException leakProgram = new IllegalStateException(
mProviderName + " Requery misuse db, mCursor isClosed:" +
mCursor.isClosed(), e);
throw leakProgram;
}
if (mWindow != null) {
mCursor.fillWindow(0, window);
mWindow = window;
}
maybeUnregisterObserverProxy();
createAndRegisterObserverProxy(observer);
return mCursor.getCount();
}
public boolean getWantsAllOnMoveCalls() {
return mCursor.getWantsAllOnMoveCalls();
}
/**
* Create a ContentObserver from the observer and register it as an observer on the
* underlying cursor.
* @param observer the IContentObserver that wants to monitor the cursor
* @throws IllegalStateException if an observer is already registered
*/
private void createAndRegisterObserverProxy(IContentObserver observer) {
if (mObserver != null) {
throw new IllegalStateException("an observer is already registered");
}
mObserver = new ContentObserverProxy(observer, this);
mCursor.registerContentObserver(mObserver);
}
/** Unregister the observer if it is already registered. */
private void maybeUnregisterObserverProxy() {
if (mObserver != null) {
mCursor.unregisterContentObserver(mObserver);
mObserver.unlinkToDeath(this);
mObserver = null;
}
}
public boolean updateRows(Map<? extends Long, ? extends Map<String, Object>> values) {
if (mReadOnly) {
Log.w("ContentProvider", "Permission Denial: modifying "
+ mProviderName
+ " from pid=" + Binder.getCallingPid()
+ ", uid=" + Binder.getCallingUid());
return false;
}
return mCursor.commitUpdates(values);
}
public boolean deleteRow(int position) {
if (mReadOnly) {
Log.w("ContentProvider", "Permission Denial: modifying "
+ mProviderName
+ " from pid=" + Binder.getCallingPid()
+ ", uid=" + Binder.getCallingUid());
return false;
}
if (mCursor.moveToPosition(position) == false) {
return false;
}
return mCursor.deleteRow();
}
public Bundle getExtras() {
return mCursor.getExtras();
}
public Bundle respond(Bundle extras) {
return mCursor.respond(extras);
}
}
@@ -0,0 +1,534 @@
/*
* 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 android.database;
import android.database.sqlite.SQLiteClosable;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
/**
* A buffer containing multiple cursor rows.
*/
public class CursorWindow extends SQLiteClosable implements Parcelable {
/** The pointer to the native window class */
@SuppressWarnings("unused")
private int nWindow;
private int mStartPos;
/**
* Creates a new empty window.
*
* @param localWindow true if this window will be used in this process only
*/
public CursorWindow(boolean localWindow) {
mStartPos = 0;
native_init(localWindow);
}
/**
* Returns the starting position of this window within the entire
* Cursor's result set.
*
* @return the starting position of this window within the entire
* Cursor's result set.
*/
public int getStartPosition() {
return mStartPos;
}
/**
* Set the start position of cursor window
* @param pos
*/
public void setStartPosition(int pos) {
mStartPos = pos;
}
/**
* Returns the number of rows in this window.
*
* @return the number of rows in this window.
*/
public int getNumRows() {
acquireReference();
try {
return getNumRows_native();
} finally {
releaseReference();
}
}
private native int getNumRows_native();
/**
* Set number of Columns
* @param columnNum
* @return true if success
*/
public boolean setNumColumns(int columnNum) {
acquireReference();
try {
return setNumColumns_native(columnNum);
} finally {
releaseReference();
}
}
private native boolean setNumColumns_native(int columnNum);
/**
* Allocate a row in cursor window
* @return false if cursor window is out of memory
*/
public boolean allocRow(){
acquireReference();
try {
return allocRow_native();
} finally {
releaseReference();
}
}
private native boolean allocRow_native();
/**
* Free the last row
*/
public void freeLastRow(){
acquireReference();
try {
freeLastRow_native();
} finally {
releaseReference();
}
}
private native void freeLastRow_native();
/**
* copy byte array to cursor window
* @param value
* @param row
* @param col
* @return false if fail to copy
*/
public boolean putBlob(byte[] value, int row, int col) {
acquireReference();
try {
return putBlob_native(value, row - mStartPos, col);
} finally {
releaseReference();
}
}
private native boolean putBlob_native(byte[] value, int row, int col);
/**
* Copy String to cursor window
* @param value
* @param row
* @param col
* @return false if fail to copy
*/
public boolean putString(String value, int row, int col) {
acquireReference();
try {
return putString_native(value, row - mStartPos, col);
} finally {
releaseReference();
}
}
private native boolean putString_native(String value, int row, int col);
/**
* Copy integer to cursor window
* @param value
* @param row
* @param col
* @return false if fail to copy
*/
public boolean putLong(long value, int row, int col) {
acquireReference();
try {
return putLong_native(value, row - mStartPos, col);
} finally {
releaseReference();
}
}
private native boolean putLong_native(long value, int row, int col);
/**
* Copy double to cursor window
* @param value
* @param row
* @param col
* @return false if fail to copy
*/
public boolean putDouble(double value, int row, int col) {
acquireReference();
try {
return putDouble_native(value, row - mStartPos, col);
} finally {
releaseReference();
}
}
private native boolean putDouble_native(double value, int row, int col);
/**
* Set the [row, col] value to NULL
* @param row
* @param col
* @return false if fail to copy
*/
public boolean putNull(int row, int col) {
acquireReference();
try {
return putNull_native(row - mStartPos, col);
} finally {
releaseReference();
}
}
private native boolean putNull_native(int row, int col);
/**
* Returns {@code true} if given field is {@code NULL}.
*
* @param row the row to read from, row - getStartPosition() being the actual row in the window
* @param col the column to read from
* @return {@code true} if given field is {@code NULL}
*/
public boolean isNull(int row, int col) {
acquireReference();
try {
return isNull_native(row - mStartPos, col);
} finally {
releaseReference();
}
}
private native boolean isNull_native(int row, int col);
/**
* Returns a byte array for the given field.
*
* @param row the row to read from, row - getStartPosition() being the actual row in the window
* @param col the column to read from
* @return a String value for the given field
*/
public byte[] getBlob(int row, int col) {
acquireReference();
try {
return getBlob_native(row - mStartPos, col);
} finally {
releaseReference();
}
}
private native byte[] getBlob_native(int row, int col);
/**
* Checks if a field contains either a blob or is null.
*
* @param row the row to read from, row - getStartPosition() being the actual row in the window
* @param col the column to read from
* @return {@code true} if given field is {@code NULL} or a blob
*/
public boolean isBlob(int row, int col) {
acquireReference();
try {
return isBlob_native(row - mStartPos, col);
} finally {
releaseReference();
}
}
/**
* Checks if a field contains a long
*
* @param row the row to read from, row - getStartPosition() being the actual row in the window
* @param col the column to read from
* @return {@code true} if given field is a long
*/
public boolean isLong(int row, int col) {
acquireReference();
try {
return isInteger_native(row - mStartPos, col);
} finally {
releaseReference();
}
}
/**
* Checks if a field contains a float.
*
* @param row the row to read from, row - getStartPosition() being the actual row in the window
* @param col the column to read from
* @return {@code true} if given field is a float
*/
public boolean isFloat(int row, int col) {
acquireReference();
try {
return isFloat_native(row - mStartPos, col);
} finally {
releaseReference();
}
}
/**
* Checks if a field contains either a String or is null.
*
* @param row the row to read from, row - getStartPosition() being the actual row in the window
* @param col the column to read from
* @return {@code true} if given field is {@code NULL} or a String
*/
public boolean isString(int row, int col) {
acquireReference();
try {
return isString_native(row - mStartPos, col);
} finally {
releaseReference();
}
}
private native boolean isBlob_native(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);
/**
* Returns a String for the given field.
*
* @param row the row to read from, row - getStartPosition() being the actual row in the window
* @param col the column to read from
* @return a String value for the given field
*/
public String getString(int row, int col) {
acquireReference();
try {
return getString_native(row - mStartPos, col);
} finally {
releaseReference();
}
}
private native String getString_native(int row, int col);
/**
* copy the text for the given field in the provided char array.
*
* @param row the row to read from, row - getStartPosition() being the actual row in the window
* @param col the column to read from
* @param buffer the CharArrayBuffer to copy the text into,
* If the requested string is larger than the buffer
* a new char buffer will be created to hold the string. and assigne to
* CharArrayBuffer.data
*/
public void copyStringToBuffer(int row, int col, CharArrayBuffer buffer) {
if (buffer == null) {
throw new IllegalArgumentException("CharArrayBuffer should not be null");
}
if (buffer.data == null) {
buffer.data = new char[64];
}
acquireReference();
try {
char[] newbuf = copyStringToBuffer_native(
row - mStartPos, col, buffer.data.length, buffer);
if (newbuf != null) {
buffer.data = newbuf;
}
} finally {
releaseReference();
}
}
private native char[] copyStringToBuffer_native(
int row, int col, int bufferSize, CharArrayBuffer buffer);
/**
* Returns a long for the given field.
* row is 0 based
*
* @param row the row to read from, row - getStartPosition() being the actual row in the window
* @param col the column to read from
* @return a long value for the given field
*/
public long getLong(int row, int col) {
acquireReference();
try {
return getLong_native(row - mStartPos, col);
} finally {
releaseReference();
}
}
private native long getLong_native(int row, int col);
/**
* Returns a double for the given field.
* row is 0 based
*
* @param row the row to read from, row - getStartPosition() being the actual row in the window
* @param col the column to read from
* @return a double value for the given field
*/
public double getDouble(int row, int col) {
acquireReference();
try {
return getDouble_native(row - mStartPos, col);
} finally {
releaseReference();
}
}
private native double getDouble_native(int row, int col);
/**
* Returns a short for the given field.
* row is 0 based
*
* @param row the row to read from, row - getStartPosition() being the actual row in the window
* @param col the column to read from
* @return a short value for the given field
*/
public short getShort(int row, int col) {
acquireReference();
try {
return (short) getLong_native(row - mStartPos, col);
} finally {
releaseReference();
}
}
/**
* Returns an int for the given field.
*
* @param row the row to read from, row - getStartPosition() being the actual row in the window
* @param col the column to read from
* @return an int value for the given field
*/
public int getInt(int row, int col) {
acquireReference();
try {
return (int) getLong_native(row - mStartPos, col);
} finally {
releaseReference();
}
}
/**
* Returns a float for the given field.
* row is 0 based
*
* @param row the row to read from, row - getStartPosition() being the actual row in the window
* @param col the column to read from
* @return a float value for the given field
*/
public float getFloat(int row, int col) {
acquireReference();
try {
return (float) getDouble_native(row - mStartPos, col);
} finally {
releaseReference();
}
}
/**
* Clears out the existing contents of the window, making it safe to reuse
* for new data. Note that the number of columns in the window may NOT
* change across a call to clear().
*/
public void clear() {
acquireReference();
try {
mStartPos = 0;
native_clear();
} finally {
releaseReference();
}
}
/** Clears out the native side of things */
private native void native_clear();
/**
* Cleans up the native resources associated with the window.
*/
public void close() {
releaseReference();
}
private native void close_native();
@Override
protected void finalize() {
// Just in case someone forgot to call close...
close_native();
}
public static final Parcelable.Creator<CursorWindow> CREATOR
= new Parcelable.Creator<CursorWindow>() {
public CursorWindow createFromParcel(Parcel source) {
return new CursorWindow(source);
}
public CursorWindow[] newArray(int size) {
return new CursorWindow[size];
}
};
public static CursorWindow newFromParcel(Parcel p) {
return CREATOR.createFromParcel(p);
}
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel dest, int flags) {
dest.writeStrongBinder(native_getBinder());
dest.writeInt(mStartPos);
}
private CursorWindow(Parcel source) {
IBinder nativeBinder = source.readStrongBinder();
mStartPos = source.readInt();
native_init(nativeBinder);
}
/** Get the binder for the native side of the window */
private native IBinder native_getBinder();
/** Does the native side initialization for an empty window */
private native void native_init(boolean localOnly);
/** Does the native side initialization with an existing binder from another process */
private native void native_init(IBinder nativeBinder);
@Override
protected void onAllReferencesReleased() {
close_native();
}
}
@@ -0,0 +1,305 @@
/*
* 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 android.database;
import android.content.ContentResolver;
import android.database.CharArrayBuffer;
import android.net.Uri;
import android.os.Bundle;
import java.util.Map;
/**
* Wrapper class for Cursor that delegates all calls to the actual cursor object
*/
public class CursorWrapper implements Cursor {
public CursorWrapper(Cursor cursor) {
mCursor = cursor;
}
/**
* @hide
* @deprecated
*/
public void abortUpdates() {
mCursor.abortUpdates();
}
public void close() {
mCursor.close();
}
public boolean isClosed() {
return mCursor.isClosed();
}
/**
* @hide
* @deprecated
*/
public boolean commitUpdates() {
return mCursor.commitUpdates();
}
/**
* @hide
* @deprecated
*/
public boolean commitUpdates(
Map<? extends Long, ? extends Map<String, Object>> values) {
return mCursor.commitUpdates(values);
}
public int getCount() {
return mCursor.getCount();
}
public void deactivate() {
mCursor.deactivate();
}
/**
* @hide
* @deprecated
*/
public boolean deleteRow() {
return mCursor.deleteRow();
}
public boolean moveToFirst() {
return mCursor.moveToFirst();
}
public int getColumnCount() {
return mCursor.getColumnCount();
}
public int getColumnIndex(String columnName) {
return mCursor.getColumnIndex(columnName);
}
public int getColumnIndexOrThrow(String columnName)
throws IllegalArgumentException {
return mCursor.getColumnIndexOrThrow(columnName);
}
public String getColumnName(int columnIndex) {
return mCursor.getColumnName(columnIndex);
}
public String[] getColumnNames() {
return mCursor.getColumnNames();
}
public double getDouble(int columnIndex) {
return mCursor.getDouble(columnIndex);
}
public Bundle getExtras() {
return mCursor.getExtras();
}
public float getFloat(int columnIndex) {
return mCursor.getFloat(columnIndex);
}
public int getInt(int columnIndex) {
return mCursor.getInt(columnIndex);
}
public long getLong(int columnIndex) {
return mCursor.getLong(columnIndex);
}
public short getShort(int columnIndex) {
return mCursor.getShort(columnIndex);
}
public String getString(int columnIndex) {
return mCursor.getString(columnIndex);
}
public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
mCursor.copyStringToBuffer(columnIndex, buffer);
}
public byte[] getBlob(int columnIndex) {
return mCursor.getBlob(columnIndex);
}
public boolean getWantsAllOnMoveCalls() {
return mCursor.getWantsAllOnMoveCalls();
}
/**
* @hide
* @deprecated
*/
public boolean hasUpdates() {
return mCursor.hasUpdates();
}
public boolean isAfterLast() {
return mCursor.isAfterLast();
}
public boolean isBeforeFirst() {
return mCursor.isBeforeFirst();
}
public boolean isFirst() {
return mCursor.isFirst();
}
public boolean isLast() {
return mCursor.isLast();
}
public boolean isNull(int columnIndex) {
return mCursor.isNull(columnIndex);
}
public boolean moveToLast() {
return mCursor.moveToLast();
}
public boolean move(int offset) {
return mCursor.move(offset);
}
public boolean moveToPosition(int position) {
return mCursor.moveToPosition(position);
}
public boolean moveToNext() {
return mCursor.moveToNext();
}
public int getPosition() {
return mCursor.getPosition();
}
public boolean moveToPrevious() {
return mCursor.moveToPrevious();
}
public void registerContentObserver(ContentObserver observer) {
mCursor.registerContentObserver(observer);
}
public void registerDataSetObserver(DataSetObserver observer) {
mCursor.registerDataSetObserver(observer);
}
public boolean requery() {
return mCursor.requery();
}
public Bundle respond(Bundle extras) {
return mCursor.respond(extras);
}
public void setNotificationUri(ContentResolver cr, Uri uri) {
mCursor.setNotificationUri(cr, uri);
}
/**
* @hide
* @deprecated
*/
public boolean supportsUpdates() {
return mCursor.supportsUpdates();
}
public void unregisterContentObserver(ContentObserver observer) {
mCursor.unregisterContentObserver(observer);
}
public void unregisterDataSetObserver(DataSetObserver observer) {
mCursor.unregisterDataSetObserver(observer);
}
/**
* @hide
* @deprecated
*/
public boolean updateDouble(int columnIndex, double value) {
return mCursor.updateDouble(columnIndex, value);
}
/**
* @hide
* @deprecated
*/
public boolean updateFloat(int columnIndex, float value) {
return mCursor.updateFloat(columnIndex, value);
}
/**
* @hide
* @deprecated
*/
public boolean updateInt(int columnIndex, int value) {
return mCursor.updateInt(columnIndex, value);
}
/**
* @hide
* @deprecated
*/
public boolean updateLong(int columnIndex, long value) {
return mCursor.updateLong(columnIndex, value);
}
/**
* @hide
* @deprecated
*/
public boolean updateShort(int columnIndex, short value) {
return mCursor.updateShort(columnIndex, value);
}
/**
* @hide
* @deprecated
*/
public boolean updateString(int columnIndex, String value) {
return mCursor.updateString(columnIndex, value);
}
/**
* @hide
* @deprecated
*/
public boolean updateBlob(int columnIndex, byte[] value) {
return mCursor.updateBlob(columnIndex, value);
}
/**
* @hide
* @deprecated
*/
public boolean updateToNull(int columnIndex) {
return mCursor.updateToNull(columnIndex);
}
private Cursor mCursor;
}
@@ -0,0 +1,47 @@
/*
* 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 android.database;
/**
* A specialization of Observable for DataSetObserver that provides methods for
* invoking the various callback methods of DataSetObserver.
*/
public class DataSetObservable extends Observable<DataSetObserver> {
/**
* Invokes onChanged on each observer. Called when the data set being observed has
* changed, and which when read contains the new state of the data.
*/
public void notifyChanged() {
synchronized(mObservers) {
for (DataSetObserver observer : mObservers) {
observer.onChanged();
}
}
}
/**
* Invokes onInvalidated on each observer. Called when the data set being monitored
* has changed such that it is no longer valid.
*/
public void notifyInvalidated() {
synchronized (mObservers) {
for (DataSetObserver observer : mObservers) {
observer.onInvalidated();
}
}
}
}
@@ -0,0 +1,41 @@
/*
* 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 android.database;
/**
* Receives call backs when a data set has been changed, or made invalid. The typically data sets
* that are observed are {@link Cursor}s or {@link android.widget.Adapter}s.
* DataSetObserver must be implemented by objects which are added to a DataSetObservable.
*/
public abstract class DataSetObserver {
/**
* This method is called when the entire data set has changed,
* most likely through a call to {@link Cursor#requery()} on a {@link Cursor}.
*/
public void onChanged() {
// Do nothing
}
/**
* This method is called when the entire data becomes invalid,
* most likely through a call to {@link Cursor#deactivate()} or {@link Cursor#close()} on a
* {@link Cursor}.
*/
public void onInvalidated() {
// Do nothing
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,88 @@
/*
* 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 android.database;
import android.os.RemoteException;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Bundle;
import java.util.Map;
/**
* This interface provides a low-level way to pass bulk cursor data across
* both process and language boundries. Application code should use the Cursor
* interface directly.
*
* {@hide}
*/
public interface IBulkCursor extends IInterface {
/**
* Returns a BulkCursorWindow, which either has a reference to a shared
* memory segment with the rows, or an array of JSON strings.
*/
public CursorWindow getWindow(int startPos) throws RemoteException;
public void onMove(int position) throws RemoteException;
/**
* Returns the number of rows in the cursor.
*
* @return the number of rows in the cursor.
*/
public int count() throws RemoteException;
/**
* Returns a string array holding the names of all of the columns in the
* cursor in the order in which they were listed in the result.
*
* @return the names of the columns returned in this query.
*/
public String[] getColumnNames() throws RemoteException;
public boolean updateRows(Map<? extends Long, ? extends Map<String, Object>> values) throws RemoteException;
public boolean deleteRow(int position) throws RemoteException;
public void deactivate() throws RemoteException;
public void close() throws RemoteException;
public int requery(IContentObserver observer, CursorWindow window) throws RemoteException;
boolean getWantsAllOnMoveCalls() throws RemoteException;
Bundle getExtras() throws RemoteException;
Bundle respond(Bundle extras) throws RemoteException;
/* IPC constants */
static final String descriptor = "android.content.IBulkCursor";
static final int GET_CURSOR_WINDOW_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION;
static final int COUNT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 1;
static final int GET_COLUMN_NAMES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 2;
static final int UPDATE_ROWS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 3;
static final int DELETE_ROW_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 4;
static final int DEACTIVATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 5;
static final int REQUERY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 6;
static final int ON_MOVE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 7;
static final int WANTS_ON_MOVE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 8;
static final int GET_EXTRAS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 9;
static final int RESPOND_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 10;
static final int CLOSE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 11;
}
+31
View File
@@ -0,0 +1,31 @@
/*
**
** Copyright 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 android.database;
/**
* @hide
*/
interface IContentObserver
{
/**
* This method is called when an update occurs to the cursor that is being
* observed. selfUpdate is true if the update was caused by a call to
* commit on the cursor that is being observed.
*/
oneway void onChange(boolean selfUpdate);
}
@@ -0,0 +1,278 @@
/*
* 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 android.database;
import java.util.ArrayList;
/**
* A mutable cursor implementation backed by an array of {@code Object}s. Use
* {@link #newRow()} to add rows. Automatically expands internal capacity
* as needed.
*/
public class MatrixCursor extends AbstractCursor {
private final String[] columnNames;
private Object[] data;
private int rowCount = 0;
private final int columnCount;
/**
* Constructs a new cursor with the given initial capacity.
*
* @param columnNames names of the columns, the ordering of which
* determines column ordering elsewhere in this cursor
* @param initialCapacity in rows
*/
public MatrixCursor(String[] columnNames, int initialCapacity) {
this.columnNames = columnNames;
this.columnCount = columnNames.length;
if (initialCapacity < 1) {
initialCapacity = 1;
}
this.data = new Object[columnCount * initialCapacity];
}
/**
* Constructs a new cursor.
*
* @param columnNames names of the columns, the ordering of which
* determines column ordering elsewhere in this cursor
*/
public MatrixCursor(String[] columnNames) {
this(columnNames, 16);
}
/**
* Gets value at the given column for the current row.
*/
private Object get(int column) {
if (column < 0 || column >= columnCount) {
throw new CursorIndexOutOfBoundsException("Requested column: "
+ column + ", # of columns: " + columnCount);
}
if (mPos < 0) {
throw new CursorIndexOutOfBoundsException("Before first row.");
}
if (mPos >= rowCount) {
throw new CursorIndexOutOfBoundsException("After last row.");
}
return data[mPos * columnCount + column];
}
/**
* Adds a new row to the end and returns a builder for that row. Not safe
* for concurrent use.
*
* @return builder which can be used to set the column values for the new
* row
*/
public RowBuilder newRow() {
rowCount++;
int endIndex = rowCount * columnCount;
ensureCapacity(endIndex);
int start = endIndex - columnCount;
return new RowBuilder(start, endIndex);
}
/**
* Adds a new row to the end with the given column values. Not safe
* for concurrent use.
*
* @throws IllegalArgumentException if {@code columnValues.length !=
* columnNames.length}
* @param columnValues in the same order as the the column names specified
* at cursor construction time
*/
public void addRow(Object[] columnValues) {
if (columnValues.length != columnCount) {
throw new IllegalArgumentException("columnNames.length = "
+ columnCount + ", columnValues.length = "
+ columnValues.length);
}
int start = rowCount++ * columnCount;
ensureCapacity(start + columnCount);
System.arraycopy(columnValues, 0, data, start, columnCount);
}
/**
* Adds a new row to the end with the given column values. Not safe
* for concurrent use.
*
* @throws IllegalArgumentException if {@code columnValues.size() !=
* columnNames.length}
* @param columnValues in the same order as the the column names specified
* at cursor construction time
*/
public void addRow(Iterable<?> columnValues) {
int start = rowCount * columnCount;
int end = start + columnCount;
ensureCapacity(end);
if (columnValues instanceof ArrayList<?>) {
addRow((ArrayList<?>) columnValues, start);
return;
}
int current = start;
Object[] localData = data;
for (Object columnValue : columnValues) {
if (current == end) {
// TODO: null out row?
throw new IllegalArgumentException(
"columnValues.size() > columnNames.length");
}
localData[current++] = columnValue;
}
if (current != end) {
// TODO: null out row?
throw new IllegalArgumentException(
"columnValues.size() < columnNames.length");
}
// Increase row count here in case we encounter an exception.
rowCount++;
}
/** Optimization for {@link ArrayList}. */
private void addRow(ArrayList<?> columnValues, int start) {
int size = columnValues.size();
if (size != columnCount) {
throw new IllegalArgumentException("columnNames.length = "
+ columnCount + ", columnValues.size() = " + size);
}
rowCount++;
Object[] localData = data;
for (int i = 0; i < size; i++) {
localData[start + i] = columnValues.get(i);
}
}
/** 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);
}
}
/**
* 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.
*/
public class RowBuilder {
private int index;
private final int endIndex;
RowBuilder(int index, int endIndex) {
this.index = index;
this.endIndex = endIndex;
}
/**
* Sets the next column value in this row.
*
* @throws CursorIndexOutOfBoundsException if you try to add too many
* values
* @return this builder to support chaining
*/
public RowBuilder add(Object columnValue) {
if (index == endIndex) {
throw new CursorIndexOutOfBoundsException(
"No more columns left.");
}
data[index++] = columnValue;
return this;
}
}
// AbstractCursor implementation.
@Override
public int getCount() {
return rowCount;
}
@Override
public String[] getColumnNames() {
return columnNames;
}
@Override
public String getString(int column) {
Object value = get(column);
if (value == null) return null;
return value.toString();
}
@Override
public short getShort(int column) {
Object value = get(column);
if (value == null) return 0;
if (value instanceof Number) return ((Number) value).shortValue();
return Short.parseShort(value.toString());
}
@Override
public int getInt(int column) {
Object value = get(column);
if (value == null) return 0;
if (value instanceof Number) return ((Number) value).intValue();
return Integer.parseInt(value.toString());
}
@Override
public long getLong(int column) {
Object value = get(column);
if (value == null) return 0;
if (value instanceof Number) return ((Number) value).longValue();
return Long.parseLong(value.toString());
}
@Override
public float getFloat(int column) {
Object value = get(column);
if (value == null) return 0.0f;
if (value instanceof Number) return ((Number) value).floatValue();
return Float.parseFloat(value.toString());
}
@Override
public double getDouble(int column) {
Object value = get(column);
if (value == null) return 0.0d;
if (value instanceof Number) return ((Number) value).doubleValue();
return Double.parseDouble(value.toString());
}
@Override
public boolean isNull(int column) {
return get(column) == null;
}
}
@@ -0,0 +1,259 @@
/*
* 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 android.database;
/**
* A convience class that lets you present an array of Cursors as a single linear Cursor.
* The schema of the cursors presented is entirely up to the creator of the MergeCursor, and
* may be different if that is desired. Calls to getColumns, getColumnIndex, etc will return the
* value for the row that the MergeCursor is currently pointing at.
*/
public class MergeCursor extends AbstractCursor
{
private DataSetObserver mObserver = new DataSetObserver() {
@Override
public void onChanged() {
// Reset our position so the optimizations in move-related code
// don't screw us over
mPos = -1;
}
@Override
public void onInvalidated() {
mPos = -1;
}
};
public MergeCursor(Cursor[] cursors)
{
mCursors = cursors;
mCursor = cursors[0];
for (int i = 0; i < mCursors.length; i++) {
if (mCursors[i] == null) continue;
mCursors[i].registerDataSetObserver(mObserver);
}
}
@Override
public int getCount()
{
int count = 0;
int length = mCursors.length;
for (int i = 0 ; i < length ; i++) {
if (mCursors[i] != null) {
count += mCursors[i].getCount();
}
}
return count;
}
@Override
public boolean onMove(int oldPosition, int newPosition)
{
/* Find the right cursor */
mCursor = null;
int cursorStartPos = 0;
int length = mCursors.length;
for (int i = 0 ; i < length; i++) {
if (mCursors[i] == null) {
continue;
}
if (newPosition < (cursorStartPos + mCursors[i].getCount())) {
mCursor = mCursors[i];
break;
}
cursorStartPos += mCursors[i].getCount();
}
/* Move it to the right position */
if (mCursor != null) {
boolean ret = mCursor.moveToPosition(newPosition - cursorStartPos);
return ret;
}
return false;
}
/**
* @hide
* @deprecated
*/
@Override
public boolean deleteRow()
{
return mCursor.deleteRow();
}
/**
* @hide
* @deprecated
*/
@Override
public boolean commitUpdates() {
int length = mCursors.length;
for (int i = 0 ; i < length ; i++) {
if (mCursors[i] != null) {
mCursors[i].commitUpdates();
}
}
onChange(true);
return true;
}
@Override
public String getString(int column)
{
return mCursor.getString(column);
}
@Override
public short getShort(int column)
{
return mCursor.getShort(column);
}
@Override
public int getInt(int column)
{
return mCursor.getInt(column);
}
@Override
public long getLong(int column)
{
return mCursor.getLong(column);
}
@Override
public float getFloat(int column)
{
return mCursor.getFloat(column);
}
@Override
public double getDouble(int column)
{
return mCursor.getDouble(column);
}
@Override
public boolean isNull(int column)
{
return mCursor.isNull(column);
}
@Override
public byte[] getBlob(int column)
{
return mCursor.getBlob(column);
}
@Override
public String[] getColumnNames()
{
if (mCursor != null) {
return mCursor.getColumnNames();
} else {
return new String[0];
}
}
@Override
public void deactivate()
{
int length = mCursors.length;
for (int i = 0 ; i < length ; i++) {
if (mCursors[i] != null) {
mCursors[i].deactivate();
}
}
super.deactivate();
}
@Override
public void close() {
int length = mCursors.length;
for (int i = 0 ; i < length ; i++) {
if (mCursors[i] == null) continue;
mCursors[i].close();
}
super.close();
}
@Override
public void registerContentObserver(ContentObserver observer) {
int length = mCursors.length;
for (int i = 0 ; i < length ; i++) {
if (mCursors[i] != null) {
mCursors[i].registerContentObserver(observer);
}
}
}
@Override
public void unregisterContentObserver(ContentObserver observer) {
int length = mCursors.length;
for (int i = 0 ; i < length ; i++) {
if (mCursors[i] != null) {
mCursors[i].unregisterContentObserver(observer);
}
}
}
@Override
public void registerDataSetObserver(DataSetObserver observer) {
int length = mCursors.length;
for (int i = 0 ; i < length ; i++) {
if (mCursors[i] != null) {
mCursors[i].registerDataSetObserver(observer);
}
}
}
@Override
public void unregisterDataSetObserver(DataSetObserver observer) {
int length = mCursors.length;
for (int i = 0 ; i < length ; i++) {
if (mCursors[i] != null) {
mCursors[i].unregisterDataSetObserver(observer);
}
}
}
@Override
public boolean requery()
{
int length = mCursors.length;
for (int i = 0 ; i < length ; i++) {
if (mCursors[i] == null) {
continue;
}
if (mCursors[i].requery() == false) {
return false;
}
}
return true;
}
private Cursor mCursor; // updated in onMove
private Cursor[] mCursors;
}
@@ -0,0 +1,78 @@
/*
* 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 android.database;
import java.util.ArrayList;
/**
* Provides methods for (un)registering arbitrary observers in an ArrayList.
*/
public abstract class Observable<T> {
/**
* The list of observers. An observer can be in the list at most
* once and will never be null.
*/
protected final ArrayList<T> mObservers = new ArrayList<T>();
/**
* Adds an observer to the list. The observer cannot be null and it must not already
* be registered.
* @param observer the observer to register
* @throws IllegalArgumentException the observer is null
* @throws IllegalStateException the observer is already registered
*/
public void registerObserver(T observer) {
if (observer == null) {
throw new IllegalArgumentException("The observer is null.");
}
synchronized(mObservers) {
if (mObservers.contains(observer)) {
throw new IllegalStateException("Observer " + observer + " is already registered.");
}
mObservers.add(observer);
}
}
/**
* Removes a previously registered observer. The observer must not be null and it
* must already have been registered.
* @param observer the observer to unregister
* @throws IllegalArgumentException the observer is null
* @throws IllegalStateException the observer is not yet registered
*/
public void unregisterObserver(T observer) {
if (observer == null) {
throw new IllegalArgumentException("The observer is null.");
}
synchronized(mObservers) {
int index = mObservers.indexOf(observer);
if (index == -1) {
throw new IllegalStateException("Observer " + observer + " was not registered.");
}
mObservers.remove(index);
}
}
/**
* Remove all registered observer
*/
public void unregisterAll() {
synchronized(mObservers) {
mObservers.clear();
}
}
}
@@ -0,0 +1,30 @@
/*
* 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 android.database;
/**
* An exception that indicates there was an error with SQL parsing or execution.
*/
public class SQLException extends RuntimeException
{
public SQLException() {}
public SQLException(String error)
{
super(error);
}
}
@@ -0,0 +1,34 @@
/*
* 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 android.database;
/**
* This exception is thrown when a Cursor contains stale data and must be
* requeried before being used again.
*/
public class StaleDataException extends java.lang.RuntimeException
{
public StaleDataException()
{
super();
}
public StaleDataException(String description)
{
super(description);
}
}
@@ -0,0 +1,14 @@
<HTML>
<BODY>
Contains classes to explore data returned through a content provider.
<p>
If you need to manage data in a private database, use the {@link
android.database.sqlite} classes. These classes are used to manage the {@link
android.database.Cursor} object returned from a content provider query. Databases
are usually created and opened with {@link android.content.Context#openOrCreateDatabase}
To make requests through
content providers, you can use the {@link android.content.ContentResolver
content.ContentResolver} class.
<p>All databases are stored on the device in <code>/data/data/&lt;package_name&gt;/databases</code>
</BODY>
</HTML>
@@ -0,0 +1,33 @@
/*
* 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 android.database.sqlite;
/**
* An exception that indicates that garbage-collector is finalizing a database object
* that is not explicitly closed
* @hide
*/
public class DatabaseObjectNotClosedException extends RuntimeException
{
private static final String s = "Application did not close the cursor or database object " +
"that was opened here";
public DatabaseObjectNotClosedException()
{
super(s);
}
}
@@ -0,0 +1,30 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.database.sqlite;
/**
* An exception that indicates that the SQLite program was aborted.
* This can happen either through a call to ABORT in a trigger,
* or as the result of using the ABORT conflict clause.
*/
public class SQLiteAbortException extends SQLiteException {
public SQLiteAbortException() {}
public SQLiteAbortException(String error) {
super(error);
}
}
@@ -0,0 +1,77 @@
/*
* 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 android.database.sqlite;
import android.database.CursorWindow;
/**
* An object created from a SQLiteDatabase that can be closed.
*/
public abstract class SQLiteClosable {
private int mReferenceCount = 1;
private Object mLock = new Object();
protected abstract void onAllReferencesReleased();
protected void onAllReferencesReleasedFromContainer() {}
public void acquireReference() {
synchronized(mLock) {
if (mReferenceCount <= 0) {
throw new IllegalStateException(
"attempt to re-open an already-closed object: " + getObjInfo());
}
mReferenceCount++;
}
}
public void releaseReference() {
synchronized(mLock) {
mReferenceCount--;
if (mReferenceCount == 0) {
onAllReferencesReleased();
}
}
}
public void releaseReferenceFromContainer() {
synchronized(mLock) {
mReferenceCount--;
if (mReferenceCount == 0) {
onAllReferencesReleasedFromContainer();
}
}
}
private String getObjInfo() {
StringBuilder buff = new StringBuilder();
buff.append(this.getClass().getName());
buff.append(" (");
if (this instanceof SQLiteDatabase) {
buff.append("database = ");
buff.append(((SQLiteDatabase)this).getPath());
} else if (this instanceof SQLiteProgram || this instanceof SQLiteStatement ||
this instanceof SQLiteQuery) {
buff.append("mSql = ");
buff.append(((SQLiteProgram)this).mSql);
} else if (this instanceof CursorWindow) {
buff.append("mStartPos = ");
buff.append(((CursorWindow)this).getStartPosition());
}
buff.append(") ");
return buff.toString();
}
}
@@ -0,0 +1,171 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.database.sqlite;
import android.os.StrictMode;
import android.util.Log;
/**
* This class encapsulates compilation of sql statement and release of the compiled statement obj.
* Once a sql statement is compiled, it is cached in {@link SQLiteDatabase}
* and it is released in one of the 2 following ways
* 1. when {@link SQLiteDatabase} object is closed.
* 2. if this is not cached in {@link SQLiteDatabase}, {@link android.database.Cursor#close()}
* releaases this obj.
*/
/* package */ class SQLiteCompiledSql {
private static final String TAG = "SQLiteCompiledSql";
/** The database this program is compiled against. */
/* package */ SQLiteDatabase mDatabase;
/**
* Native linkage, do not modify. This comes from the database.
*/
/* package */ int nHandle = 0;
/**
* Native linkage, do not modify. When non-0 this holds a reference to a valid
* sqlite3_statement object. It is only updated by the native code, but may be
* checked in this class when the database lock is held to determine if there
* is a valid native-side program or not.
*/
/* package */ int nStatement = 0;
/** the following are for debugging purposes */
private String mSqlStmt = null;
private Throwable mStackTrace = null;
/** when in cache and is in use, this member is set */
private boolean mInUse = false;
/* package */ SQLiteCompiledSql(SQLiteDatabase db, String sql) {
if (!db.isOpen()) {
throw new IllegalStateException("database " + db.getPath() + " already closed");
}
mDatabase = db;
mSqlStmt = sql;
mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace();
this.nHandle = db.mNativeHandle;
compile(sql, true);
}
/**
* Compiles the given SQL into a SQLite byte code program using sqlite3_prepare_v2(). If
* this method has been called previously without a call to close and forCompilation is set
* to false the previous compilation will be used. Setting forceCompilation to true will
* always re-compile the program and should be done if you pass differing SQL strings to this
* method.
*
* <P>Note: this method acquires the database lock.</P>
*
* @param sql the SQL string to compile
* @param forceCompilation forces the SQL to be recompiled in the event that there is an
* existing compiled SQL program already around
*/
private void compile(String sql, boolean forceCompilation) {
if (!mDatabase.isOpen()) {
throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
}
// Only compile if we don't have a valid statement already or the caller has
// explicitly requested a recompile.
if (forceCompilation) {
mDatabase.lock();
try {
// Note that the native_compile() takes care of destroying any previously
// existing programs before it compiles.
native_compile(sql);
} finally {
mDatabase.unlock();
}
}
}
/* package */ void releaseSqlStatement() {
// Note that native_finalize() checks to make sure that nStatement is
// non-null before destroying it.
if (nStatement != 0) {
if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) {
Log.v(TAG, "closed and deallocated DbObj (id#" + nStatement +")");
}
try {
mDatabase.lock();
native_finalize();
nStatement = 0;
} finally {
mDatabase.unlock();
}
}
}
/**
* returns true if acquire() succeeds. false otherwise.
*/
/* package */ synchronized boolean acquire() {
if (mInUse) {
// someone already has acquired it.
return false;
}
mInUse = true;
if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) {
Log.v(TAG, "Acquired DbObj (id#" + nStatement + ") from DB cache");
}
return true;
}
/* package */ synchronized void release() {
if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) {
Log.v(TAG, "Released DbObj (id#" + nStatement + ") back to DB cache");
}
mInUse = false;
}
/**
* Make sure that the native resource is cleaned up.
*/
@Override
protected void finalize() throws Throwable {
try {
if (nStatement == 0) return;
// finalizer should NEVER get called
if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) {
Log.v(TAG, "** warning ** Finalized DbObj (id#" + nStatement + ")");
}
if (StrictMode.vmSqliteObjectLeaksEnabled()) {
int len = mSqlStmt.length();
StrictMode.onSqliteObjectLeaked(
"Releasing statement in a finalizer. Please ensure " +
"that you explicitly call close() on your cursor: " +
mSqlStmt.substring(0, (len > 100) ? 100 : len),
mStackTrace);
}
releaseSqlStatement();
} finally {
super.finalize();
}
}
/**
* Compiles SQL into a SQLite program.
*
* <P>The database lock must be held when calling this method.
* @param sql The SQL to compile.
*/
private final native void native_compile(String sql);
private final native void native_finalize();
}
@@ -0,0 +1,28 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.database.sqlite;
/**
* An exception that indicates that an integrity constraint was violated.
*/
public class SQLiteConstraintException extends SQLiteException {
public SQLiteConstraintException() {}
public SQLiteConstraintException(String error) {
super(error);
}
}
@@ -0,0 +1,92 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.database.sqlite;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.os.MemoryFile;
import java.io.FileNotFoundException;
import java.io.IOException;
/**
* Some helper functions for using SQLite database to implement content providers.
*
* @hide
*/
public class SQLiteContentHelper {
/**
* Runs an SQLite query and returns an AssetFileDescriptor for the
* blob in column 0 of the first row. If the first column does
* not contain a blob, an unspecified exception is thrown.
*
* @param db Handle to a readable database.
* @param sql SQL query, possibly with query arguments.
* @param selectionArgs Query argument values, or {@code null} for no argument.
* @return If no exception is thrown, a non-null AssetFileDescriptor is returned.
* @throws FileNotFoundException If the query returns no results or the
* value of column 0 is NULL, or if there is an error creating the
* asset file descriptor.
*/
public static AssetFileDescriptor getBlobColumnAsAssetFile(SQLiteDatabase db, String sql,
String[] selectionArgs) throws FileNotFoundException {
try {
MemoryFile file = simpleQueryForBlobMemoryFile(db, sql, selectionArgs);
if (file == null) {
throw new FileNotFoundException("No results.");
}
return AssetFileDescriptor.fromMemoryFile(file);
} catch (IOException ex) {
throw new FileNotFoundException(ex.toString());
}
}
/**
* Runs an SQLite query and returns a MemoryFile for the
* blob in column 0 of the first row. If the first column does
* not contain a blob, an unspecified exception is thrown.
*
* @return A memory file, or {@code null} if the query returns no results
* or the value column 0 is NULL.
* @throws IOException If there is an error creating the memory file.
*/
// TODO: make this native and use the SQLite blob API to reduce copying
private static MemoryFile simpleQueryForBlobMemoryFile(SQLiteDatabase db, String sql,
String[] selectionArgs) throws IOException {
Cursor cursor = db.rawQuery(sql, selectionArgs);
if (cursor == null) {
return null;
}
try {
if (!cursor.moveToFirst()) {
return null;
}
byte[] bytes = cursor.getBlob(0);
if (bytes == null) {
return null;
}
MemoryFile file = new MemoryFile(null, bytes.length);
file.writeBytes(bytes, 0, 0, bytes.length);
file.deactivate();
return file;
} finally {
cursor.close();
}
}
}
@@ -0,0 +1,606 @@
/*
* 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 android.database.sqlite;
import android.database.AbstractWindowedCursor;
import android.database.CursorWindow;
import android.database.DataSetObserver;
import android.database.SQLException;
import android.os.Handler;
import android.os.Message;
import android.os.Process;
import android.os.StrictMode;
import android.text.TextUtils;
import android.util.Config;
import android.util.Log;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
/**
* A Cursor implementation that exposes results from a query on a
* {@link SQLiteDatabase}.
*
* SQLiteCursor is not internally synchronized so code using a SQLiteCursor from multiple
* threads should perform its own synchronization when using the SQLiteCursor.
*/
public class SQLiteCursor extends AbstractWindowedCursor {
static final String TAG = "Cursor";
static final int NO_COUNT = -1;
/** The name of the table to edit */
private String mEditTable;
/** The names of the columns in the rows */
private String[] mColumns;
/** The query object for the cursor */
private SQLiteQuery mQuery;
/** The database the cursor was created from */
private SQLiteDatabase mDatabase;
/** The compiled query this cursor came from */
private SQLiteCursorDriver mDriver;
/** The number of rows in the cursor */
private int mCount = NO_COUNT;
/** A mapping of column names to column indices, to speed up lookups */
private Map<String, Integer> mColumnNameMap;
/** Used to find out where a cursor was allocated in case it never got released. */
private Throwable mStackTrace;
/**
* mMaxRead is the max items that each cursor window reads
* default to a very high value
*/
private int mMaxRead = Integer.MAX_VALUE;
private int mInitialRead = Integer.MAX_VALUE;
private int mCursorState = 0;
private ReentrantLock mLock = null;
private boolean mPendingData = false;
/**
* support for a cursor variant that doesn't always read all results
* initialRead is the initial number of items that cursor window reads
* if query contains more than this number of items, a thread will be
* created and handle the left over items so that caller can show
* results as soon as possible
* @param initialRead initial number of items that cursor read
* @param maxRead leftover items read at maxRead items per time
* @hide
*/
public void setLoadStyle(int initialRead, int maxRead) {
mMaxRead = maxRead;
mInitialRead = initialRead;
mLock = new ReentrantLock(true);
}
private void queryThreadLock() {
if (mLock != null) {
mLock.lock();
}
}
private void queryThreadUnlock() {
if (mLock != null) {
mLock.unlock();
}
}
/**
* @hide
*/
final private class QueryThread implements Runnable {
private final int mThreadState;
QueryThread(int version) {
mThreadState = version;
}
private void sendMessage() {
if (mNotificationHandler != null) {
mNotificationHandler.sendEmptyMessage(1);
mPendingData = false;
} else {
mPendingData = true;
}
}
public void run() {
// use cached mWindow, to avoid get null mWindow
CursorWindow cw = mWindow;
Process.setThreadPriority(Process.myTid(), Process.THREAD_PRIORITY_BACKGROUND);
// the cursor's state doesn't change
while (true) {
mLock.lock();
if (mCursorState != mThreadState) {
mLock.unlock();
break;
}
try {
int count = mQuery.fillWindow(cw, mMaxRead, mCount);
// return -1 means not finished
if (count != 0) {
if (count == NO_COUNT){
mCount += mMaxRead;
sendMessage();
} else {
mCount = count;
sendMessage();
break;
}
} else {
break;
}
} catch (Exception e) {
// end the tread when the cursor is close
break;
} finally {
mLock.unlock();
}
}
}
}
/**
* @hide
*/
protected class MainThreadNotificationHandler extends Handler {
public void handleMessage(Message msg) {
notifyDataSetChange();
}
}
/**
* @hide
*/
protected MainThreadNotificationHandler mNotificationHandler;
public void registerDataSetObserver(DataSetObserver observer) {
super.registerDataSetObserver(observer);
if ((Integer.MAX_VALUE != mMaxRead || Integer.MAX_VALUE != mInitialRead) &&
mNotificationHandler == null) {
queryThreadLock();
try {
mNotificationHandler = new MainThreadNotificationHandler();
if (mPendingData) {
notifyDataSetChange();
mPendingData = false;
}
} finally {
queryThreadUnlock();
}
}
}
/**
* Execute a query and provide access to its result set through a Cursor
* interface. For a query such as: {@code SELECT name, birth, phone FROM
* myTable WHERE ... LIMIT 1,20 ORDER BY...} the column names (name, birth,
* phone) would be in the projection argument and everything from
* {@code FROM} onward would be in the params argument. This constructor
* has package scope.
*
* @param db a reference to a Database object that is already constructed
* and opened
* @param editTable the name of the table used for this query
* @param query the rest of the query terms
* cursor is finalized
*/
public SQLiteCursor(SQLiteDatabase db, SQLiteCursorDriver driver,
String editTable, SQLiteQuery query) {
// The AbstractCursor constructor needs to do some setup.
super();
mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace();
mDatabase = db;
mDriver = driver;
mEditTable = editTable;
mColumnNameMap = null;
mQuery = query;
try {
db.lock();
// Setup the list of columns
int columnCount = mQuery.columnCountLocked();
mColumns = new String[columnCount];
// Read in all column names
for (int i = 0; i < columnCount; i++) {
String columnName = mQuery.columnNameLocked(i);
mColumns[i] = columnName;
if (Config.LOGV) {
Log.v("DatabaseWindow", "mColumns[" + i + "] is "
+ mColumns[i]);
}
// Make note of the row ID column index for quick access to it
if ("_id".equals(columnName)) {
mRowIdColumnIndex = i;
}
}
} finally {
db.unlock();
}
}
/**
* @return the SQLiteDatabase that this cursor is associated with.
*/
public SQLiteDatabase getDatabase() {
return mDatabase;
}
@Override
public boolean onMove(int oldPosition, int newPosition) {
// Make sure the row at newPosition is present in the window
if (mWindow == null || newPosition < mWindow.getStartPosition() ||
newPosition >= (mWindow.getStartPosition() + mWindow.getNumRows())) {
fillWindow(newPosition);
}
return true;
}
@Override
public int getCount() {
if (mCount == NO_COUNT) {
fillWindow(0);
}
return mCount;
}
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 */);
} else {
mCursorState++;
queryThreadLock();
try {
mWindow.clear();
} finally {
queryThreadUnlock();
}
}
mWindow.setStartPosition(startPos);
mCount = mQuery.fillWindow(mWindow, mInitialRead, 0);
// return -1 means not finished
if (mCount == NO_COUNT){
mCount = startPos + mInitialRead;
Thread t = new Thread(new QueryThread(mCursorState), "query thread");
t.start();
}
}
@Override
public int getColumnIndex(String columnName) {
// Create mColumnNameMap on demand
if (mColumnNameMap == null) {
String[] columns = mColumns;
int columnCount = columns.length;
HashMap<String, Integer> map = new HashMap<String, Integer>(columnCount, 1);
for (int i = 0; i < columnCount; i++) {
map.put(columns[i], i);
}
mColumnNameMap = map;
}
// Hack according to bug 903852
final int periodIndex = columnName.lastIndexOf('.');
if (periodIndex != -1) {
Exception e = new Exception();
Log.e(TAG, "requesting column name with table name -- " + columnName, e);
columnName = columnName.substring(periodIndex + 1);
}
Integer i = mColumnNameMap.get(columnName);
if (i != null) {
return i.intValue();
} else {
return -1;
}
}
/**
* @hide
* @deprecated
*/
@Override
public boolean deleteRow() {
checkPosition();
// Only allow deletes if there is an ID column, and the ID has been read from it
if (mRowIdColumnIndex == -1 || mCurrentRowID == null) {
Log.e(TAG,
"Could not delete row because either the row ID column is not available or it" +
"has not been read.");
return false;
}
boolean success;
/*
* Ensure we don't change the state of the database when another
* thread is holding the database lock. requery() and moveTo() are also
* synchronized here to make sure they get the state of the database
* immediately following the DELETE.
*/
mDatabase.lock();
try {
try {
mDatabase.delete(mEditTable, mColumns[mRowIdColumnIndex] + "=?",
new String[] {mCurrentRowID.toString()});
success = true;
} catch (SQLException e) {
success = false;
}
int pos = mPos;
requery();
/*
* Ensure proper cursor state. Note that mCurrentRowID changes
* in this call.
*/
moveToPosition(pos);
} finally {
mDatabase.unlock();
}
if (success) {
onChange(true);
return true;
} else {
return false;
}
}
@Override
public String[] getColumnNames() {
return mColumns;
}
/**
* @hide
* @deprecated
*/
@Override
public boolean supportsUpdates() {
return super.supportsUpdates() && !TextUtils.isEmpty(mEditTable);
}
/**
* @hide
* @deprecated
*/
@Override
public boolean commitUpdates(Map<? extends Long,
? extends Map<String, Object>> additionalValues) {
if (!supportsUpdates()) {
Log.e(TAG, "commitUpdates not supported on this cursor, did you "
+ "include the _id column?");
return false;
}
/*
* Prevent other threads from changing the updated rows while they're
* being processed here.
*/
synchronized (mUpdatedRows) {
if (additionalValues != null) {
mUpdatedRows.putAll(additionalValues);
}
if (mUpdatedRows.size() == 0) {
return true;
}
/*
* Prevent other threads from changing the database state while
* we process the updated rows, and prevents us from changing the
* database behind the back of another thread.
*/
mDatabase.beginTransaction();
try {
StringBuilder sql = new StringBuilder(128);
// For each row that has been updated
for (Map.Entry<Long, Map<String, Object>> rowEntry :
mUpdatedRows.entrySet()) {
Map<String, Object> values = rowEntry.getValue();
Long rowIdObj = rowEntry.getKey();
if (rowIdObj == null || values == null) {
throw new IllegalStateException("null rowId or values found! rowId = "
+ rowIdObj + ", values = " + values);
}
if (values.size() == 0) {
continue;
}
long rowId = rowIdObj.longValue();
Iterator<Map.Entry<String, Object>> valuesIter =
values.entrySet().iterator();
sql.setLength(0);
sql.append("UPDATE " + mEditTable + " SET ");
// For each column value that has been updated
Object[] bindings = new Object[values.size()];
int i = 0;
while (valuesIter.hasNext()) {
Map.Entry<String, Object> entry = valuesIter.next();
sql.append(entry.getKey());
sql.append("=?");
bindings[i] = entry.getValue();
if (valuesIter.hasNext()) {
sql.append(", ");
}
i++;
}
sql.append(" WHERE " + mColumns[mRowIdColumnIndex]
+ '=' + rowId);
sql.append(';');
mDatabase.execSQL(sql.toString(), bindings);
mDatabase.rowUpdated(mEditTable, rowId);
}
mDatabase.setTransactionSuccessful();
} finally {
mDatabase.endTransaction();
}
mUpdatedRows.clear();
}
// Let any change observers know about the update
onChange(true);
return true;
}
private void deactivateCommon() {
if (Config.LOGV) Log.v(TAG, "<<< Releasing cursor " + this);
mCursorState = 0;
if (mWindow != null) {
mWindow.close();
mWindow = null;
}
if (Config.LOGV) Log.v("DatabaseWindow", "closing window in release()");
}
@Override
public void deactivate() {
super.deactivate();
deactivateCommon();
mDriver.cursorDeactivated();
}
@Override
public void close() {
super.close();
deactivateCommon();
mQuery.close();
mDriver.cursorClosed();
}
@Override
public boolean requery() {
if (isClosed()) {
return false;
}
long timeStart = 0;
if (Config.LOGV) {
timeStart = System.currentTimeMillis();
}
/*
* Synchronize on the database lock to ensure that mCount matches the
* results of mQuery.requery().
*/
mDatabase.lock();
try {
if (mWindow != null) {
mWindow.clear();
}
mPos = -1;
// This one will recreate the temp table, and get its count
mDriver.cursorRequeried(this);
mCount = NO_COUNT;
mCursorState++;
queryThreadLock();
try {
mQuery.requery();
} finally {
queryThreadUnlock();
}
} finally {
mDatabase.unlock();
}
if (Config.LOGV) {
Log.v("DatabaseWindow", "closing window in requery()");
Log.v(TAG, "--- Requery()ed cursor " + this + ": " + mQuery);
}
boolean result = super.requery();
if (Config.LOGV) {
long timeEnd = System.currentTimeMillis();
Log.v(TAG, "requery (" + (timeEnd - timeStart) + " ms): " + mDriver.toString());
}
return result;
}
@Override
public void setWindow(CursorWindow window) {
if (mWindow != null) {
mCursorState++;
queryThreadLock();
try {
mWindow.close();
} finally {
queryThreadUnlock();
}
mCount = NO_COUNT;
}
mWindow = window;
}
/**
* Changes the selection arguments. The new values take effect after a call to requery().
*/
public void setSelectionArguments(String[] selectionArgs) {
mDriver.setBindArguments(selectionArgs);
}
/**
* Release the native resources, if they haven't been released yet.
*/
@Override
protected void finalize() {
try {
// if the cursor hasn't been closed yet, close it first
if (mWindow != null) {
if (StrictMode.vmSqliteObjectLeaksEnabled()) {
int len = mQuery.mSql.length();
StrictMode.onSqliteObjectLeaked(
"Finalizing a Cursor that has not been deactivated or closed. " +
"database = " + mDatabase.getPath() + ", table = " + mEditTable +
", query = " + mQuery.mSql.substring(0, (len > 100) ? 100 : len),
mStackTrace);
}
close();
SQLiteDebug.notifyActiveCursorFinalized();
} else {
if (Config.LOGV) {
Log.v(TAG, "Finalizing cursor on database = " + mDatabase.getPath() +
", table = " + mEditTable + ", query = " + mQuery.mSql);
}
}
} finally {
super.finalize();
}
}
}
@@ -0,0 +1,58 @@
/*
* 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 android.database.sqlite;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
/**
* A driver for SQLiteCursors that is used to create them and gets notified
* by the cursors it creates on significant events in their lifetimes.
*/
public interface SQLiteCursorDriver {
/**
* Executes the query returning a Cursor over the result set.
*
* @param factory The CursorFactory to use when creating the Cursors, or
* null if standard SQLiteCursors should be returned.
* @return a Cursor over the result set
*/
Cursor query(CursorFactory factory, String[] bindArgs);
/**
* Called by a SQLiteCursor when it is released.
*/
void cursorDeactivated();
/**
* Called by a SQLiteCursor when it is requeryed.
*
* @return The new count value.
*/
void cursorRequeried(Cursor cursor);
/**
* Called by a SQLiteCursor when it it closed to destroy this object as well.
*/
void cursorClosed();
/**
* Set new bind arguments. These will take effect in cursorRequeried().
* @param bindArgs the new arguments
*/
public void setBindArguments(String[] bindArgs);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,28 @@
/*
* 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 android.database.sqlite;
/**
* An exception that indicates that the SQLite database file is corrupt.
*/
public class SQLiteDatabaseCorruptException extends SQLiteException {
public SQLiteDatabaseCorruptException() {}
public SQLiteDatabaseCorruptException(String error) {
super(error);
}
}
@@ -0,0 +1,197 @@
/*
* 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 android.database.sqlite;
import java.util.ArrayList;
import android.util.Log;
/**
* Provides debugging info about all SQLite databases running in the current process.
*
* {@hide}
*/
public final class SQLiteDebug {
/**
* Controls the printing of SQL statements as they are executed.
*/
public static final boolean DEBUG_SQL_STATEMENTS =
Log.isLoggable("SQLiteStatements", Log.VERBOSE);
/**
* Controls the printing of wall-clock time taken to execute SQL statements
* as they are executed.
*/
public static final boolean DEBUG_SQL_TIME =
Log.isLoggable("SQLiteTime", Log.VERBOSE);
/**
* Controls the printing of compiled-sql-statement cache stats.
*/
public static final boolean DEBUG_SQL_CACHE =
Log.isLoggable("SQLiteCompiledSql", Log.VERBOSE);
/**
* Controls the stack trace reporting of active cursors being
* finalized.
*/
public static final boolean DEBUG_ACTIVE_CURSOR_FINALIZATION =
Log.isLoggable("SQLiteCursorClosing", Log.VERBOSE);
/**
* Controls the tracking of time spent holding the database lock.
*/
public static final boolean DEBUG_LOCK_TIME_TRACKING =
Log.isLoggable("SQLiteLockTime", Log.VERBOSE);
/**
* Controls the printing of stack traces when tracking the time spent holding the database lock.
*/
public static final boolean DEBUG_LOCK_TIME_TRACKING_STACK_TRACE =
Log.isLoggable("SQLiteLockStackTrace", Log.VERBOSE);
/**
* Contains statistics about the active pagers in the current process.
*
* @see #getPagerStats(PagerStats)
*/
public static class PagerStats {
/** The total number of bytes in all pagers in the current process
* @deprecated not used any longer
*/
@Deprecated
public long totalBytes;
/** The number of bytes in referenced pages in all pagers in the current process
* @deprecated not used any longer
* */
@Deprecated
public long referencedBytes;
/** The number of bytes in all database files opened in the current process
* @deprecated not used any longer
*/
@Deprecated
public long databaseBytes;
/** The number of pagers opened in the current process
* @deprecated not used any longer
*/
@Deprecated
public int numPagers;
/** the current amount of memory checked out by sqlite using sqlite3_malloc().
* documented at http://www.sqlite.org/c3ref/c_status_malloc_size.html
*/
public int memoryUsed;
/** the number of bytes of page cache allocation which could not be sattisfied by the
* SQLITE_CONFIG_PAGECACHE buffer and where forced to overflow to sqlite3_malloc().
* The returned value includes allocations that overflowed because they where too large
* (they were larger than the "sz" parameter to SQLITE_CONFIG_PAGECACHE) and allocations
* that overflowed because no space was left in the page cache.
* documented at http://www.sqlite.org/c3ref/c_status_malloc_size.html
*/
public int pageCacheOverflo;
/** records the largest memory allocation request handed to sqlite3.
* documented at http://www.sqlite.org/c3ref/c_status_malloc_size.html
*/
public int largestMemAlloc;
/** a list of {@link DbStats} - one for each main database opened by the applications
* running on the android device
*/
public ArrayList<DbStats> dbStats;
}
/**
* contains statistics about a database
*/
public static class DbStats {
/** name of the database */
public String dbName;
/** the page size for the database */
public long pageSize;
/** the database size */
public long dbSize;
/** documented here http://www.sqlite.org/c3ref/c_dbstatus_lookaside_used.html */
public int lookaside;
public DbStats(String dbName, long pageCount, long pageSize, int lookaside) {
this.dbName = dbName;
this.pageSize = pageSize / 1024;
dbSize = (pageCount * pageSize) / 1024;
this.lookaside = lookaside;
}
}
/**
* return all pager and database stats for the current process.
* @return {@link PagerStats}
*/
public static PagerStats getDatabaseInfo() {
PagerStats stats = new PagerStats();
getPagerStats(stats);
stats.dbStats = SQLiteDatabase.getDbStats();
return stats;
}
/**
* Gathers statistics about all pagers in the current process.
*/
public static native void getPagerStats(PagerStats stats);
/**
* Returns the size of the SQLite heap.
* @return The size of the SQLite heap in bytes.
*/
public static native long getHeapSize();
/**
* Returns the amount of allocated memory in the SQLite heap.
* @return The allocated size in bytes.
*/
public static native long getHeapAllocatedSize();
/**
* Returns the amount of free memory in the SQLite heap.
* @return The freed size in bytes.
*/
public static native long getHeapFreeSize();
/**
* Determines the number of dirty belonging to the SQLite
* heap segments of this process. pages[0] returns the number of
* shared pages, pages[1] returns the number of private pages
*/
public static native void getHeapDirtyPages(int[] pages);
private static int sNumActiveCursorsFinalized = 0;
/**
* Returns the number of active cursors that have been finalized. This depends on the GC having
* run but is still useful for tests.
*/
public static int getNumActiveCursorsFinalized() {
return sNumActiveCursorsFinalized;
}
static synchronized void notifyActiveCursorFinalized() {
sNumActiveCursorsFinalized++;
}
}
@@ -0,0 +1,90 @@
/*
* 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 android.database.sqlite;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
/**
* A cursor driver that uses the given query directly.
*
* @hide
*/
public class SQLiteDirectCursorDriver implements SQLiteCursorDriver {
private String mEditTable;
private SQLiteDatabase mDatabase;
private Cursor mCursor;
private String mSql;
private SQLiteQuery mQuery;
public SQLiteDirectCursorDriver(SQLiteDatabase db, String sql, String editTable) {
mDatabase = db;
mEditTable = editTable;
mSql = sql;
}
public Cursor query(CursorFactory factory, String[] selectionArgs) {
// Compile the query
SQLiteQuery query = new SQLiteQuery(mDatabase, mSql, 0, selectionArgs);
try {
// Arg binding
int numArgs = selectionArgs == null ? 0 : selectionArgs.length;
for (int i = 0; i < numArgs; i++) {
query.bindString(i + 1, selectionArgs[i]);
}
// Create the cursor
if (factory == null) {
mCursor = new SQLiteCursor(mDatabase, this, mEditTable, query);
} else {
mCursor = factory.newCursor(mDatabase, this, mEditTable, query);
}
mQuery = query;
query = null;
return mCursor;
} finally {
// Make sure this object is cleaned up if something happens
if (query != null) query.close();
}
}
public void cursorClosed() {
mCursor = null;
}
public void setBindArguments(String[] bindArgs) {
final int numArgs = bindArgs.length;
for (int i = 0; i < numArgs; i++) {
mQuery.bindString(i + 1, bindArgs[i]);
}
}
public void cursorDeactivated() {
// Do nothing
}
public void cursorRequeried(Cursor cursor) {
// Do nothing
}
@Override
public String toString() {
return "SQLiteDirectCursorDriver: " + mSql;
}
}
@@ -0,0 +1,29 @@
/*
* 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 android.database.sqlite;
/**
* An exception that indicates that an IO error occured while accessing the
* SQLite database file.
*/
public class SQLiteDiskIOException extends SQLiteException {
public SQLiteDiskIOException() {}
public SQLiteDiskIOException(String error) {
super(error);
}
}
@@ -0,0 +1,31 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.database.sqlite;
/**
* An exception that indicates that the SQLite program is done.
* Thrown when an operation that expects a row (such as {@link
* SQLiteStatement#simpleQueryForString} or {@link
* SQLiteStatement#simpleQueryForLong}) does not get one.
*/
public class SQLiteDoneException extends SQLiteException {
public SQLiteDoneException() {}
public SQLiteDoneException(String error) {
super(error);
}
}
@@ -0,0 +1,30 @@
/*
* 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 android.database.sqlite;
import android.database.SQLException;
/**
* A SQLite exception that indicates there was an error with SQL parsing or execution.
*/
public class SQLiteException extends SQLException {
public SQLiteException() {}
public SQLiteException(String error) {
super(error);
}
}
@@ -0,0 +1,28 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.database.sqlite;
/**
* An exception that indicates that the SQLite database is full.
*/
public class SQLiteFullException extends SQLiteException {
public SQLiteFullException() {}
public SQLiteFullException(String error) {
super(error);
}
}
@@ -0,0 +1,25 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.database.sqlite;
public class SQLiteMisuseException extends SQLiteException {
public SQLiteMisuseException() {}
public SQLiteMisuseException(String error) {
super(error);
}
}
@@ -0,0 +1,258 @@
/*
* 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 android.database.sqlite;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.util.Log;
/**
* A helper class to manage database creation and version management.
*
* <p>You create a subclass implementing {@link #onCreate}, {@link #onUpgrade} and
* optionally {@link #onOpen}, and this class takes care of opening the database
* if it exists, creating it if it does not, and upgrading it as necessary.
* Transactions are used to make sure the database is always in a sensible state.
*
* <p>This class makes it easy for {@link android.content.ContentProvider}
* implementations to defer opening and upgrading the database until first use,
* to avoid blocking application startup with long-running database upgrades.
*
* <p>For an example, see the NotePadProvider class in the NotePad sample application,
* in the <em>samples/</em> directory of the SDK.</p>
*
* <p class="note"><strong>Note:</strong> this class assumes
* monotonically increasing version numbers for upgrades. Also, there
* is no concept of a database downgrade; installing a new version of
* your app which uses a lower version number than a
* previously-installed version will result in undefined behavior.</p>
*/
public abstract class SQLiteOpenHelper {
private static final String TAG = SQLiteOpenHelper.class.getSimpleName();
private final Context mContext;
private final String mName;
private final CursorFactory mFactory;
private final int mNewVersion;
private SQLiteDatabase mDatabase = null;
private boolean mIsInitializing = false;
/**
* Create a helper object to create, open, and/or manage a database.
* This method always returns very quickly. The database is not actually
* created or opened until one of {@link #getWritableDatabase} or
* {@link #getReadableDatabase} is called.
*
* @param context to use to open or create the database
* @param name of the database file, or null for an in-memory database
* @param factory to use for creating cursor objects, or null for the default
* @param version number of the database (starting at 1); if the database is older,
* {@link #onUpgrade} will be used to upgrade the database
*/
public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) {
if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version);
mContext = context;
mName = name;
mFactory = factory;
mNewVersion = version;
}
/**
* Create and/or open a database that will be used for reading and writing.
* The first time this is called, the database will be opened and
* {@link #onCreate}, {@link #onUpgrade} and/or {@link #onOpen} will be
* called.
*
* <p>Once opened successfully, the database is cached, so you can
* call this method every time you need to write to the database.
* (Make sure to call {@link #close} when you no longer need the database.)
* Errors such as bad permissions or a full disk may cause this method
* to fail, but future attempts may succeed if the problem is fixed.</p>
*
* <p class="caution">Database upgrade may take a long time, you
* should not call this method from the application main thread, including
* from {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}.
*
* @throws SQLiteException if the database cannot be opened for writing
* @return a read/write database object valid until {@link #close} is called
*/
public synchronized SQLiteDatabase getWritableDatabase() {
if (mDatabase != null && mDatabase.isOpen() && !mDatabase.isReadOnly()) {
return mDatabase; // The database is already open for business
}
if (mIsInitializing) {
throw new IllegalStateException("getWritableDatabase called recursively");
}
// If we have a read-only database open, someone could be using it
// (though they shouldn't), which would cause a lock to be held on
// the file, and our attempts to open the database read-write would
// fail waiting for the file lock. To prevent that, we acquire the
// lock on the read-only database, which shuts out other users.
boolean success = false;
SQLiteDatabase db = null;
if (mDatabase != null) mDatabase.lock();
try {
mIsInitializing = true;
if (mName == null) {
db = SQLiteDatabase.create(null);
} else {
db = mContext.openOrCreateDatabase(mName, 0, mFactory);
}
int version = db.getVersion();
if (version != mNewVersion) {
db.beginTransaction();
try {
if (version == 0) {
onCreate(db);
} else {
if (version > mNewVersion) {
Log.wtf(TAG, "Can't downgrade read-only database from version " +
version + " to " + mNewVersion + ": " + db.getPath());
}
onUpgrade(db, version, mNewVersion);
}
db.setVersion(mNewVersion);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
onOpen(db);
success = true;
return db;
} finally {
mIsInitializing = false;
if (success) {
if (mDatabase != null) {
try { mDatabase.close(); } catch (Exception e) { }
mDatabase.unlock();
}
mDatabase = db;
} else {
if (mDatabase != null) mDatabase.unlock();
if (db != null) db.close();
}
}
}
/**
* Create and/or open a database. This will be the same object returned by
* {@link #getWritableDatabase} unless some problem, such as a full disk,
* requires the database to be opened read-only. In that case, a read-only
* database object will be returned. If the problem is fixed, a future call
* to {@link #getWritableDatabase} may succeed, in which case the read-only
* database object will be closed and the read/write object will be returned
* in the future.
*
* <p class="caution">Like {@link #getWritableDatabase}, this method may
* take a long time to return, so you should not call it from the
* application main thread, including from
* {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}.
*
* @throws SQLiteException if the database cannot be opened
* @return a database object valid until {@link #getWritableDatabase}
* or {@link #close} is called.
*/
public synchronized SQLiteDatabase getReadableDatabase() {
if (mDatabase != null && mDatabase.isOpen()) {
return mDatabase; // The database is already open for business
}
if (mIsInitializing) {
throw new IllegalStateException("getReadableDatabase called recursively");
}
try {
return getWritableDatabase();
} catch (SQLiteException e) {
if (mName == null) throw e; // Can't open a temp database read-only!
Log.e(TAG, "Couldn't open " + mName + " for writing (will try read-only):", e);
}
SQLiteDatabase db = null;
try {
mIsInitializing = true;
String path = mContext.getDatabasePath(mName).getPath();
db = SQLiteDatabase.openDatabase(path, mFactory, SQLiteDatabase.OPEN_READONLY);
if (db.getVersion() != mNewVersion) {
throw new SQLiteException("Can't upgrade read-only database from version " +
db.getVersion() + " to " + mNewVersion + ": " + path);
}
onOpen(db);
Log.w(TAG, "Opened " + mName + " in read-only mode");
mDatabase = db;
return mDatabase;
} finally {
mIsInitializing = false;
if (db != null && db != mDatabase) db.close();
}
}
/**
* Close any open database object.
*/
public synchronized void close() {
if (mIsInitializing) throw new IllegalStateException("Closed during initialization");
if (mDatabase != null && mDatabase.isOpen()) {
mDatabase.close();
mDatabase = null;
}
}
/**
* Called when the database is created for the first time. This is where the
* creation of tables and the initial population of the tables should happen.
*
* @param db The database.
*/
public abstract void onCreate(SQLiteDatabase db);
/**
* Called when the database needs to be upgraded. The implementation
* should use this method to drop tables, add tables, or do anything else it
* needs to upgrade to the new schema version.
*
* <p>The SQLite ALTER TABLE documentation can be found
* <a href="http://sqlite.org/lang_altertable.html">here</a>. If you add new columns
* you can use ALTER TABLE to insert them into a live table. If you rename or remove columns
* you can use ALTER TABLE to rename the old table, then create the new table and then
* populate the new table with the contents of the old table.
*
* @param db The database.
* @param oldVersion The old database version.
* @param newVersion The new database version.
*/
public abstract void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion);
/**
* Called when the database has been opened. The implementation
* should check {@link SQLiteDatabase#isReadOnly} before updating the
* database.
*
* @param db The database.
*/
public void onOpen(SQLiteDatabase db) {}
}
@@ -0,0 +1,325 @@
/*
* 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 android.database.sqlite;
import android.util.Log;
/**
* A base class for compiled SQLite programs.
*
* SQLiteProgram is not internally synchronized so code using a SQLiteProgram from multiple
* threads should perform its own synchronization when using the SQLiteProgram.
*/
public abstract class SQLiteProgram extends SQLiteClosable {
private static final String TAG = "SQLiteProgram";
/** The database this program is compiled against.
* @deprecated do not use this
*/
@Deprecated
protected SQLiteDatabase mDatabase;
/** The SQL used to create this query */
/* package */ final String mSql;
/**
* Native linkage, do not modify. This comes from the database and should not be modified
* in here or in the native code.
* @deprecated do not use this
*/
@Deprecated
protected int nHandle = 0;
/**
* the SQLiteCompiledSql object for the given sql statement.
*/
private SQLiteCompiledSql mCompiledSql;
/**
* SQLiteCompiledSql statement id is populated with the corresponding object from the above
* member. This member is used by the native_bind_* methods
* @deprecated do not use this
*/
@Deprecated
protected int nStatement = 0;
/* package */ SQLiteProgram(SQLiteDatabase db, String sql) {
mDatabase = db;
mSql = sql.trim();
db.acquireReference();
db.addSQLiteClosable(this);
this.nHandle = db.mNativeHandle;
// only cache CRUD statements
String prefixSql = mSql.substring(0, 6);
if (!prefixSql.equalsIgnoreCase("INSERT") && !prefixSql.equalsIgnoreCase("UPDATE") &&
!prefixSql.equalsIgnoreCase("REPLAC") &&
!prefixSql.equalsIgnoreCase("DELETE") && !prefixSql.equalsIgnoreCase("SELECT")) {
mCompiledSql = new SQLiteCompiledSql(db, sql);
nStatement = mCompiledSql.nStatement;
// since it is not in the cache, no need to acquire() it.
return;
}
// it is not pragma
mCompiledSql = db.getCompiledStatementForSql(sql);
if (mCompiledSql == null) {
// create a new compiled-sql obj
mCompiledSql = new SQLiteCompiledSql(db, sql);
// add it to the cache of compiled-sqls
// but before adding it and thus making it available for anyone else to use it,
// make sure it is acquired by me.
mCompiledSql.acquire();
db.addToCompiledQueries(sql, mCompiledSql);
if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) {
Log.v(TAG, "Created DbObj (id#" + mCompiledSql.nStatement +
") for sql: " + sql);
}
} else {
// it is already in compiled-sql cache.
// try to acquire the object.
if (!mCompiledSql.acquire()) {
int last = mCompiledSql.nStatement;
// the SQLiteCompiledSql in cache is in use by some other SQLiteProgram object.
// we can't have two different SQLiteProgam objects can't share the same
// CompiledSql object. create a new one.
// finalize it when I am done with it in "this" object.
mCompiledSql = new SQLiteCompiledSql(db, sql);
if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) {
Log.v(TAG, "** possible bug ** Created NEW DbObj (id#" +
mCompiledSql.nStatement +
") because the previously created DbObj (id#" + last +
") was not released for sql:" + sql);
}
// since it is not in the cache, no need to acquire() it.
}
}
nStatement = mCompiledSql.nStatement;
}
@Override
protected void onAllReferencesReleased() {
releaseCompiledSqlIfNotInCache();
mDatabase.releaseReference();
mDatabase.removeSQLiteClosable(this);
}
@Override
protected void onAllReferencesReleasedFromContainer() {
releaseCompiledSqlIfNotInCache();
mDatabase.releaseReference();
}
private void releaseCompiledSqlIfNotInCache() {
if (mCompiledSql == null) {
return;
}
synchronized(mDatabase.mCompiledQueries) {
if (!mDatabase.mCompiledQueries.containsValue(mCompiledSql)) {
// it is NOT in compiled-sql cache. i.e., responsibility of
// releasing this statement is on me.
mCompiledSql.releaseSqlStatement();
mCompiledSql = null;
nStatement = 0;
} else {
// it is in compiled-sql cache. reset its CompiledSql#mInUse flag
mCompiledSql.release();
}
}
}
/**
* Returns a unique identifier for this program.
*
* @return a unique identifier for this program
*/
public final int getUniqueId() {
return nStatement;
}
/* package */ String getSqlString() {
return mSql;
}
/**
* @deprecated This method is deprecated and must not be used.
*
* @param sql the SQL string to compile
* @param forceCompilation forces the SQL to be recompiled in the event that there is an
* existing compiled SQL program already around
*/
@Deprecated
protected void compile(String sql, boolean forceCompilation) {
// TODO is there a need for this?
}
/**
* Bind a NULL value to this statement. The value remains bound until
* {@link #clearBindings} is called.
*
* @param index The 1-based index to the parameter to bind null to
*/
public void bindNull(int index) {
if (!mDatabase.isOpen()) {
throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
}
acquireReference();
try {
native_bind_null(index);
} finally {
releaseReference();
}
}
/**
* Bind a long value to this statement. The value remains bound until
* {@link #clearBindings} is called.
*
* @param index The 1-based index to the parameter to bind
* @param value The value to bind
*/
public void bindLong(int index, long value) {
if (!mDatabase.isOpen()) {
throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
}
acquireReference();
try {
native_bind_long(index, value);
} finally {
releaseReference();
}
}
/**
* Bind a double value to this statement. The value remains bound until
* {@link #clearBindings} is called.
*
* @param index The 1-based index to the parameter to bind
* @param value The value to bind
*/
public void bindDouble(int index, double value) {
if (!mDatabase.isOpen()) {
throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
}
acquireReference();
try {
native_bind_double(index, value);
} finally {
releaseReference();
}
}
/**
* Bind a String value to this statement. The value remains bound until
* {@link #clearBindings} is called.
*
* @param index The 1-based index to the parameter to bind
* @param value The value to bind
*/
public void bindString(int index, String value) {
if (value == null) {
throw new IllegalArgumentException("the bind value at index " + index + " is null");
}
if (!mDatabase.isOpen()) {
throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
}
acquireReference();
try {
native_bind_string(index, value);
} finally {
releaseReference();
}
}
/**
* Bind a byte array value to this statement. The value remains bound until
* {@link #clearBindings} is called.
*
* @param index The 1-based index to the parameter to bind
* @param value The value to bind
*/
public void bindBlob(int index, byte[] value) {
if (value == null) {
throw new IllegalArgumentException("the bind value at index " + index + " is null");
}
if (!mDatabase.isOpen()) {
throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
}
acquireReference();
try {
native_bind_blob(index, value);
} finally {
releaseReference();
}
}
/**
* Clears all existing bindings. Unset bindings are treated as NULL.
*/
public void clearBindings() {
if (!mDatabase.isOpen()) {
throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
}
acquireReference();
try {
native_clear_bindings();
} finally {
releaseReference();
}
}
/**
* Release this program's resources, making it invalid.
*/
public void close() {
if (!mDatabase.isOpen()) {
return;
}
mDatabase.lock();
try {
releaseReference();
} finally {
mDatabase.unlock();
}
}
/**
* @deprecated This method is deprecated and must not be used.
* Compiles SQL into a SQLite program.
*
* <P>The database lock must be held when calling this method.
* @param sql The SQL to compile.
*/
@Deprecated
protected final native void native_compile(String sql);
/**
* @deprecated This method is deprecated and must not be used.
*/
@Deprecated
protected final native void native_finalize();
protected final native void native_bind_null(int index);
protected final native void native_bind_long(int index, long value);
protected final native void native_bind_double(int index, double value);
protected final native void native_bind_string(int index, String value);
protected final native void native_bind_blob(int index, byte[] value);
private final native void native_clear_bindings();
}
@@ -0,0 +1,194 @@
/*
* 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 android.database.sqlite;
import android.database.CursorWindow;
import android.os.SystemClock;
import android.util.Log;
/**
* A SQLite program that represents a query that reads the resulting rows into a CursorWindow.
* This class is used by SQLiteCursor and isn't useful itself.
*
* SQLiteQuery is not internally synchronized so code using a SQLiteQuery from multiple
* threads should perform its own synchronization when using the SQLiteQuery.
*/
public class SQLiteQuery extends SQLiteProgram {
private static final String TAG = "Cursor";
/** The index of the unbound OFFSET parameter */
private int mOffsetIndex;
/** Args to bind on requery */
private String[] mBindArgs;
private boolean mClosed = false;
/**
* Create a persistent query object.
*
* @param db The database that this query object is associated with
* @param query The SQL string for this query.
* @param offsetIndex The 1-based index to the OFFSET parameter,
*/
/* package */ SQLiteQuery(SQLiteDatabase db, String query, int offsetIndex, String[] bindArgs) {
super(db, query);
mOffsetIndex = offsetIndex;
mBindArgs = bindArgs;
}
/**
* Reads rows into a buffer. This method acquires the database lock.
*
* @param window The window to fill into
* @return number of total rows in the query
*/
/* package */ int fillWindow(CursorWindow window,
int maxRead, int lastPos) {
long timeStart = SystemClock.uptimeMillis();
mDatabase.lock();
mDatabase.logTimeStat(mSql, timeStart, SQLiteDatabase.GET_LOCK_LOG_PREFIX);
try {
acquireReference();
try {
window.acquireReference();
// 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,
maxRead, lastPos);
// Logging
if (SQLiteDebug.DEBUG_SQL_STATEMENTS) {
Log.d(TAG, "fillWindow(): " + mSql);
}
mDatabase.logTimeStat(mSql, timeStart);
return numRows;
} catch (IllegalStateException e){
// simply ignore it
return 0;
} catch (SQLiteDatabaseCorruptException e) {
mDatabase.onCorruption();
throw e;
} finally {
window.releaseReference();
}
} finally {
releaseReference();
mDatabase.unlock();
}
}
/**
* Get the column count for the statement. Only valid on query based
* statements. The database must be locked
* when calling this method.
*
* @return The number of column in the statement's result set.
*/
/* package */ int columnCountLocked() {
acquireReference();
try {
return native_column_count();
} finally {
releaseReference();
}
}
/**
* Retrieves the column name for the given column index. The database must be locked
* when calling this method.
*
* @param columnIndex the index of the column to get the name for
* @return The requested column's name
*/
/* package */ String columnNameLocked(int columnIndex) {
acquireReference();
try {
return native_column_name(columnIndex);
} finally {
releaseReference();
}
}
@Override
public String toString() {
return "SQLiteQuery: " + mSql;
}
@Override
public void close() {
super.close();
mClosed = true;
}
/**
* Called by SQLiteCursor when it is requeried.
*/
/* package */ void requery() {
if (mBindArgs != null) {
int len = mBindArgs.length;
try {
for (int i = 0; i < len; i++) {
super.bindString(i + 1, mBindArgs[i]);
}
} catch (SQLiteMisuseException e) {
StringBuilder errMsg = new StringBuilder("mSql " + mSql);
for (int i = 0; i < len; i++) {
errMsg.append(" ");
errMsg.append(mBindArgs[i]);
}
errMsg.append(" ");
IllegalStateException leakProgram = new IllegalStateException(
errMsg.toString(), e);
throw leakProgram;
}
}
}
@Override
public void bindNull(int index) {
mBindArgs[index - 1] = null;
if (!mClosed) super.bindNull(index);
}
@Override
public void bindLong(int index, long value) {
mBindArgs[index - 1] = Long.toString(value);
if (!mClosed) super.bindLong(index, value);
}
@Override
public void bindDouble(int index, double value) {
mBindArgs[index - 1] = Double.toString(value);
if (!mClosed) super.bindDouble(index, value);
}
@Override
public void bindString(int index, String value) {
mBindArgs[index - 1] = value;
if (!mClosed) super.bindString(index, value);
}
private final native int native_fill_window(CursorWindow window,
int startPos, int offsetParam, int maxRead, int lastPos);
private final native int native_column_count();
private final native String native_column_name(int columnIndex);
}
@@ -0,0 +1,550 @@
/*
* 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 android.database.sqlite;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.provider.BaseColumns;
import android.text.TextUtils;
import android.util.Log;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.regex.Pattern;
/**
* This is a convience class that helps build SQL queries to be sent to
* {@link SQLiteDatabase} objects.
*/
public class SQLiteQueryBuilder
{
private static final String TAG = "SQLiteQueryBuilder";
private static final Pattern sLimitPattern =
Pattern.compile("\\s*\\d+\\s*(,\\s*\\d+\\s*)?");
private Map<String, String> mProjectionMap = null;
private String mTables = "";
private StringBuilder mWhereClause = null; // lazily created
private boolean mDistinct;
private SQLiteDatabase.CursorFactory mFactory;
private boolean mStrictProjectionMap;
public SQLiteQueryBuilder() {
mDistinct = false;
mFactory = null;
}
/**
* Mark the query as DISTINCT.
*
* @param distinct if true the query is DISTINCT, otherwise it isn't
*/
public void setDistinct(boolean distinct) {
mDistinct = distinct;
}
/**
* Returns the list of tables being queried
*
* @return the list of tables being queried
*/
public String getTables() {
return mTables;
}
/**
* Sets the list of tables to query. Multiple tables can be specified to perform a join.
* For example:
* setTables("foo, bar")
* setTables("foo LEFT OUTER JOIN bar ON (foo.id = bar.foo_id)")
*
* @param inTables the list of tables to query on
*/
public void setTables(String inTables) {
mTables = inTables;
}
/**
* Append a chunk to the WHERE clause of the query. All chunks appended are surrounded
* by parenthesis and ANDed with the selection passed to {@link #query}. The final
* WHERE clause looks like:
*
* WHERE (&lt;append chunk 1>&lt;append chunk2>) AND (&lt;query() selection parameter>)
*
* @param inWhere the chunk of text to append to the WHERE clause.
*/
public void appendWhere(CharSequence inWhere) {
if (mWhereClause == null) {
mWhereClause = new StringBuilder(inWhere.length() + 16);
}
if (mWhereClause.length() == 0) {
mWhereClause.append('(');
}
mWhereClause.append(inWhere);
}
/**
* Append a chunk to the WHERE clause of the query. All chunks appended are surrounded
* by parenthesis and ANDed with the selection passed to {@link #query}. The final
* WHERE clause looks like:
*
* WHERE (&lt;append chunk 1>&lt;append chunk2>) AND (&lt;query() selection parameter>)
*
* @param inWhere the chunk of text to append to the WHERE clause. it will be escaped
* to avoid SQL injection attacks
*/
public void appendWhereEscapeString(String inWhere) {
if (mWhereClause == null) {
mWhereClause = new StringBuilder(inWhere.length() + 16);
}
if (mWhereClause.length() == 0) {
mWhereClause.append('(');
}
DatabaseUtils.appendEscapedSQLString(mWhereClause, inWhere);
}
/**
* Sets the projection map for the query. The projection map maps
* from column names that the caller passes into query to database
* column names. This is useful for renaming columns as well as
* disambiguating column names when doing joins. For example you
* could map "name" to "people.name". If a projection map is set
* it must contain all column names the user may request, even if
* the key and value are the same.
*
* @param columnMap maps from the user column names to the database column names
*/
public void setProjectionMap(Map<String, String> columnMap) {
mProjectionMap = columnMap;
}
/**
* Sets the cursor factory to be used for the query. You can use
* one factory for all queries on a database but it is normally
* easier to specify the factory when doing this query. @param
* factory the factor to use
*/
public void setCursorFactory(SQLiteDatabase.CursorFactory factory) {
mFactory = factory;
}
/**
* @hide
*/
public void setStrictProjectionMap(boolean flag) {
mStrictProjectionMap = flag;
}
/**
* Build an SQL query string from the given clauses.
*
* @param distinct true if you want each row to be unique, false otherwise.
* @param tables The table names to compile the query against.
* @param columns A list of which columns to return. Passing null will
* return all columns, which is discouraged to prevent reading
* data from storage that isn't going to be used.
* @param where A filter declaring which rows to return, formatted as an SQL
* WHERE clause (excluding the WHERE itself). Passing null will
* return all rows for the given URL.
* @param groupBy A filter declaring how to group rows, formatted as an SQL
* GROUP BY clause (excluding the GROUP BY itself). Passing null
* will cause the rows to not be grouped.
* @param having A filter declare which row groups to include in the cursor,
* if row grouping is being used, formatted as an SQL HAVING
* clause (excluding the HAVING itself). Passing null will cause
* all row groups to be included, and is required when row
* grouping is not being used.
* @param orderBy How to order the rows, formatted as an SQL ORDER BY clause
* (excluding the ORDER BY itself). Passing null will use the
* default sort order, which may be unordered.
* @param limit Limits the number of rows returned by the query,
* formatted as LIMIT clause. Passing null denotes no LIMIT clause.
* @return the SQL query string
*/
public static String buildQueryString(
boolean distinct, String tables, String[] columns, String where,
String groupBy, String having, String orderBy, String limit) {
if (TextUtils.isEmpty(groupBy) && !TextUtils.isEmpty(having)) {
throw new IllegalArgumentException(
"HAVING clauses are only permitted when using a groupBy clause");
}
if (!TextUtils.isEmpty(limit) && !sLimitPattern.matcher(limit).matches()) {
throw new IllegalArgumentException("invalid LIMIT clauses:" + limit);
}
StringBuilder query = new StringBuilder(120);
query.append("SELECT ");
if (distinct) {
query.append("DISTINCT ");
}
if (columns != null && columns.length != 0) {
appendColumns(query, columns);
} else {
query.append("* ");
}
query.append("FROM ");
query.append(tables);
appendClause(query, " WHERE ", where);
appendClause(query, " GROUP BY ", groupBy);
appendClause(query, " HAVING ", having);
appendClause(query, " ORDER BY ", orderBy);
appendClause(query, " LIMIT ", limit);
return query.toString();
}
private static void appendClause(StringBuilder s, String name, String clause) {
if (!TextUtils.isEmpty(clause)) {
s.append(name);
s.append(clause);
}
}
private static void appendClauseEscapeClause(StringBuilder s, String name, String clause) {
if (!TextUtils.isEmpty(clause)) {
s.append(name);
DatabaseUtils.appendEscapedSQLString(s, clause);
}
}
/**
* Add the names that are non-null in columns to s, separating
* them with commas.
*/
public static void appendColumns(StringBuilder s, String[] columns) {
int n = columns.length;
for (int i = 0; i < n; i++) {
String column = columns[i];
if (column != null) {
if (i > 0) {
s.append(", ");
}
s.append(column);
}
}
s.append(' ');
}
/**
* Perform a query by combining all current settings and the
* information passed into this method.
*
* @param db the database to query on
* @param projectionIn A list of which columns to return. Passing
* null will return all columns, which is discouraged to prevent
* reading data from storage that isn't going to be used.
* @param selection A filter declaring which rows to return,
* formatted as an SQL WHERE clause (excluding the WHERE
* itself). Passing null will return all rows for the given URL.
* @param selectionArgs You may include ?s in selection, which
* will be replaced by the values from selectionArgs, in order
* that they appear in the selection. The values will be bound
* as Strings.
* @param groupBy A filter declaring how to group rows, formatted
* as an SQL GROUP BY clause (excluding the GROUP BY
* itself). Passing null will cause the rows to not be grouped.
* @param having A filter declare which row groups to include in
* the cursor, if row grouping is being used, formatted as an
* SQL HAVING clause (excluding the HAVING itself). Passing
* null will cause all row groups to be included, and is
* required when row grouping is not being used.
* @param sortOrder How to order the rows, formatted as an SQL
* ORDER BY clause (excluding the ORDER BY itself). Passing null
* will use the default sort order, which may be unordered.
* @return a cursor over the result set
* @see android.content.ContentResolver#query(android.net.Uri, String[],
* String, String[], String)
*/
public Cursor query(SQLiteDatabase db, String[] projectionIn,
String selection, String[] selectionArgs, String groupBy,
String having, String sortOrder) {
return query(db, projectionIn, selection, selectionArgs, groupBy, having, sortOrder,
null /* limit */);
}
/**
* Perform a query by combining all current settings and the
* information passed into this method.
*
* @param db the database to query on
* @param projectionIn A list of which columns to return. Passing
* null will return all columns, which is discouraged to prevent
* reading data from storage that isn't going to be used.
* @param selection A filter declaring which rows to return,
* formatted as an SQL WHERE clause (excluding the WHERE
* itself). Passing null will return all rows for the given URL.
* @param selectionArgs You may include ?s in selection, which
* will be replaced by the values from selectionArgs, in order
* that they appear in the selection. The values will be bound
* as Strings.
* @param groupBy A filter declaring how to group rows, formatted
* as an SQL GROUP BY clause (excluding the GROUP BY
* itself). Passing null will cause the rows to not be grouped.
* @param having A filter declare which row groups to include in
* the cursor, if row grouping is being used, formatted as an
* SQL HAVING clause (excluding the HAVING itself). Passing
* null will cause all row groups to be included, and is
* required when row grouping is not being used.
* @param sortOrder How to order the rows, formatted as an SQL
* ORDER BY clause (excluding the ORDER BY itself). Passing null
* will use the default sort order, which may be unordered.
* @param limit Limits the number of rows returned by the query,
* formatted as LIMIT clause. Passing null denotes no LIMIT clause.
* @return a cursor over the result set
* @see android.content.ContentResolver#query(android.net.Uri, String[],
* String, String[], String)
*/
public Cursor query(SQLiteDatabase db, String[] projectionIn,
String selection, String[] selectionArgs, String groupBy,
String having, String sortOrder, String limit) {
if (mTables == null) {
return null;
}
String sql = buildQuery(
projectionIn, selection, selectionArgs, groupBy, having,
sortOrder, limit);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Performing query: " + sql);
}
return db.rawQueryWithFactory(
mFactory, sql, selectionArgs,
SQLiteDatabase.findEditTable(mTables));
}
/**
* Construct a SELECT statement suitable for use in a group of
* SELECT statements that will be joined through UNION operators
* in buildUnionQuery.
*
* @param projectionIn A list of which columns to return. Passing
* null will return all columns, which is discouraged to
* prevent reading data from storage that isn't going to be
* used.
* @param selection A filter declaring which rows to return,
* formatted as an SQL WHERE clause (excluding the WHERE
* itself). Passing null will return all rows for the given
* URL.
* @param selectionArgs You may include ?s in selection, which
* will be replaced by the values from selectionArgs, in order
* that they appear in the selection. The values will be bound
* as Strings.
* @param groupBy A filter declaring how to group rows, formatted
* as an SQL GROUP BY clause (excluding the GROUP BY itself).
* Passing null will cause the rows to not be grouped.
* @param having A filter declare which row groups to include in
* the cursor, if row grouping is being used, formatted as an
* SQL HAVING clause (excluding the HAVING itself). Passing
* null will cause all row groups to be included, and is
* required when row grouping is not being used.
* @param sortOrder How to order the rows, formatted as an SQL
* ORDER BY clause (excluding the ORDER BY itself). Passing null
* will use the default sort order, which may be unordered.
* @param limit Limits the number of rows returned by the query,
* formatted as LIMIT clause. Passing null denotes no LIMIT clause.
* @return the resulting SQL SELECT statement
*/
public String buildQuery(
String[] projectionIn, String selection, String[] selectionArgs,
String groupBy, String having, String sortOrder, String limit) {
String[] projection = computeProjection(projectionIn);
StringBuilder where = new StringBuilder();
boolean hasBaseWhereClause = mWhereClause != null && mWhereClause.length() > 0;
if (hasBaseWhereClause) {
where.append(mWhereClause.toString());
where.append(')');
}
// Tack on the user's selection, if present.
if (selection != null && selection.length() > 0) {
if (hasBaseWhereClause) {
where.append(" AND ");
}
where.append('(');
where.append(selection);
where.append(')');
}
return buildQueryString(
mDistinct, mTables, projection, where.toString(),
groupBy, having, sortOrder, limit);
}
/**
* Construct a SELECT statement suitable for use in a group of
* SELECT statements that will be joined through UNION operators
* in buildUnionQuery.
*
* @param typeDiscriminatorColumn the name of the result column
* whose cells will contain the name of the table from which
* each row was drawn.
* @param unionColumns the names of the columns to appear in the
* result. This may include columns that do not appear in the
* table this SELECT is querying (i.e. mTables), but that do
* appear in one of the other tables in the UNION query that we
* are constructing.
* @param columnsPresentInTable a Set of the names of the columns
* that appear in this table (i.e. in the table whose name is
* mTables). Since columns in unionColumns include columns that
* appear only in other tables, we use this array to distinguish
* which ones actually are present. Other columns will have
* NULL values for results from this subquery.
* @param computedColumnsOffset all columns in unionColumns before
* this index are included under the assumption that they're
* computed and therefore won't appear in columnsPresentInTable,
* e.g. "date * 1000 as normalized_date"
* @param typeDiscriminatorValue the value used for the
* type-discriminator column in this subquery
* @param selection A filter declaring which rows to return,
* formatted as an SQL WHERE clause (excluding the WHERE
* itself). Passing null will return all rows for the given
* URL.
* @param selectionArgs You may include ?s in selection, which
* will be replaced by the values from selectionArgs, in order
* that they appear in the selection. The values will be bound
* as Strings.
* @param groupBy A filter declaring how to group rows, formatted
* as an SQL GROUP BY clause (excluding the GROUP BY itself).
* Passing null will cause the rows to not be grouped.
* @param having A filter declare which row groups to include in
* the cursor, if row grouping is being used, formatted as an
* SQL HAVING clause (excluding the HAVING itself). Passing
* null will cause all row groups to be included, and is
* required when row grouping is not being used.
* @return the resulting SQL SELECT statement
*/
public String buildUnionSubQuery(
String typeDiscriminatorColumn,
String[] unionColumns,
Set<String> columnsPresentInTable,
int computedColumnsOffset,
String typeDiscriminatorValue,
String selection,
String[] selectionArgs,
String groupBy,
String having) {
int unionColumnsCount = unionColumns.length;
String[] projectionIn = new String[unionColumnsCount];
for (int i = 0; i < unionColumnsCount; i++) {
String unionColumn = unionColumns[i];
if (unionColumn.equals(typeDiscriminatorColumn)) {
projectionIn[i] = "'" + typeDiscriminatorValue + "' AS "
+ typeDiscriminatorColumn;
} else if (i <= computedColumnsOffset
|| columnsPresentInTable.contains(unionColumn)) {
projectionIn[i] = unionColumn;
} else {
projectionIn[i] = "NULL AS " + unionColumn;
}
}
return buildQuery(
projectionIn, selection, selectionArgs, groupBy, having,
null /* sortOrder */,
null /* limit */);
}
/**
* Given a set of subqueries, all of which are SELECT statements,
* construct a query that returns the union of what those
* subqueries return.
* @param subQueries an array of SQL SELECT statements, all of
* which must have the same columns as the same positions in
* their results
* @param sortOrder How to order the rows, formatted as an SQL
* ORDER BY clause (excluding the ORDER BY itself). Passing
* null will use the default sort order, which may be unordered.
* @param limit The limit clause, which applies to the entire union result set
*
* @return the resulting SQL SELECT statement
*/
public String buildUnionQuery(String[] subQueries, String sortOrder, String limit) {
StringBuilder query = new StringBuilder(128);
int subQueryCount = subQueries.length;
String unionOperator = mDistinct ? " UNION " : " UNION ALL ";
for (int i = 0; i < subQueryCount; i++) {
if (i > 0) {
query.append(unionOperator);
}
query.append(subQueries[i]);
}
appendClause(query, " ORDER BY ", sortOrder);
appendClause(query, " LIMIT ", limit);
return query.toString();
}
private String[] computeProjection(String[] projectionIn) {
if (projectionIn != null && projectionIn.length > 0) {
if (mProjectionMap != null) {
String[] projection = new String[projectionIn.length];
int length = projectionIn.length;
for (int i = 0; i < length; i++) {
String userColumn = projectionIn[i];
String column = mProjectionMap.get(userColumn);
if (column != null) {
projection[i] = column;
continue;
}
if (!mStrictProjectionMap &&
( userColumn.contains(" AS ") || userColumn.contains(" as "))) {
/* A column alias already exist */
projection[i] = userColumn;
continue;
}
throw new IllegalArgumentException("Invalid column "
+ projectionIn[i]);
}
return projection;
} else {
return projectionIn;
}
} else if (mProjectionMap != null) {
// Return all columns in projection map.
Set<Entry<String, String>> entrySet = mProjectionMap.entrySet();
String[] projection = new String[entrySet.size()];
Iterator<Entry<String, String>> entryIter = entrySet.iterator();
int i = 0;
while (entryIter.hasNext()) {
Entry<String, String> entry = entryIter.next();
// Don't include the _count column when people ask for no projection.
if (entry.getKey().equals(BaseColumns._COUNT)) {
continue;
}
projection[i++] = entry.getValue();
}
return projection;
}
return null;
}
}
@@ -0,0 +1,154 @@
/*
* 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 android.database.sqlite;
import android.os.SystemClock;
import dalvik.system.BlockGuard;
/**
* A pre-compiled statement against a {@link SQLiteDatabase} that can be reused.
* The statement cannot return multiple rows, but 1x1 result sets are allowed.
* Don't use SQLiteStatement constructor directly, please use
* {@link SQLiteDatabase#compileStatement(String)}
*
* SQLiteStatement is not internally synchronized so code using a SQLiteStatement from multiple
* threads should perform its own synchronization when using the SQLiteStatement.
*/
public class SQLiteStatement extends SQLiteProgram
{
/**
* Don't use SQLiteStatement constructor directly, please use
* {@link SQLiteDatabase#compileStatement(String)}
* @param db
* @param sql
*/
/* package */ SQLiteStatement(SQLiteDatabase db, String sql) {
super(db, sql);
}
/**
* Execute this SQL statement, if it is not a query. For example,
* CREATE TABLE, DELTE, INSERT, etc.
*
* @throws android.database.SQLException If the SQL string is invalid for
* some reason
*/
public void execute() {
BlockGuard.getThreadPolicy().onWriteToDisk();
if (!mDatabase.isOpen()) {
throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
}
long timeStart = SystemClock.uptimeMillis();
mDatabase.lock();
acquireReference();
try {
native_execute();
mDatabase.logTimeStat(mSql, timeStart);
} finally {
releaseReference();
mDatabase.unlock();
}
}
/**
* Execute this SQL statement and return the ID of the row inserted due to this call.
* The SQL statement should be an INSERT for this to be a useful call.
*
* @return the row ID of the last row inserted, if this insert is successful. -1 otherwise.
*
* @throws android.database.SQLException If the SQL string is invalid for
* some reason
*/
public long executeInsert() {
BlockGuard.getThreadPolicy().onWriteToDisk();
if (!mDatabase.isOpen()) {
throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
}
long timeStart = SystemClock.uptimeMillis();
mDatabase.lock();
acquireReference();
try {
native_execute();
mDatabase.logTimeStat(mSql, timeStart);
return (mDatabase.lastChangeCount() > 0) ? mDatabase.lastInsertRow() : -1;
} finally {
releaseReference();
mDatabase.unlock();
}
}
/**
* Execute a statement that returns a 1 by 1 table with a numeric value.
* For example, SELECT COUNT(*) FROM table;
*
* @return The result of the query.
*
* @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
*/
public long simpleQueryForLong() {
BlockGuard.getThreadPolicy().onReadFromDisk();
if (!mDatabase.isOpen()) {
throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
}
long timeStart = SystemClock.uptimeMillis();
mDatabase.lock();
acquireReference();
try {
long retValue = native_1x1_long();
mDatabase.logTimeStat(mSql, timeStart);
return retValue;
} finally {
releaseReference();
mDatabase.unlock();
}
}
/**
* Execute a statement that returns a 1 by 1 table with a text value.
* For example, SELECT COUNT(*) FROM table;
*
* @return The result of the query.
*
* @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows
*/
public String simpleQueryForString() {
BlockGuard.getThreadPolicy().onReadFromDisk();
if (!mDatabase.isOpen()) {
throw new IllegalStateException("database " + mDatabase.getPath() + " already closed");
}
long timeStart = SystemClock.uptimeMillis();
mDatabase.lock();
acquireReference();
try {
String retValue = native_1x1_string();
mDatabase.logTimeStat(mSql, timeStart);
return retValue;
} finally {
releaseReference();
mDatabase.unlock();
}
}
private final native void native_execute();
private final native long native_1x1_long();
private final native String native_1x1_string();
}
@@ -0,0 +1,37 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.database.sqlite;
/**
* A listener for transaction events.
*/
public interface SQLiteTransactionListener {
/**
* Called immediately after the transaction begins.
*/
void onBegin();
/**
* Called immediately before commiting the transaction.
*/
void onCommit();
/**
* Called if the transaction is about to be rolled back.
*/
void onRollback();
}
@@ -0,0 +1,108 @@
/*
* Copyright (C) 2008 Esmertec AG.
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.database.sqlite;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.util.Log;
import android.widget.Toast;
/**
* @hide
*/
public final class SqliteWrapper {
private static final String TAG = "SqliteWrapper";
private static final String SQLITE_EXCEPTION_DETAIL_MESSAGE
= "unable to open database file";
private SqliteWrapper() {
// Forbidden being instantiated.
}
// FIXME: need to optimize this method.
private static boolean isLowMemory(SQLiteException e) {
return e.getMessage().equals(SQLITE_EXCEPTION_DETAIL_MESSAGE);
}
public static void checkSQLiteException(Context context, SQLiteException e) {
if (isLowMemory(e)) {
Toast.makeText(context, com.android.internal.R.string.low_memory,
Toast.LENGTH_SHORT).show();
} else {
throw e;
}
}
public static Cursor query(Context context, ContentResolver resolver, Uri uri,
String[] projection, String selection, String[] selectionArgs, String sortOrder) {
try {
return resolver.query(uri, projection, selection, selectionArgs, sortOrder);
} catch (SQLiteException e) {
Log.e(TAG, "Catch a SQLiteException when query: ", e);
checkSQLiteException(context, e);
return null;
}
}
public static boolean requery(Context context, Cursor cursor) {
try {
return cursor.requery();
} catch (SQLiteException e) {
Log.e(TAG, "Catch a SQLiteException when requery: ", e);
checkSQLiteException(context, e);
return false;
}
}
public static int update(Context context, ContentResolver resolver, Uri uri,
ContentValues values, String where, String[] selectionArgs) {
try {
return resolver.update(uri, values, where, selectionArgs);
} catch (SQLiteException e) {
Log.e(TAG, "Catch a SQLiteException when update: ", e);
checkSQLiteException(context, e);
return -1;
}
}
public static int delete(Context context, ContentResolver resolver, Uri uri,
String where, String[] selectionArgs) {
try {
return resolver.delete(uri, where, selectionArgs);
} catch (SQLiteException e) {
Log.e(TAG, "Catch a SQLiteException when delete: ", e);
checkSQLiteException(context, e);
return -1;
}
}
public static Uri insert(Context context, ContentResolver resolver,
Uri uri, ContentValues values) {
try {
return resolver.insert(uri, values);
} catch (SQLiteException e) {
Log.e(TAG, "Catch a SQLiteException when insert: ", e);
checkSQLiteException(context, e);
return null;
}
}
}
@@ -0,0 +1,20 @@
<HTML>
<BODY>
Contains the SQLite database management
classes that an application would use to manage its own private database.
<p>
Applications use these classes to maange private databases. If creating a
content provider, you will probably have to use these classes to create and
manage your own database to store content. See <a
href="{@docRoot}guide/topics/providers/content-providers.html">Content Providers</a> to learn
the conventions for implementing a content provider. See the
NotePadProvider class in the NotePad sample application in the SDK for an
example of a content provider. Android ships with SQLite version 3.4.0
<p>If you are working with data sent to you by a provider, you will not use
these SQLite classes, but instead use the generic {@link android.database}
classes.
<p>Android ships with the sqlite3 database tool in the <code>tools/</code>
folder. You can use this tool to browse or run SQL commands on the device. Run by
typing <code>sqlite3</code> in a shell window.
</BODY>
</HTML>