summaryrefslogtreecommitdiffstats
path: root/core/java/android/content
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2009-01-15 16:12:10 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2009-01-15 16:12:10 -0800
commit9266c558bf1d21ff647525ff99f7dadbca417309 (patch)
tree1630b1ba80f4793caf39d865528e662bdb1037fe /core/java/android/content
parentb798689749c64baba81f02e10cf2157c747d6b46 (diff)
downloadframeworks_base-9266c558bf1d21ff647525ff99f7dadbca417309.zip
frameworks_base-9266c558bf1d21ff647525ff99f7dadbca417309.tar.gz
frameworks_base-9266c558bf1d21ff647525ff99f7dadbca417309.tar.bz2
auto import from //branches/cupcake/...@126645
Diffstat (limited to 'core/java/android/content')
-rw-r--r--core/java/android/content/AbstractSyncableContentProvider.java601
-rw-r--r--core/java/android/content/AbstractTableMerger.java108
-rw-r--r--core/java/android/content/SyncableContentProvider.java446
3 files changed, 679 insertions, 476 deletions
diff --git a/core/java/android/content/AbstractSyncableContentProvider.java b/core/java/android/content/AbstractSyncableContentProvider.java
new file mode 100644
index 0000000..ce6501c
--- /dev/null
+++ b/core/java/android/content/AbstractSyncableContentProvider.java
@@ -0,0 +1,601 @@
+package android.content;
+
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.Cursor;
+import android.net.Uri;
+import android.accounts.AccountMonitor;
+import android.accounts.AccountMonitorListener;
+import android.provider.SyncConstValue;
+import android.util.Config;
+import android.util.Log;
+import android.os.Bundle;
+import android.text.TextUtils;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Vector;
+import java.util.ArrayList;
+
+/**
+ * A specialization of the ContentProvider that centralizes functionality
+ * used by ContentProviders that are syncable. It also wraps calls to the ContentProvider
+ * inside of database transactions.
+ *
+ * @hide
+ */
+public abstract class AbstractSyncableContentProvider extends SyncableContentProvider {
+ private static final String TAG = "SyncableContentProvider";
+ protected SQLiteOpenHelper mOpenHelper;
+ protected SQLiteDatabase mDb;
+ private final String mDatabaseName;
+ private final int mDatabaseVersion;
+ private final Uri mContentUri;
+ private AccountMonitor mAccountMonitor;
+
+ /** the account set in the last call to onSyncStart() */
+ private String mSyncingAccount;
+
+ private SyncStateContentProviderHelper mSyncState = null;
+
+ private static final String[] sAccountProjection = new String[] {SyncConstValue._SYNC_ACCOUNT};
+
+ private boolean mIsTemporary;
+
+ private AbstractTableMerger mCurrentMerger = null;
+ private boolean mIsMergeCancelled = false;
+
+ private static final String SYNC_ACCOUNT_WHERE_CLAUSE = SyncConstValue._SYNC_ACCOUNT + "=?";
+
+ protected boolean isTemporary() {
+ return mIsTemporary;
+ }
+
+ /**
+ * Indicates whether or not this ContentProvider contains a full
+ * set of data or just diffs. This knowledge comes in handy when
+ * determining how to incorporate the contents of a temporary
+ * provider into a real provider.
+ */
+ private boolean mContainsDiffs;
+
+ /**
+ * Initializes the AbstractSyncableContentProvider
+ * @param dbName the filename of the database
+ * @param dbVersion the current version of the database schema
+ * @param contentUri The base Uri of the syncable content in this provider
+ */
+ public AbstractSyncableContentProvider(String dbName, int dbVersion, Uri contentUri) {
+ super();
+
+ mDatabaseName = dbName;
+ mDatabaseVersion = dbVersion;
+ mContentUri = contentUri;
+ mIsTemporary = false;
+ setContainsDiffs(false);
+ if (Config.LOGV) {
+ Log.v(TAG, "created SyncableContentProvider " + this);
+ }
+ }
+
+ /**
+ * Close resources that must be closed. You must call this to properly release
+ * the resources used by the AbstractSyncableContentProvider.
+ */
+ public void close() {
+ if (mOpenHelper != null) {
+ mOpenHelper.close(); // OK to call .close() repeatedly.
+ }
+ }
+
+ /**
+ * Override to create your schema and do anything else you need to do with a new database.
+ * This is run inside a transaction (so you don't need to use one).
+ * This method may not use getDatabase(), or call content provider methods, it must only
+ * use the database handle passed to it.
+ */
+ protected void bootstrapDatabase(SQLiteDatabase db) {}
+
+ /**
+ * Override to upgrade your database from an old version to the version you specified.
+ * Don't set the DB version; this will automatically be done after the method returns.
+ * This method may not use getDatabase(), or call content provider methods, it must only
+ * use the database handle passed to it.
+ *
+ * @param oldVersion version of the existing database
+ * @param newVersion current version to upgrade to
+ * @return true if the upgrade was lossless, false if it was lossy
+ */
+ protected abstract boolean upgradeDatabase(SQLiteDatabase db, int oldVersion, int newVersion);
+
+ /**
+ * Override to do anything (like cleanups or checks) you need to do after opening a database.
+ * Does nothing by default. This is run inside a transaction (so you don't need to use one).
+ * This method may not use getDatabase(), or call content provider methods, it must only
+ * use the database handle passed to it.
+ */
+ protected void onDatabaseOpened(SQLiteDatabase db) {}
+
+ private class DatabaseHelper extends SQLiteOpenHelper {
+ DatabaseHelper(Context context, String name) {
+ // Note: context and name may be null for temp providers
+ super(context, name, null, mDatabaseVersion);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ bootstrapDatabase(db);
+ mSyncState.createDatabase(db);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ if (!upgradeDatabase(db, oldVersion, newVersion)) {
+ mSyncState.discardSyncData(db, null /* all accounts */);
+ getContext().getContentResolver().startSync(mContentUri, new Bundle());
+ }
+ }
+
+ @Override
+ public void onOpen(SQLiteDatabase db) {
+ onDatabaseOpened(db);
+ mSyncState.onDatabaseOpened(db);
+ }
+ }
+
+ @Override
+ public boolean onCreate() {
+ if (isTemporary()) throw new IllegalStateException("onCreate() called for temp provider");
+ mOpenHelper = new AbstractSyncableContentProvider.DatabaseHelper(getContext(), mDatabaseName);
+ mSyncState = new SyncStateContentProviderHelper(mOpenHelper);
+
+ AccountMonitorListener listener = new AccountMonitorListener() {
+ public void onAccountsUpdated(String[] accounts) {
+ // Some providers override onAccountsChanged(); give them a database to work with.
+ mDb = mOpenHelper.getWritableDatabase();
+ onAccountsChanged(accounts);
+ TempProviderSyncAdapter syncAdapter = (TempProviderSyncAdapter)getSyncAdapter();
+ if (syncAdapter != null) {
+ syncAdapter.onAccountsChanged(accounts);
+ }
+ }
+ };
+ mAccountMonitor = new AccountMonitor(getContext(), listener);
+
+ return true;
+ }
+
+ /**
+ * Get a non-persistent instance of this content provider.
+ * You must call {@link #close} on the returned
+ * SyncableContentProvider when you are done with it.
+ *
+ * @return a non-persistent content provider with the same layout as this
+ * provider.
+ */
+ public AbstractSyncableContentProvider getTemporaryInstance() {
+ AbstractSyncableContentProvider temp;
+ try {
+ temp = getClass().newInstance();
+ } catch (InstantiationException e) {
+ throw new RuntimeException("unable to instantiate class, "
+ + "this should never happen", e);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(
+ "IllegalAccess while instantiating class, "
+ + "this should never happen", e);
+ }
+
+ // Note: onCreate() isn't run for the temp provider, and it has no Context.
+ temp.mIsTemporary = true;
+ temp.setContainsDiffs(true);
+ temp.mOpenHelper = temp.new DatabaseHelper(null, null);
+ temp.mSyncState = new SyncStateContentProviderHelper(temp.mOpenHelper);
+ if (!isTemporary()) {
+ mSyncState.copySyncState(
+ mOpenHelper.getReadableDatabase(),
+ temp.mOpenHelper.getWritableDatabase(),
+ getSyncingAccount());
+ }
+ return temp;
+ }
+
+ public SQLiteDatabase getDatabase() {
+ if (mDb == null) mDb = mOpenHelper.getWritableDatabase();
+ return mDb;
+ }
+
+ public boolean getContainsDiffs() {
+ return mContainsDiffs;
+ }
+
+ public void setContainsDiffs(boolean containsDiffs) {
+ if (containsDiffs && !isTemporary()) {
+ throw new IllegalStateException(
+ "only a temporary provider can contain diffs");
+ }
+ mContainsDiffs = containsDiffs;
+ }
+
+ /**
+ * Each subclass of this class should define a subclass of {@link
+ * android.content.AbstractTableMerger} for each table they wish to merge. It
+ * should then override this method and return one instance of
+ * each merger, in sequence. Their {@link
+ * android.content.AbstractTableMerger#merge merge} methods will be called, one at a
+ * time, in the order supplied.
+ *
+ * <p>The default implementation returns an empty list, so that no
+ * merging will occur.
+ * @return A sequence of subclasses of {@link
+ * android.content.AbstractTableMerger}, one for each table that should be merged.
+ */
+ protected Iterable<? extends AbstractTableMerger> getMergers() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public final int update(final Uri url, final ContentValues values,
+ final String selection, final String[] selectionArgs) {
+ mDb = mOpenHelper.getWritableDatabase();
+ mDb.beginTransaction();
+ try {
+ if (isTemporary() && mSyncState.matches(url)) {
+ int numRows = mSyncState.asContentProvider().update(
+ url, values, selection, selectionArgs);
+ mDb.setTransactionSuccessful();
+ return numRows;
+ }
+
+ int result = updateInternal(url, values, selection, selectionArgs);
+ mDb.setTransactionSuccessful();
+
+ if (!isTemporary() && result > 0) {
+ getContext().getContentResolver().notifyChange(url, null /* observer */,
+ changeRequiresLocalSync(url));
+ }
+
+ return result;
+ } finally {
+ mDb.endTransaction();
+ }
+ }
+
+ @Override
+ public final int delete(final Uri url, final String selection,
+ final String[] selectionArgs) {
+ mDb = mOpenHelper.getWritableDatabase();
+ mDb.beginTransaction();
+ try {
+ if (isTemporary() && mSyncState.matches(url)) {
+ int numRows = mSyncState.asContentProvider().delete(url, selection, selectionArgs);
+ mDb.setTransactionSuccessful();
+ return numRows;
+ }
+ int result = deleteInternal(url, selection, selectionArgs);
+ mDb.setTransactionSuccessful();
+ if (!isTemporary() && result > 0) {
+ getContext().getContentResolver().notifyChange(url, null /* observer */,
+ changeRequiresLocalSync(url));
+ }
+ return result;
+ } finally {
+ mDb.endTransaction();
+ }
+ }
+
+ @Override
+ public final Uri insert(final Uri url, final ContentValues values) {
+ mDb = mOpenHelper.getWritableDatabase();
+ mDb.beginTransaction();
+ try {
+ if (isTemporary() && mSyncState.matches(url)) {
+ Uri result = mSyncState.asContentProvider().insert(url, values);
+ mDb.setTransactionSuccessful();
+ return result;
+ }
+ Uri result = insertInternal(url, values);
+ mDb.setTransactionSuccessful();
+ if (!isTemporary() && result != null) {
+ getContext().getContentResolver().notifyChange(url, null /* observer */,
+ changeRequiresLocalSync(url));
+ }
+ return result;
+ } finally {
+ mDb.endTransaction();
+ }
+ }
+
+ @Override
+ public final int bulkInsert(final Uri uri, final ContentValues[] values) {
+ int size = values.length;
+ int completed = 0;
+ final boolean isSyncStateUri = mSyncState.matches(uri);
+ mDb = mOpenHelper.getWritableDatabase();
+ mDb.beginTransaction();
+ try {
+ for (int i = 0; i < size; i++) {
+ Uri result;
+ if (isTemporary() && isSyncStateUri) {
+ result = mSyncState.asContentProvider().insert(uri, values[i]);
+ } else {
+ result = insertInternal(uri, values[i]);
+ mDb.yieldIfContended();
+ }
+ if (result != null) {
+ completed++;
+ }
+ }
+ mDb.setTransactionSuccessful();
+ } finally {
+ mDb.endTransaction();
+ }
+ if (!isTemporary() && completed == size) {
+ getContext().getContentResolver().notifyChange(uri, null /* observer */,
+ changeRequiresLocalSync(uri));
+ }
+ return completed;
+ }
+
+ /**
+ * Check if changes to this URI can be syncable changes.
+ * @param uri the URI of the resource that was changed
+ * @return true if changes to this URI can be syncable changes, false otherwise
+ */
+ public boolean changeRequiresLocalSync(Uri uri) {
+ return true;
+ }
+
+ @Override
+ public final Cursor query(final Uri url, final String[] projection,
+ final String selection, final String[] selectionArgs,
+ final String sortOrder) {
+ mDb = mOpenHelper.getReadableDatabase();
+ if (isTemporary() && mSyncState.matches(url)) {
+ return mSyncState.asContentProvider().query(
+ url, projection, selection, selectionArgs, sortOrder);
+ }
+ return queryInternal(url, projection, selection, selectionArgs, sortOrder);
+ }
+
+ /**
+ * Called right before a sync is started.
+ *
+ * @param context the sync context for the operation
+ * @param account
+ */
+ public void onSyncStart(SyncContext context, String account) {
+ if (TextUtils.isEmpty(account)) {
+ throw new IllegalArgumentException("you passed in an empty account");
+ }
+ mSyncingAccount = account;
+ }
+
+ /**
+ * Called right after a sync is completed
+ *
+ * @param context the sync context for the operation
+ * @param success true if the sync succeeded, false if an error occurred
+ */
+ public void onSyncStop(SyncContext context, boolean success) {
+ }
+
+ /**
+ * The account of the most recent call to onSyncStart()
+ * @return the account
+ */
+ public String getSyncingAccount() {
+ return mSyncingAccount;
+ }
+
+ /**
+ * Merge diffs from a sync source with this content provider.
+ *
+ * @param context the SyncContext within which this merge is taking place
+ * @param diffs A temporary content provider containing diffs from a sync
+ * source.
+ * @param result a MergeResult that contains information about the merge, including
+ * a temporary content provider with the same layout as this provider containing
+ * @param syncResult
+ */
+ public void merge(SyncContext context, SyncableContentProvider diffs,
+ TempProviderSyncResult result, SyncResult syncResult) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ db.beginTransaction();
+ try {
+ synchronized(this) {
+ mIsMergeCancelled = false;
+ }
+ Iterable<? extends AbstractTableMerger> mergers = getMergers();
+ try {
+ for (AbstractTableMerger merger : mergers) {
+ synchronized(this) {
+ if (mIsMergeCancelled) break;
+ mCurrentMerger = merger;
+ }
+ merger.merge(context, getSyncingAccount(), diffs, result, syncResult, this);
+ }
+ if (mIsMergeCancelled) return;
+ if (diffs != null) {
+ mSyncState.copySyncState(
+ ((AbstractSyncableContentProvider)diffs).mOpenHelper.getReadableDatabase(),
+ mOpenHelper.getWritableDatabase(),
+ getSyncingAccount());
+ }
+ } finally {
+ synchronized (this) {
+ mCurrentMerger = null;
+ }
+ }
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+
+ /**
+ * Invoked when the active sync has been canceled. Sets the sync state of this provider and
+ * its merger to canceled.
+ */
+ public void onSyncCanceled() {
+ synchronized (this) {
+ mIsMergeCancelled = true;
+ if (mCurrentMerger != null) {
+ mCurrentMerger.onMergeCancelled();
+ }
+ }
+ }
+
+
+ public boolean isMergeCancelled() {
+ return mIsMergeCancelled;
+ }
+
+ /**
+ * Subclasses should override this instead of update(). See update()
+ * for details.
+ *
+ * <p> This method is called within a acquireDbLock()/releaseDbLock() block,
+ * which means a database transaction will be active during the call;
+ */
+ protected abstract int updateInternal(Uri url, ContentValues values,
+ String selection, String[] selectionArgs);
+
+ /**
+ * Subclasses should override this instead of delete(). See delete()
+ * for details.
+ *
+ * <p> This method is called within a acquireDbLock()/releaseDbLock() block,
+ * which means a database transaction will be active during the call;
+ */
+ protected abstract int deleteInternal(Uri url, String selection, String[] selectionArgs);
+
+ /**
+ * Subclasses should override this instead of insert(). See insert()
+ * for details.
+ *
+ * <p> This method is called within a acquireDbLock()/releaseDbLock() block,
+ * which means a database transaction will be active during the call;
+ */
+ protected abstract Uri insertInternal(Uri url, ContentValues values);
+
+ /**
+ * Subclasses should override this instead of query(). See query()
+ * for details.
+ *
+ * <p> This method is *not* called within a acquireDbLock()/releaseDbLock()
+ * block for performance reasons. If an implementation needs atomic access
+ * to the database the lock can be acquired then.
+ */
+ protected abstract Cursor queryInternal(Uri url, String[] projection,
+ String selection, String[] selectionArgs, String sortOrder);
+
+ /**
+ * Make sure that there are no entries for accounts that no longer exist
+ * @param accountsArray the array of currently-existing accounts
+ */
+ protected void onAccountsChanged(String[] accountsArray) {
+ Map<String, Boolean> accounts = new HashMap<String, Boolean>();
+ for (String account : accountsArray) {
+ accounts.put(account, false);
+ }
+ accounts.put(SyncConstValue.NON_SYNCABLE_ACCOUNT, false);
+
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ Map<String, String> tableMap = db.getSyncedTables();
+ Vector<String> tables = new Vector<String>();
+ tables.addAll(tableMap.keySet());
+ tables.addAll(tableMap.values());
+
+ db.beginTransaction();
+ try {
+ mSyncState.onAccountsChanged(accountsArray);
+ for (String table : tables) {
+ deleteRowsForRemovedAccounts(accounts, table,
+ SyncConstValue._SYNC_ACCOUNT);
+ }
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ /**
+ * A helper method to delete all rows whose account is not in the accounts
+ * map. The accountColumnName is the name of the column that is expected
+ * to hold the account. If a row has an empty account it is never deleted.
+ *
+ * @param accounts a map of existing accounts
+ * @param table the table to delete from
+ * @param accountColumnName the name of the column that is expected
+ * to hold the account.
+ */
+ protected void deleteRowsForRemovedAccounts(Map<String, Boolean> accounts,
+ String table, String accountColumnName) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ Cursor c = db.query(table, sAccountProjection, null, null,
+ accountColumnName, null, null);
+ try {
+ while (c.moveToNext()) {
+ String account = c.getString(0);
+ if (TextUtils.isEmpty(account)) {
+ continue;
+ }
+ if (!accounts.containsKey(account)) {
+ int numDeleted;
+ numDeleted = db.delete(table, accountColumnName + "=?", new String[]{account});
+ if (Config.LOGV) {
+ Log.v(TAG, "deleted " + numDeleted
+ + " records from table " + table
+ + " for account " + account);
+ }
+ }
+ }
+ } finally {
+ c.close();
+ }
+ }
+
+ /**
+ * Called when the sync system determines that this provider should no longer
+ * contain records for the specified account.
+ */
+ public void wipeAccount(String account) {
+ SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+ Map<String, String> tableMap = db.getSyncedTables();
+ ArrayList<String> tables = new ArrayList<String>();
+ tables.addAll(tableMap.keySet());
+ tables.addAll(tableMap.values());
+
+ db.beginTransaction();
+
+ try {
+ // remove the SyncState data
+ mSyncState.discardSyncData(db, account);
+
+ // remove the data in the synced tables
+ for (String table : tables) {
+ db.delete(table, SYNC_ACCOUNT_WHERE_CLAUSE, new String[]{account});
+ }
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ /**
+ * Retrieves the SyncData bytes for the given account. The byte array returned may be null.
+ */
+ public byte[] readSyncDataBytes(String account) {
+ return mSyncState.readSyncDataBytes(mOpenHelper.getReadableDatabase(), account);
+ }
+
+ /**
+ * Sets the SyncData bytes for the given account. The byte array may be null.
+ */
+ public void writeSyncDataBytes(String account, byte[] data) {
+ mSyncState.writeSyncDataBytes(mOpenHelper.getWritableDatabase(), account, data);
+ }
+}
diff --git a/core/java/android/content/AbstractTableMerger.java b/core/java/android/content/AbstractTableMerger.java
index 5511ff6..700f1d8 100644
--- a/core/java/android/content/AbstractTableMerger.java
+++ b/core/java/android/content/AbstractTableMerger.java
@@ -21,12 +21,8 @@ import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.Debug;
-import static android.provider.SyncConstValue._SYNC_ACCOUNT;
-import static android.provider.SyncConstValue._SYNC_DIRTY;
-import static android.provider.SyncConstValue._SYNC_ID;
-import static android.provider.SyncConstValue._SYNC_LOCAL_ID;
-import static android.provider.SyncConstValue._SYNC_MARK;
-import static android.provider.SyncConstValue._SYNC_VERSION;
+import android.provider.BaseColumns;
+import static android.provider.SyncConstValue.*;
import android.text.TextUtils;
import android.util.Log;
@@ -53,7 +49,7 @@ public abstract class AbstractTableMerger
private static final String TAG = "AbstractTableMerger";
private static final String[] syncDirtyProjection =
- new String[] {_SYNC_DIRTY, "_id", _SYNC_ID, _SYNC_VERSION};
+ new String[] {_SYNC_DIRTY, BaseColumns._ID, _SYNC_ID, _SYNC_VERSION};
private static final String[] syncIdAndVersionProjection =
new String[] {_SYNC_ID, _SYNC_VERSION};
@@ -61,8 +57,9 @@ public abstract class AbstractTableMerger
private static final String SELECT_MARKED = _SYNC_MARK + "> 0 and " + _SYNC_ACCOUNT + "=?";
- private static final String SELECT_BY_ID_AND_ACCOUNT =
+ private static final String SELECT_BY_SYNC_ID_AND_ACCOUNT =
_SYNC_ID +"=? and " + _SYNC_ACCOUNT + "=?";
+ private static final String SELECT_BY_ID = BaseColumns._ID +"=?";
private static final String SELECT_UNSYNCED = ""
+ _SYNC_DIRTY + " > 0 and (" + _SYNC_ACCOUNT + "=? or " + _SYNC_ACCOUNT + " is null)";
@@ -90,7 +87,8 @@ public abstract class AbstractTableMerger
* This is called when it is determined that a row should be deleted from the
* ContentProvider. The localCursor is on a table from the local ContentProvider
* and its current position is of the row that should be deleted. The localCursor
- * contains the complete projection of the table.
+ * is only guaranteed to contain the BaseColumns.ID column so the implementation
+ * of deleteRow() must query the database directly if other columns are needed.
* <p>
* It is the responsibility of the implementation of this method to ensure that the cursor
* points to the next row when this method returns, either by calling Cursor.deleteRow() or
@@ -153,7 +151,10 @@ public abstract class AbstractTableMerger
if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "merge complete");
}
- private void mergeServerDiffs(SyncContext context,
+ /**
+ * @hide this is public for testing purposes only
+ */
+ public void mergeServerDiffs(SyncContext context,
String account, SyncableContentProvider serverDiffs, SyncResult syncResult) {
boolean diffsArePartial = serverDiffs.getContainsDiffs();
// mark the current rows so that we can distinguish these from new
@@ -202,7 +203,7 @@ public abstract class AbstractTableMerger
mDb.yieldIfContended();
String serverSyncId = diffsCursor.getString(serverSyncIDColumn);
String serverSyncVersion = diffsCursor.getString(serverSyncVersionColumn);
- long localPersonID = 0;
+ long localRowId = 0;
String localSyncVersion = null;
diffsCount++;
@@ -316,7 +317,7 @@ public abstract class AbstractTableMerger
" that matches the server _sync_id");
}
localSyncDirty = localCursor.getInt(0) != 0;
- localPersonID = localCursor.getLong(1);
+ localRowId = localCursor.getLong(1);
localSyncVersion = localCursor.getString(3);
localCursor.moveToNext();
}
@@ -345,23 +346,20 @@ public abstract class AbstractTableMerger
continue;
}
- // If the _sync_local_id is set and > -1 in the diffsCursor
+ // If the _sync_local_id is present in the diffsCursor
// then this record corresponds to a local record that was just
// inserted into the server and the _sync_local_id is the row id
// of the local record. Set these fields so that the next check
// treats this record as an update, which will allow the
// merger to update the record with the server's sync id
- long serverLocalSyncId =
- diffsCursor.isNull(serverSyncLocalIdColumn)
- ? -1
- : diffsCursor.getLong(serverSyncLocalIdColumn);
- if (serverLocalSyncId > -1) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "the remote record with sync id "
- + serverSyncId + " has a local sync id, "
- + serverLocalSyncId);
+ if (!diffsCursor.isNull(serverSyncLocalIdColumn)) {
+ localRowId = diffsCursor.getLong(serverSyncLocalIdColumn);
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "the remote record with sync id " + serverSyncId
+ + " has a local sync id, " + localRowId);
+ }
localSyncID = serverSyncId;
localSyncDirty = false;
- localPersonID = serverLocalSyncId;
localSyncVersion = null;
}
@@ -372,12 +370,9 @@ public abstract class AbstractTableMerger
if (recordChanged) {
if (localSyncDirty) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG,
- "remote record " +
- serverSyncId +
- " conflicts with local _sync_id " +
- localSyncID + ", local _id " +
- localPersonID);
+ Log.v(TAG, "remote record " + serverSyncId
+ + " conflicts with local _sync_id " + localSyncID
+ + ", local _id " + localRowId);
}
conflict = true;
} else {
@@ -387,7 +382,7 @@ public abstract class AbstractTableMerger
serverSyncId +
" updates local _sync_id " +
localSyncID + ", local _id " +
- localPersonID);
+ localRowId);
}
update = true;
}
@@ -395,18 +390,16 @@ public abstract class AbstractTableMerger
} else {
// the local db doesn't know about this record so add it
if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "remote record "
- + serverSyncId + " is new, inserting");
+ Log.v(TAG, "remote record " + serverSyncId + " is new, inserting");
}
insert = true;
}
if (update) {
- updateRow(localPersonID, serverDiffs, diffsCursor);
+ updateRow(localRowId, serverDiffs, diffsCursor);
syncResult.stats.numUpdates++;
} else if (conflict) {
- resolveRow(localPersonID, serverSyncId, serverDiffs,
- diffsCursor);
+ resolveRow(localRowId, serverSyncId, serverDiffs, diffsCursor);
syncResult.stats.numUpdates++;
} else if (insert) {
insertRow(serverDiffs, diffsCursor);
@@ -414,16 +407,16 @@ public abstract class AbstractTableMerger
}
}
- if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "processed " + diffsCount +
- " server entries");
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "processed " + diffsCount + " server entries");
+ }
// If tombstones aren't in use delete any remaining local rows that
// don't have corresponding server rows. Keep the rows that don't
// have a sync id since those were created locally and haven't been
// synced to the server yet.
if (!diffsArePartial) {
- while (!localCursor.isAfterLast() &&
- !TextUtils.isEmpty(localCursor.getString(2))) {
+ while (!localCursor.isAfterLast() && !TextUtils.isEmpty(localCursor.getString(2))) {
if (mIsMergeCancelled) {
localCursor.deactivate();
deletedCursor.deactivate();
@@ -458,7 +451,6 @@ public abstract class AbstractTableMerger
// Apply deletions from the server
if (mDeletedTableURL != null) {
diffsCursor = serverDiffs.query(mDeletedTableURL, null, null, null, null);
- serverSyncIDColumn = diffsCursor.getColumnIndexOrThrow(_SYNC_ID);
while (diffsCursor.moveToNext()) {
if (mIsMergeCancelled) {
@@ -466,19 +458,31 @@ public abstract class AbstractTableMerger
return;
}
// delete all rows that match each element in the diffsCursor
- fullyDeleteRowsWithSyncId(diffsCursor.getString(serverSyncIDColumn), account,
- syncResult);
+ fullyDeleteMatchingRows(diffsCursor, account, syncResult);
mDb.yieldIfContended();
}
diffsCursor.deactivate();
}
}
- private void fullyDeleteRowsWithSyncId(String syncId, String account, SyncResult syncResult) {
- final String[] selectionArgs = new String[]{syncId, account};
+ private void fullyDeleteMatchingRows(Cursor diffsCursor, String account,
+ SyncResult syncResult) {
+ int serverSyncIdColumn = diffsCursor.getColumnIndexOrThrow(_SYNC_ID);
+ final boolean deleteBySyncId = !diffsCursor.isNull(serverSyncIdColumn);
+
// delete the rows explicitly so that the delete operation can be overridden
- Cursor c = mDb.query(mTable, getDeleteRowProjection(), SELECT_BY_ID_AND_ACCOUNT,
- selectionArgs, null, null, null);
+ final Cursor c;
+ final String[] selectionArgs;
+ if (deleteBySyncId) {
+ selectionArgs = new String[]{diffsCursor.getString(serverSyncIdColumn), account};
+ c = mDb.query(mTable, new String[]{BaseColumns._ID}, SELECT_BY_SYNC_ID_AND_ACCOUNT,
+ selectionArgs, null, null, null);
+ } else {
+ int serverSyncLocalIdColumn = diffsCursor.getColumnIndexOrThrow(_SYNC_LOCAL_ID);
+ selectionArgs = new String[]{diffsCursor.getString(serverSyncLocalIdColumn)};
+ c = mDb.query(mTable, new String[]{BaseColumns._ID}, SELECT_BY_ID, selectionArgs,
+ null, null, null);
+ }
try {
c.moveToFirst();
while (!c.isAfterLast()) {
@@ -488,22 +492,12 @@ public abstract class AbstractTableMerger
} finally {
c.deactivate();
}
- if (mDeletedTable != null) {
- mDb.delete(mDeletedTable, SELECT_BY_ID_AND_ACCOUNT, selectionArgs);
+ if (deleteBySyncId && mDeletedTable != null) {
+ mDb.delete(mDeletedTable, SELECT_BY_SYNC_ID_AND_ACCOUNT, selectionArgs);
}
}
/**
- * Provides the projection used by
- * {@link AbstractTableMerger#deleteRow(android.database.Cursor)}.
- * This should be overridden if the deleteRow implementation requires
- * additional columns.
- */
- protected String[] getDeleteRowProjection() {
- return new String[]{"_id"};
- }
-
- /**
* Converts cursor into a Map, using the correct types for the values.
*/
protected void cursorRowToContentValues(Cursor cursor, ContentValues map) {
diff --git a/core/java/android/content/SyncableContentProvider.java b/core/java/android/content/SyncableContentProvider.java
index 1e55e27..e0cd786 100644
--- a/core/java/android/content/SyncableContentProvider.java
+++ b/core/java/android/content/SyncableContentProvider.java
@@ -16,23 +16,11 @@
package android.content;
-import android.accounts.AccountMonitor;
-import android.accounts.AccountMonitorListener;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
-import android.provider.SyncConstValue;
-import android.text.TextUtils;
-import android.util.Config;
-import android.util.Log;
-import android.os.Bundle;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
+
import java.util.Map;
-import java.util.Vector;
/**
* A specialization of the ContentProvider that centralizes functionality
@@ -42,68 +30,13 @@ import java.util.Vector;
* @hide
*/
public abstract class SyncableContentProvider extends ContentProvider {
- private static final String TAG = "SyncableContentProvider";
- protected SQLiteOpenHelper mOpenHelper;
- protected SQLiteDatabase mDb;
- private final String mDatabaseName;
- private final int mDatabaseVersion;
- private final Uri mContentUri;
- private AccountMonitor mAccountMonitor;
-
- /** the account set in the last call to onSyncStart() */
- private String mSyncingAccount;
-
- private SyncStateContentProviderHelper mSyncState = null;
-
- private static final String[] sAccountProjection = new String[] {SyncConstValue._SYNC_ACCOUNT};
-
- private boolean mIsTemporary;
-
- private AbstractTableMerger mCurrentMerger = null;
- private boolean mIsMergeCancelled = false;
-
- private static final String SYNC_ACCOUNT_WHERE_CLAUSE = SyncConstValue._SYNC_ACCOUNT + "=?";
-
- protected boolean isTemporary() {
- return mIsTemporary;
- }
-
- /**
- * Indicates whether or not this ContentProvider contains a full
- * set of data or just diffs. This knowledge comes in handy when
- * determining how to incorporate the contents of a temporary
- * provider into a real provider.
- */
- private boolean mContainsDiffs;
-
- /**
- * Initializes the SyncableContentProvider
- * @param dbName the filename of the database
- * @param dbVersion the current version of the database schema
- * @param contentUri The base Uri of the syncable content in this provider
- */
- public SyncableContentProvider(String dbName, int dbVersion, Uri contentUri) {
- super();
-
- mDatabaseName = dbName;
- mDatabaseVersion = dbVersion;
- mContentUri = contentUri;
- mIsTemporary = false;
- setContainsDiffs(false);
- if (Config.LOGV) {
- Log.v(TAG, "created SyncableContentProvider " + this);
- }
- }
+ protected abstract boolean isTemporary();
/**
* Close resources that must be closed. You must call this to properly release
* the resources used by the SyncableContentProvider.
*/
- public void close() {
- if (mOpenHelper != null) {
- mOpenHelper.close(); // OK to call .close() repeatedly.
- }
- }
+ public abstract void close();
/**
* Override to create your schema and do anything else you need to do with a new database.
@@ -111,7 +44,7 @@ public abstract class SyncableContentProvider extends ContentProvider {
* This method may not use getDatabase(), or call content provider methods, it must only
* use the database handle passed to it.
*/
- protected void bootstrapDatabase(SQLiteDatabase db) {}
+ protected abstract void bootstrapDatabase(SQLiteDatabase db);
/**
* Override to upgrade your database from an old version to the version you specified.
@@ -131,56 +64,7 @@ public abstract class SyncableContentProvider extends ContentProvider {
* This method may not use getDatabase(), or call content provider methods, it must only
* use the database handle passed to it.
*/
- protected void onDatabaseOpened(SQLiteDatabase db) {}
-
- private class DatabaseHelper extends SQLiteOpenHelper {
- DatabaseHelper(Context context, String name) {
- // Note: context and name may be null for temp providers
- super(context, name, null, mDatabaseVersion);
- }
-
- @Override
- public void onCreate(SQLiteDatabase db) {
- bootstrapDatabase(db);
- mSyncState.createDatabase(db);
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- if (!upgradeDatabase(db, oldVersion, newVersion)) {
- mSyncState.discardSyncData(db, null /* all accounts */);
- getContext().getContentResolver().startSync(mContentUri, new Bundle());
- }
- }
-
- @Override
- public void onOpen(SQLiteDatabase db) {
- onDatabaseOpened(db);
- mSyncState.onDatabaseOpened(db);
- }
- }
-
- @Override
- public boolean onCreate() {
- if (isTemporary()) throw new IllegalStateException("onCreate() called for temp provider");
- mOpenHelper = new DatabaseHelper(getContext(), mDatabaseName);
- mSyncState = new SyncStateContentProviderHelper(mOpenHelper);
-
- AccountMonitorListener listener = new AccountMonitorListener() {
- public void onAccountsUpdated(String[] accounts) {
- // Some providers override onAccountsChanged(); give them a database to work with.
- mDb = mOpenHelper.getWritableDatabase();
- onAccountsChanged(accounts);
- TempProviderSyncAdapter syncAdapter = (TempProviderSyncAdapter)getSyncAdapter();
- if (syncAdapter != null) {
- syncAdapter.onAccountsChanged(accounts);
- }
- }
- };
- mAccountMonitor = new AccountMonitor(getContext(), listener);
-
- return true;
- }
+ protected abstract void onDatabaseOpened(SQLiteDatabase db);
/**
* Get a non-persistent instance of this content provider.
@@ -190,49 +74,13 @@ public abstract class SyncableContentProvider extends ContentProvider {
* @return a non-persistent content provider with the same layout as this
* provider.
*/
- public SyncableContentProvider getTemporaryInstance() {
- SyncableContentProvider temp;
- try {
- temp = getClass().newInstance();
- } catch (InstantiationException e) {
- throw new RuntimeException("unable to instantiate class, "
- + "this should never happen", e);
- } catch (IllegalAccessException e) {
- throw new RuntimeException(
- "IllegalAccess while instantiating class, "
- + "this should never happen", e);
- }
-
- // Note: onCreate() isn't run for the temp provider, and it has no Context.
- temp.mIsTemporary = true;
- temp.setContainsDiffs(true);
- temp.mOpenHelper = temp.new DatabaseHelper(null, null);
- temp.mSyncState = new SyncStateContentProviderHelper(temp.mOpenHelper);
- if (!isTemporary()) {
- mSyncState.copySyncState(
- mOpenHelper.getReadableDatabase(),
- temp.mOpenHelper.getWritableDatabase(),
- getSyncingAccount());
- }
- return temp;
- }
-
- public SQLiteDatabase getDatabase() {
- if (mDb == null) mDb = mOpenHelper.getWritableDatabase();
- return mDb;
- }
-
- public boolean getContainsDiffs() {
- return mContainsDiffs;
- }
-
- public void setContainsDiffs(boolean containsDiffs) {
- if (containsDiffs && !isTemporary()) {
- throw new IllegalStateException(
- "only a temporary provider can contain diffs");
- }
- mContainsDiffs = containsDiffs;
- }
+ public abstract SyncableContentProvider getTemporaryInstance();
+
+ public abstract SQLiteDatabase getDatabase();
+
+ public abstract boolean getContainsDiffs();
+
+ public abstract void setContainsDiffs(boolean containsDiffs);
/**
* Each subclass of this class should define a subclass of {@link
@@ -247,133 +95,14 @@ public abstract class SyncableContentProvider extends ContentProvider {
* @return A sequence of subclasses of {@link
* AbstractTableMerger}, one for each table that should be merged.
*/
- protected Iterable<? extends AbstractTableMerger> getMergers() {
- return Collections.emptyList();
- }
-
- @Override
- public final int update(final Uri url, final ContentValues values,
- final String selection, final String[] selectionArgs) {
- mDb = mOpenHelper.getWritableDatabase();
- mDb.beginTransaction();
- try {
- if (isTemporary() && mSyncState.matches(url)) {
- int numRows = mSyncState.asContentProvider().update(
- url, values, selection, selectionArgs);
- mDb.setTransactionSuccessful();
- return numRows;
- }
-
- int result = updateInternal(url, values, selection, selectionArgs);
- mDb.setTransactionSuccessful();
-
- if (!isTemporary() && result > 0) {
- getContext().getContentResolver().notifyChange(url, null /* observer */,
- changeRequiresLocalSync(url));
- }
-
- return result;
- } finally {
- mDb.endTransaction();
- }
- }
-
- @Override
- public final int delete(final Uri url, final String selection,
- final String[] selectionArgs) {
- mDb = mOpenHelper.getWritableDatabase();
- mDb.beginTransaction();
- try {
- if (isTemporary() && mSyncState.matches(url)) {
- int numRows = mSyncState.asContentProvider().delete(url, selection, selectionArgs);
- mDb.setTransactionSuccessful();
- return numRows;
- }
- int result = deleteInternal(url, selection, selectionArgs);
- mDb.setTransactionSuccessful();
- if (!isTemporary() && result > 0) {
- getContext().getContentResolver().notifyChange(url, null /* observer */,
- changeRequiresLocalSync(url));
- }
- return result;
- } finally {
- mDb.endTransaction();
- }
- }
-
- @Override
- public final Uri insert(final Uri url, final ContentValues values) {
- mDb = mOpenHelper.getWritableDatabase();
- mDb.beginTransaction();
- try {
- if (isTemporary() && mSyncState.matches(url)) {
- Uri result = mSyncState.asContentProvider().insert(url, values);
- mDb.setTransactionSuccessful();
- return result;
- }
- Uri result = insertInternal(url, values);
- mDb.setTransactionSuccessful();
- if (!isTemporary() && result != null) {
- getContext().getContentResolver().notifyChange(url, null /* observer */,
- changeRequiresLocalSync(url));
- }
- return result;
- } finally {
- mDb.endTransaction();
- }
- }
-
- @Override
- public final int bulkInsert(final Uri uri, final ContentValues[] values) {
- int size = values.length;
- int completed = 0;
- final boolean isSyncStateUri = mSyncState.matches(uri);
- mDb = mOpenHelper.getWritableDatabase();
- mDb.beginTransaction();
- try {
- for (int i = 0; i < size; i++) {
- Uri result;
- if (isTemporary() && isSyncStateUri) {
- result = mSyncState.asContentProvider().insert(uri, values[i]);
- } else {
- result = insertInternal(uri, values[i]);
- mDb.yieldIfContended();
- }
- if (result != null) {
- completed++;
- }
- }
- mDb.setTransactionSuccessful();
- } finally {
- mDb.endTransaction();
- }
- if (!isTemporary() && completed == values.length) {
- getContext().getContentResolver().notifyChange(uri, null /* observer */,
- changeRequiresLocalSync(uri));
- }
- return completed;
- }
+ protected abstract Iterable<? extends AbstractTableMerger> getMergers();
/**
* Check if changes to this URI can be syncable changes.
* @param uri the URI of the resource that was changed
* @return true if changes to this URI can be syncable changes, false otherwise
*/
- public boolean changeRequiresLocalSync(Uri uri) {
- return true;
- }
-
- @Override
- public final Cursor query(final Uri url, final String[] projection,
- final String selection, final String[] selectionArgs,
- final String sortOrder) {
- mDb = mOpenHelper.getReadableDatabase();
- if (isTemporary() && mSyncState.matches(url)) {
- return mSyncState.asContentProvider().query(
- url, projection, selection, selectionArgs, sortOrder);
- }
- return queryInternal(url, projection, selection, selectionArgs, sortOrder);
- }
+ public abstract boolean changeRequiresLocalSync(Uri uri);
/**
* Called right before a sync is started.
@@ -381,12 +110,7 @@ public abstract class SyncableContentProvider extends ContentProvider {
* @param context the sync context for the operation
* @param account
*/
- public void onSyncStart(SyncContext context, String account) {
- if (TextUtils.isEmpty(account)) {
- throw new IllegalArgumentException("you passed in an empty account");
- }
- mSyncingAccount = account;
- }
+ public abstract void onSyncStart(SyncContext context, String account);
/**
* Called right after a sync is completed
@@ -394,16 +118,13 @@ public abstract class SyncableContentProvider extends ContentProvider {
* @param context the sync context for the operation
* @param success true if the sync succeeded, false if an error occurred
*/
- public void onSyncStop(SyncContext context, boolean success) {
- }
+ public abstract void onSyncStop(SyncContext context, boolean success);
/**
* The account of the most recent call to onSyncStart()
* @return the account
*/
- public String getSyncingAccount() {
- return mSyncingAccount;
- }
+ public abstract String getSyncingAccount();
/**
* Merge diffs from a sync source with this content provider.
@@ -415,40 +136,8 @@ public abstract class SyncableContentProvider extends ContentProvider {
* a temporary content provider with the same layout as this provider containing
* @param syncResult
*/
- public void merge(SyncContext context, SyncableContentProvider diffs,
- TempProviderSyncResult result, SyncResult syncResult) {
- SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- db.beginTransaction();
- try {
- synchronized(this) {
- mIsMergeCancelled = false;
- }
- Iterable<? extends AbstractTableMerger> mergers = getMergers();
- try {
- for (AbstractTableMerger merger : mergers) {
- synchronized(this) {
- if (mIsMergeCancelled) break;
- mCurrentMerger = merger;
- }
- merger.merge(context, getSyncingAccount(), diffs, result, syncResult, this);
- }
- if (mIsMergeCancelled) return;
- if (diffs != null) {
- mSyncState.copySyncState(
- diffs.mOpenHelper.getReadableDatabase(),
- mOpenHelper.getWritableDatabase(),
- getSyncingAccount());
- }
- } finally {
- synchronized (this) {
- mCurrentMerger = null;
- }
- }
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
- }
+ public abstract void merge(SyncContext context, SyncableContentProvider diffs,
+ TempProviderSyncResult result, SyncResult syncResult);
/**
@@ -457,19 +146,10 @@ public abstract class SyncableContentProvider extends ContentProvider {
* provider is syncable). Subclasses of ContentProvider
* that support canceling of sync should override this.
*/
- public void onSyncCanceled() {
- synchronized (this) {
- mIsMergeCancelled = true;
- if (mCurrentMerger != null) {
- mCurrentMerger.onMergeCancelled();
- }
- }
- }
+ public abstract void onSyncCanceled();
- public boolean isMergeCancelled() {
- return mIsMergeCancelled;
- }
+ public abstract boolean isMergeCancelled();
/**
* Subclasses should override this instead of update(). See update()
@@ -514,31 +194,7 @@ public abstract class SyncableContentProvider extends ContentProvider {
* Make sure that there are no entries for accounts that no longer exist
* @param accountsArray the array of currently-existing accounts
*/
- protected void onAccountsChanged(String[] accountsArray) {
- Map<String, Boolean> accounts = new HashMap<String, Boolean>();
- for (String account : accountsArray) {
- accounts.put(account, false);
- }
- accounts.put(SyncConstValue.NON_SYNCABLE_ACCOUNT, false);
-
- SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- Map<String, String> tableMap = db.getSyncedTables();
- Vector<String> tables = new Vector<String>();
- tables.addAll(tableMap.keySet());
- tables.addAll(tableMap.values());
-
- db.beginTransaction();
- try {
- mSyncState.onAccountsChanged(accountsArray);
- for (String table : tables) {
- deleteRowsForRemovedAccounts(accounts, table,
- SyncConstValue._SYNC_ACCOUNT);
- }
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
- }
+ protected abstract void onAccountsChanged(String[] accountsArray);
/**
* A helper method to delete all rows whose account is not in the accounts
@@ -550,71 +206,23 @@ public abstract class SyncableContentProvider extends ContentProvider {
* @param accountColumnName the name of the column that is expected
* to hold the account.
*/
- protected void deleteRowsForRemovedAccounts(Map<String, Boolean> accounts,
- String table, String accountColumnName) {
- SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- Cursor c = db.query(table, sAccountProjection, null, null,
- accountColumnName, null, null);
- try {
- while (c.moveToNext()) {
- String account = c.getString(0);
- if (TextUtils.isEmpty(account)) {
- continue;
- }
- if (!accounts.containsKey(account)) {
- int numDeleted;
- numDeleted = db.delete(table, accountColumnName + "=?", new String[]{account});
- if (Config.LOGV) {
- Log.v(TAG, "deleted " + numDeleted
- + " records from table " + table
- + " for account " + account);
- }
- }
- }
- } finally {
- c.close();
- }
- }
+ protected abstract void deleteRowsForRemovedAccounts(Map<String, Boolean> accounts,
+ String table, String accountColumnName);
/**
* Called when the sync system determines that this provider should no longer
* contain records for the specified account.
*/
- public void wipeAccount(String account) {
- SQLiteDatabase db = mOpenHelper.getWritableDatabase();
- Map<String, String> tableMap = db.getSyncedTables();
- ArrayList<String> tables = new ArrayList<String>();
- tables.addAll(tableMap.keySet());
- tables.addAll(tableMap.values());
-
- db.beginTransaction();
-
- try {
- // remote the SyncState data
- mSyncState.discardSyncData(db, account);
-
- // remove the data in the synced tables
- for (String table : tables) {
- db.delete(table, SYNC_ACCOUNT_WHERE_CLAUSE, new String[]{account});
- }
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
- }
+ public abstract void wipeAccount(String account);
/**
* Retrieves the SyncData bytes for the given account. The byte array returned may be null.
*/
- public byte[] readSyncDataBytes(String account) {
- return mSyncState.readSyncDataBytes(mOpenHelper.getReadableDatabase(), account);
- }
+ public abstract byte[] readSyncDataBytes(String account);
/**
* Sets the SyncData bytes for the given account. The bytes array may be null.
*/
- public void writeSyncDataBytes(String account, byte[] data) {
- mSyncState.writeSyncDataBytes(mOpenHelper.getWritableDatabase(), account, data);
- }
+ public abstract void writeSyncDataBytes(String account, byte[] data);
}