Home | History | Annotate | Download | only in content
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.content;
     18 
     19 import android.accounts.Account;
     20 import android.os.Bundle;
     21 import android.os.IBinder;
     22 import android.os.Process;
     23 import android.os.RemoteException;
     24 
     25 import java.util.concurrent.atomic.AtomicInteger;
     26 
     27 /**
     28  * An abstract implementation of a SyncAdapter that spawns a thread to invoke a sync operation.
     29  * If a sync operation is already in progress when a startSync() request is received then an error
     30  * will be returned to the new request and the existing request will be allowed to continue.
     31  * When a startSync() is received and there is no sync operation in progress then a thread
     32  * will be started to run the operation and {@link #onPerformSync} will be invoked on that thread.
     33  * If a cancelSync() is received that matches an existing sync operation then the thread
     34  * that is running that sync operation will be interrupted, which will indicate to the thread
     35  * that the sync has been canceled.
     36  */
     37 public abstract class AbstractThreadedSyncAdapter {
     38     /**
     39      * Kernel event log tag.  Also listed in data/etc/event-log-tags.
     40      * @Deprecated
     41      */
     42     public static final int LOG_SYNC_DETAILS = 2743;
     43 
     44     private final Context mContext;
     45     private final AtomicInteger mNumSyncStarts;
     46     private final ISyncAdapterImpl mISyncAdapterImpl;
     47 
     48     // all accesses to this member variable must be synchronized on mSyncThreadLock
     49     private SyncThread mSyncThread;
     50     private final Object mSyncThreadLock = new Object();
     51 
     52     private final boolean mAutoInitialize;
     53 
     54     /**
     55      * Creates an {@link AbstractThreadedSyncAdapter}.
     56      * @param context the {@link android.content.Context} that this is running within.
     57      * @param autoInitialize if true then sync requests that have
     58      * {@link ContentResolver#SYNC_EXTRAS_INITIALIZE} set will be internally handled by
     59      * {@link AbstractThreadedSyncAdapter} by calling
     60      * {@link ContentResolver#setIsSyncable(android.accounts.Account, String, int)} with 1 if it
     61      * is currently set to <0.
     62      */
     63     public AbstractThreadedSyncAdapter(Context context, boolean autoInitialize) {
     64         mContext = context;
     65         mISyncAdapterImpl = new ISyncAdapterImpl();
     66         mNumSyncStarts = new AtomicInteger(0);
     67         mSyncThread = null;
     68         mAutoInitialize = autoInitialize;
     69     }
     70 
     71     public Context getContext() {
     72         return mContext;
     73     }
     74 
     75     private class ISyncAdapterImpl extends ISyncAdapter.Stub {
     76         public void startSync(ISyncContext syncContext, String authority, Account account,
     77                 Bundle extras) {
     78             final SyncContext syncContextClient = new SyncContext(syncContext);
     79 
     80             boolean alreadyInProgress;
     81             // synchronize to make sure that mSyncThread doesn't change between when we
     82             // check it and when we use it
     83             synchronized (mSyncThreadLock) {
     84                 if (mSyncThread == null) {
     85                     if (mAutoInitialize
     86                             && extras != null
     87                             && extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)) {
     88                         if (ContentResolver.getIsSyncable(account, authority) < 0) {
     89                             ContentResolver.setIsSyncable(account, authority, 1);
     90                         }
     91                         syncContextClient.onFinished(new SyncResult());
     92                         return;
     93                     }
     94                     mSyncThread = new SyncThread(
     95                             "SyncAdapterThread-" + mNumSyncStarts.incrementAndGet(),
     96                             syncContextClient, authority, account, extras);
     97                     mSyncThread.start();
     98                     alreadyInProgress = false;
     99                 } else {
    100                     alreadyInProgress = true;
    101                 }
    102             }
    103 
    104             // do this outside since we don't want to call back into the syncContext while
    105             // holding the synchronization lock
    106             if (alreadyInProgress) {
    107                 syncContextClient.onFinished(SyncResult.ALREADY_IN_PROGRESS);
    108             }
    109         }
    110 
    111         public void cancelSync(ISyncContext syncContext) {
    112             // synchronize to make sure that mSyncThread doesn't change between when we
    113             // check it and when we use it
    114             final SyncThread syncThread;
    115             synchronized (mSyncThreadLock) {
    116                 syncThread = mSyncThread;
    117             }
    118             if (syncThread != null
    119                     && syncThread.mSyncContext.getSyncContextBinder() == syncContext.asBinder()) {
    120                 onSyncCanceled();
    121             }
    122         }
    123 
    124         public void initialize(Account account, String authority) throws RemoteException {
    125             Bundle extras = new Bundle();
    126             extras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
    127             startSync(null, authority, account, extras);
    128         }
    129     }
    130 
    131     /**
    132      * The thread that invokes {@link AbstractThreadedSyncAdapter#onPerformSync}. It also acquires
    133      * the provider for this sync before calling onPerformSync and releases it afterwards. Cancel
    134      * this thread in order to cancel the sync.
    135      */
    136     private class SyncThread extends Thread {
    137         private final SyncContext mSyncContext;
    138         private final String mAuthority;
    139         private final Account mAccount;
    140         private final Bundle mExtras;
    141 
    142         private SyncThread(String name, SyncContext syncContext, String authority,
    143                 Account account, Bundle extras) {
    144             super(name);
    145             mSyncContext = syncContext;
    146             mAuthority = authority;
    147             mAccount = account;
    148             mExtras = extras;
    149         }
    150 
    151         public void run() {
    152             Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    153 
    154             if (isCanceled()) {
    155                 return;
    156             }
    157 
    158             SyncResult syncResult = new SyncResult();
    159             ContentProviderClient provider = null;
    160             try {
    161                 provider = mContext.getContentResolver().acquireContentProviderClient(mAuthority);
    162                 if (provider != null) {
    163                     AbstractThreadedSyncAdapter.this.onPerformSync(mAccount, mExtras,
    164                             mAuthority, provider, syncResult);
    165                 } else {
    166                     syncResult.databaseError = true;
    167                 }
    168             } finally {
    169                 if (provider != null) {
    170                     provider.release();
    171                 }
    172                 if (!isCanceled()) {
    173                     mSyncContext.onFinished(syncResult);
    174                 }
    175                 // synchronize so that the assignment will be seen by other threads
    176                 // that also synchronize accesses to mSyncThread
    177                 synchronized (mSyncThreadLock) {
    178                     mSyncThread = null;
    179                 }
    180             }
    181         }
    182 
    183         private boolean isCanceled() {
    184             return Thread.currentThread().isInterrupted();
    185         }
    186     }
    187 
    188     /**
    189      * @return a reference to the IBinder of the SyncAdapter service.
    190      */
    191     public final IBinder getSyncAdapterBinder() {
    192         return mISyncAdapterImpl.asBinder();
    193     }
    194 
    195     /**
    196      * Perform a sync for this account. SyncAdapter-specific parameters may
    197      * be specified in extras, which is guaranteed to not be null. Invocations
    198      * of this method are guaranteed to be serialized.
    199      *
    200      * @param account the account that should be synced
    201      * @param extras SyncAdapter-specific parameters
    202      * @param authority the authority of this sync request
    203      * @param provider a ContentProviderClient that points to the ContentProvider for this
    204      *   authority
    205      * @param syncResult SyncAdapter-specific parameters
    206      */
    207     public abstract void onPerformSync(Account account, Bundle extras,
    208             String authority, ContentProviderClient provider, SyncResult syncResult);
    209 
    210     /**
    211      * Indicates that a sync operation has been canceled. This will be invoked on a separate
    212      * thread than the sync thread and so you must consider the multi-threaded implications
    213      * of the work that you do in this method.
    214      *
    215      */
    216     public void onSyncCanceled() {
    217         final SyncThread syncThread;
    218         synchronized (mSyncThreadLock) {
    219             syncThread = mSyncThread;
    220         }
    221         if (syncThread != null) {
    222             syncThread.interrupt();
    223         }
    224     }
    225 }
    226