blob: 538225a0a5eeae3c3097d759f7a781ff8f8d72f6 [file] [log] [blame]
/*
* 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.content;
import android.accounts.Account;
import android.os.Bundle;
import android.os.Process;
import java.util.concurrent.atomic.AtomicInteger;
/**
* An abstract implementation of a SyncAdapter that spawns a thread to invoke a sync operation.
* If a sync operation is already in progress when a startSync() request is received then an error
* will be returned to the new request and the existing request will be allowed to continue.
* When a startSync() is received and there is no sync operation in progress then a thread
* will be started to run the operation and {@link #performSync} will be invoked on that thread.
* If a cancelSync() is received that matches an existing sync operation then the thread
* that is running that sync operation will be interrupted, which will indicate to the thread
* that the sync has been canceled.
*
* @hide
*/
public abstract class AbstractThreadedSyncAdapter {
private final Context mContext;
private final AtomicInteger mNumSyncStarts;
private final ISyncAdapterImpl mISyncAdapterImpl;
// all accesses to this member variable must be synchronized on "this"
private SyncThread mSyncThread;
/** Kernel event log tag. Also listed in data/etc/event-log-tags. */
public static final int LOG_SYNC_DETAILS = 2743;
private final boolean mAutoInitialize;
/**
* Creates an {@link AbstractThreadedSyncAdapter}.
* @param context the {@link android.content.Context} that this is running within.
* @param autoInitialize if true then sync requests that have
* {@link ContentResolver#SYNC_EXTRAS_INITIALIZE} set will be internally handled by
* {@link AbstractThreadedSyncAdapter} by calling
* {@link ContentResolver#setIsSyncable(android.accounts.Account, String, int)} with 1 if it
* is currently set to <0.
*/
public AbstractThreadedSyncAdapter(Context context, boolean autoInitialize) {
mContext = context;
mISyncAdapterImpl = new ISyncAdapterImpl();
mNumSyncStarts = new AtomicInteger(0);
mSyncThread = null;
mAutoInitialize = autoInitialize;
}
class ISyncAdapterImpl extends ISyncAdapter.Stub {
public void startSync(ISyncContext syncContext, String authority, Account account,
Bundle extras) {
final SyncContext syncContextClient = new SyncContext(syncContext);
boolean alreadyInProgress;
// synchronize to make sure that mSyncThread doesn't change between when we
// check it and when we use it
synchronized (this) {
if (mSyncThread == null) {
if (mAutoInitialize
&& extras != null
&& extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)) {
if (ContentResolver.getIsSyncable(account, authority) < 0) {
ContentResolver.setIsSyncable(account, authority, 1);
}
syncContextClient.onFinished(new SyncResult());
return;
}
mSyncThread = new SyncThread(
"SyncAdapterThread-" + mNumSyncStarts.incrementAndGet(),
syncContextClient, authority, account, extras);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
mSyncThread.start();
alreadyInProgress = false;
} else {
alreadyInProgress = true;
}
}
// do this outside since we don't want to call back into the syncContext while
// holding the synchronization lock
if (alreadyInProgress) {
syncContextClient.onFinished(SyncResult.ALREADY_IN_PROGRESS);
}
}
public void cancelSync(ISyncContext syncContext) {
// synchronize to make sure that mSyncThread doesn't change between when we
// check it and when we use it
synchronized (this) {
if (mSyncThread != null
&& mSyncThread.mSyncContext.getISyncContext() == syncContext) {
mSyncThread.interrupt();
}
}
}
}
/**
* The thread that invokes performSync(). It also acquires the provider for this sync
* before calling performSync and releases it afterwards. Cancel this thread in order to
* cancel the sync.
*/
private class SyncThread extends Thread {
private final SyncContext mSyncContext;
private final String mAuthority;
private final Account mAccount;
private final Bundle mExtras;
private SyncThread(String name, SyncContext syncContext, String authority,
Account account, Bundle extras) {
super(name);
mSyncContext = syncContext;
mAuthority = authority;
mAccount = account;
mExtras = extras;
}
public void run() {
if (isCanceled()) {
return;
}
SyncResult syncResult = new SyncResult();
ContentProviderClient provider = null;
try {
provider = mContext.getContentResolver().acquireContentProviderClient(mAuthority);
if (provider != null) {
AbstractThreadedSyncAdapter.this.performSync(mAccount, mExtras,
mAuthority, provider, syncResult);
} else {
// TODO(fredq) update the syncResults to indicate that we were unable to
// find the provider. maybe with a ProviderError?
}
} finally {
if (provider != null) {
provider.release();
}
if (!isCanceled()) {
mSyncContext.onFinished(syncResult);
}
// synchronize so that the assignment will be seen by other threads
// that also synchronize accesses to mSyncThread
synchronized (this) {
mSyncThread = null;
}
}
}
private boolean isCanceled() {
return Thread.currentThread().isInterrupted();
}
}
/**
* @return a reference to the ISyncAdapter interface into this SyncAdapter implementation.
*/
public final ISyncAdapter getISyncAdapter() {
return mISyncAdapterImpl;
}
/**
* Perform a sync for this account. SyncAdapter-specific parameters may
* be specified in extras, which is guaranteed to not be null. Invocations
* of this method are guaranteed to be serialized.
*
* @param account the account that should be synced
* @param extras SyncAdapter-specific parameters
* @param authority the authority of this sync request
* @param provider a ContentProviderClient that points to the ContentProvider for this
* authority
* @param syncResult SyncAdapter-specific parameters
*/
public abstract void performSync(Account account, Bundle extras,
String authority, ContentProviderClient provider, SyncResult syncResult);
}