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 Private constant.  May go away in the next release.
     41      */
     42     @Deprecated
     43     public static final int LOG_SYNC_DETAILS = 2743;
     44 
     45     private final Context mContext;
     46     private final AtomicInteger mNumSyncStarts;
     47     private final ISyncAdapterImpl mISyncAdapterImpl;
     48 
     49     // all accesses to this member variable must be synchronized on mSyncThreadLock
     50     private SyncThread mSyncThread;
     51     private final Object mSyncThreadLock = new Object();
     52 
     53     private final boolean mAutoInitialize;
     54 
     55     /**
     56      * Creates an {@link AbstractThreadedSyncAdapter}.
     57      * @param context the {@link android.content.Context} that this is running within.
     58      * @param autoInitialize if true then sync requests that have
     59      * {@link ContentResolver#SYNC_EXTRAS_INITIALIZE} set will be internally handled by
     60      * {@link AbstractThreadedSyncAdapter} by calling
     61      * {@link ContentResolver#setIsSyncable(android.accounts.Account, String, int)} with 1 if it
     62      * is currently set to <0.
     63      */
     64     public AbstractThreadedSyncAdapter(Context context, boolean autoInitialize) {
     65         mContext = context;
     66         mISyncAdapterImpl = new ISyncAdapterImpl();
     67         mNumSyncStarts = new AtomicInteger(0);
     68         mSyncThread = null;
     69         mAutoInitialize = autoInitialize;
     70     }
     71 
     72     public Context getContext() {
     73         return mContext;
     74     }
     75 
     76     private class ISyncAdapterImpl extends ISyncAdapter.Stub {
     77         public void startSync(ISyncContext syncContext, String authority, Account account,
     78                 Bundle extras) {
     79             final SyncContext syncContextClient = new SyncContext(syncContext);
     80 
     81             boolean alreadyInProgress;
     82             // synchronize to make sure that mSyncThread doesn't change between when we
     83             // check it and when we use it
     84             synchronized (mSyncThreadLock) {
     85                 if (mSyncThread == null) {
     86                     if (mAutoInitialize
     87                             && extras != null
     88                             && extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)) {
     89                         if (ContentResolver.getIsSyncable(account, authority) < 0) {
     90                             ContentResolver.setIsSyncable(account, authority, 1);
     91                         }
     92                         syncContextClient.onFinished(new SyncResult());
     93                         return;
     94                     }
     95                     mSyncThread = new SyncThread(
     96                             "SyncAdapterThread-" + mNumSyncStarts.incrementAndGet(),
     97                             syncContextClient, authority, account, extras);
     98                     mSyncThread.start();
     99                     alreadyInProgress = false;
    100                 } else {
    101                     alreadyInProgress = true;
    102                 }
    103             }
    104 
    105             // do this outside since we don't want to call back into the syncContext while
    106             // holding the synchronization lock
    107             if (alreadyInProgress) {
    108                 syncContextClient.onFinished(SyncResult.ALREADY_IN_PROGRESS);
    109             }
    110         }
    111 
    112         public void cancelSync(ISyncContext syncContext) {
    113             // synchronize to make sure that mSyncThread doesn't change between when we
    114             // check it and when we use it
    115             final SyncThread syncThread;
    116             synchronized (mSyncThreadLock) {
    117                 syncThread = mSyncThread;
    118             }
    119             if (syncThread != null
    120                     && syncThread.mSyncContext.getSyncContextBinder() == syncContext.asBinder()) {
    121                 onSyncCanceled();
    122             }
    123         }
    124 
    125         public void initialize(Account account, String authority) throws RemoteException {
    126             Bundle extras = new Bundle();
    127             extras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
    128             startSync(null, authority, account, extras);
    129         }
    130     }
    131 
    132     /**
    133      * The thread that invokes {@link AbstractThreadedSyncAdapter#onPerformSync}. It also acquires
    134      * the provider for this sync before calling onPerformSync and releases it afterwards. Cancel
    135      * this thread in order to cancel the sync.
    136      */
    137     private class SyncThread extends Thread {
    138         private final SyncContext mSyncContext;
    139         private final String mAuthority;
    140         private final Account mAccount;
    141         private final Bundle mExtras;
    142 
    143         private SyncThread(String name, SyncContext syncContext, String authority,
    144                 Account account, Bundle extras) {
    145             super(name);
    146             mSyncContext = syncContext;
    147             mAuthority = authority;
    148             mAccount = account;
    149             mExtras = extras;
    150         }
    151 
    152         public void run() {
    153             Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    154 
    155             if (isCanceled()) {
    156                 return;
    157             }
    158 
    159             SyncResult syncResult = new SyncResult();
    160             ContentProviderClient provider = null;
    161             try {
    162                 provider = mContext.getContentResolver().acquireContentProviderClient(mAuthority);
    163                 if (provider != null) {
    164                     AbstractThreadedSyncAdapter.this.onPerformSync(mAccount, mExtras,
    165                             mAuthority, provider, syncResult);
    166                 } else {
    167                     syncResult.databaseError = true;
    168                 }
    169             } finally {
    170                 if (provider != null) {
    171                     provider.release();
    172                 }
    173                 if (!isCanceled()) {
    174                     mSyncContext.onFinished(syncResult);
    175                 }
    176                 // synchronize so that the assignment will be seen by other threads
    177                 // that also synchronize accesses to mSyncThread
    178                 synchronized (mSyncThreadLock) {
    179                     mSyncThread = null;
    180                 }
    181             }
    182         }
    183 
    184         private boolean isCanceled() {
    185             return Thread.currentThread().isInterrupted();
    186         }
    187     }
    188 
    189     /**
    190      * @return a reference to the IBinder of the SyncAdapter service.
    191      */
    192     public final IBinder getSyncAdapterBinder() {
    193         return mISyncAdapterImpl.asBinder();
    194     }
    195 
    196     /**
    197      * Perform a sync for this account. SyncAdapter-specific parameters may
    198      * be specified in extras, which is guaranteed to not be null. Invocations
    199      * of this method are guaranteed to be serialized.
    200      *
    201      * @param account the account that should be synced
    202      * @param extras SyncAdapter-specific parameters
    203      * @param authority the authority of this sync request
    204      * @param provider a ContentProviderClient that points to the ContentProvider for this
    205      *   authority
    206      * @param syncResult SyncAdapter-specific parameters
    207      */
    208     public abstract void onPerformSync(Account account, Bundle extras,
    209             String authority, ContentProviderClient provider, SyncResult syncResult);
    210 
    211     /**
    212      * Indicates that a sync operation has been canceled. This will be invoked on a separate
    213      * thread than the sync thread and so you must consider the multi-threaded implications
    214      * of the work that you do in this method.
    215      *
    216      */
    217     public void onSyncCanceled() {
    218         final SyncThread syncThread;
    219         synchronized (mSyncThreadLock) {
    220             syncThread = mSyncThread;
    221         }
    222         if (syncThread != null) {
    223             syncThread.interrupt();
    224         }
    225     }
    226 }
    227