if (notApplyingBatch) {
}
throws OperationApplicationException {
getDatabase().beginTransaction();
try {
mDb.setTransactionSuccessful();
}
mApplyingBatch.set(true);
if (!isTemporary() && result > 0) {
}
}
if (notApplyingBatch) {
}
Uri result;
/**
// initialize if this is the first time this thread has applied a batch
if (isTemporary() && isSyncStateUri) {
if (mApplyingBatch.get() == null) {
mApplyingBatch.set(false);
result = mSyncState.asContentProvider().insert(uri, values[i]);
} else {
mPendingBatchNotifications.set(new HashSet());
}
if (applyingBatch()) {
throw new IllegalStateException(
result = insertInternal(uri, values[i]);
mDb.yieldIfContended();
}
if (result != null) {
completed++;
}
}
mDb.setTransactionSuccessful();
} finally {
"applyBatch is not reentrant but mApplyingBatch is already set");
mDb.endTransaction();
}
if (!isTemporary() && completed == size) {
public ContentProviderResult[] applyBatch(ContentProviderOperation[] operations)
getContext().getContentResolver().notifyChange(uri, null /* observer */,
changeRequiresLocalSync(uri));
}
return completed;
}
int result = updateInternal(url, values, selection, selectionArgs);
getContext().getContentResolver().notifyChange(url, null /* observer */,
changeRequiresLocalSync(url));
} else {
mPendingBatchNotifications.get().add(url);
}
}
return result;
} finally {
if (notApplyingBatch) {
mDb.endTransaction();
}
}
}
@Override
public final int delete(final Uri url, final String selection,
final String[] selectionArgs) {
mDb = mOpenHelper.getWritableDatabase();
final boolean notApplyingBatch = !applyingBatch();
if (notApplyingBatch) {
mDb.beginTransaction();
}
try {
if (isTemporary() && mSyncState.matches(url)) {
int numRows = mSyncState.asContentProvider().delete(url, selection, selectionArgs);
if (notApplyingBatch) {
mDb.setTransactionSuccessful();
}
return numRows;
}
int result = deleteInternal(url, selection, selectionArgs);
if (notApplyingBatch) {
mDb.setTransactionSuccessful();
}
if (!isTemporary() && result > 0) {
if (notApplyingBatch) {
getContext().getContentResolver().notifyChange(url, null /* observer */,
changeRequiresLocalSync(url));
} else {
mPendingBatchNotifications.get().add(url);
}
}
return result;
} finally {
if (notApplyingBatch) {
mDb.endTransaction();
}
}
}
private boolean applyingBatch() {
return mApplyingBatch.get() != null && mApplyingBatch.get();
}
@Override
public final Uri insert(final Uri url, final ContentValues values) {
mDb = mOpenHelper.getWritableDatabase();
final boolean notApplyingBatch = !applyingBatch();
if (notApplyingBatch) {
mDb.beginTransaction();
}
try {
if (isTemporary() && mSyncState.matches(url)) {
Uri result = mSyncState.asContentProvider().insert(url, values);
if (notApplyingBatch) {
mDb.setTransactionSuccessful();
}
return result;
}
Uri result = insertInternal(url, values);
if (notApplyingBatch) {
mDb.setTransactionSuccessful();
}
if (!isTemporary() && result != null) {
if (notApplyingBatch) {
getContext().getContentResolver().notifyChange(url, null /* observer */,
changeRequiresLocalSync(url));
} else {
mPendingBatchNotifications.get().add(url);
}
}
return result;
} finally {
if (notApplyingBatch) {
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++) {
ContentProviderResult[] results = super.applyBatch(operations);
getDatabase().setTransactionSuccessful();
return results;
} finally {
mApplyingBatch.set(false);
getDatabase().endTransaction();
for (Uri url : mPendingBatchNotifications.get()) {
getContext().getContentResolver().notifyChange(url, null /* observer */,
changeRequiresLocalSync(url));
}
}
}
/**
* 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, Account account) {
if (account == null) {
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 Account 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(
* A helper method to delete all rows whose account is not in the accounts
((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.
*
* 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.
*
* 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.
*
* 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.
*
* 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(Account[] accountsArray) {
Map accounts = Maps.newHashMap();
for (Account account : accountsArray) {
accounts.put(account, false);
}
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
Map tableMap = db.getSyncedTables();
Vector tables = new Vector();
tables.addAll(tableMap.keySet());
tables.addAll(tableMap.values());
db.beginTransaction();
try {
mSyncState.onAccountsChanged(accountsArray);
for (String table : tables) {
deleteRowsForRemovedAccounts(accounts, table);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
* 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
*/
protected void deleteRowsForRemovedAccounts(Map accounts, String table) {
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
Cursor c = db.query(table, sAccountProjection, null, null,
"_sync_account, _sync_account_type", null, null);
try {
while (c.moveToNext()) {
String accountName = c.getString(0);
String accountType = c.getString(1);
if (TextUtils.isEmpty(accountName)) {
continue;
}
Account account = new Account(accountName, accountType);
if (!accounts.containsKey(account)) {
int numDeleted;
numDeleted = db.delete(table, "_sync_account=? AND _sync_account_type=?",
new String[]{account.mName, account.mType});
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(Account account) {
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
Map tableMap = db.getSyncedTables();
ArrayList tables = new ArrayList();
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.mName, account.mType});
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
/**
* Retrieves the SyncData bytes for the given account. The byte array returned may be null.
*/
public byte[] readSyncDataBytes(Account account) {
return mSyncState.readSyncDataBytes(mOpenHelper.getReadableDatabase(), account);
}
/**
* Sets the SyncData bytes for the given account. The byte array may be null.
*/
public void writeSyncDataBytes(Account account, byte[] data) {
mSyncState.writeSyncDataBytes(mOpenHelper.getWritableDatabase(), account, data);
}
} |