/* * 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; /** * Creates an {@link AbstractThreadedSyncAdapter}. * @param context the {@link Context} that this is running within. */ public AbstractThreadedSyncAdapter(Context context) { mContext = context; mISyncAdapterImpl = new ISyncAdapterImpl(); mNumSyncStarts = new AtomicInteger(0); mSyncThread = null; } 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) { 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); }