AbstractThreadedSyncAdapter.java revision 9257ec05639ac1a529c81ba94cc631b1fa5f49d9
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 17package android.content; 18 19import android.accounts.Account; 20import android.os.Bundle; 21import android.os.IBinder; 22import android.os.Process; 23import android.os.RemoteException; 24 25import java.util.HashMap; 26import java.util.concurrent.atomic.AtomicInteger; 27 28/** 29 * An abstract implementation of a SyncAdapter that spawns a thread to invoke a sync operation. 30 * If a sync operation is already in progress when a startSync() request is received then an error 31 * will be returned to the new request and the existing request will be allowed to continue. 32 * When a startSync() is received and there is no sync operation in progress then a thread 33 * will be started to run the operation and {@link #onPerformSync} will be invoked on that thread. 34 * If a cancelSync() is received that matches an existing sync operation then the thread 35 * that is running that sync operation will be interrupted, which will indicate to the thread 36 * that the sync has been canceled. 37 */ 38public abstract class AbstractThreadedSyncAdapter { 39 /** 40 * Kernel event log tag. Also listed in data/etc/event-log-tags. 41 * @deprecated Private constant. May go away in the next release. 42 */ 43 @Deprecated 44 public static final int LOG_SYNC_DETAILS = 2743; 45 46 private final Context mContext; 47 private final AtomicInteger mNumSyncStarts; 48 private final ISyncAdapterImpl mISyncAdapterImpl; 49 50 // all accesses to this member variable must be synchronized on mSyncThreadLock 51 private final HashMap<Account, SyncThread> mSyncThreads = new HashMap<Account, SyncThread>(); 52 private final Object mSyncThreadLock = new Object(); 53 54 private final boolean mAutoInitialize; 55 private boolean mAllowParallelSyncs; 56 57 /** 58 * Creates an {@link AbstractThreadedSyncAdapter}. 59 * @param context the {@link android.content.Context} that this is running within. 60 * @param autoInitialize if true then sync requests that have 61 * {@link ContentResolver#SYNC_EXTRAS_INITIALIZE} set will be internally handled by 62 * {@link AbstractThreadedSyncAdapter} by calling 63 * {@link ContentResolver#setIsSyncable(android.accounts.Account, String, int)} with 1 if it 64 * is currently set to <0. 65 */ 66 public AbstractThreadedSyncAdapter(Context context, boolean autoInitialize) { 67 this(context, autoInitialize, false /* allowParallelSyncs */); 68 } 69 70 /** 71 * Creates an {@link AbstractThreadedSyncAdapter}. 72 * @param context the {@link android.content.Context} that this is running within. 73 * @param autoInitialize if true then sync requests that have 74 * {@link ContentResolver#SYNC_EXTRAS_INITIALIZE} set will be internally handled by 75 * {@link AbstractThreadedSyncAdapter} by calling 76 * {@link ContentResolver#setIsSyncable(android.accounts.Account, String, int)} with 1 if it 77 * is currently set to <0. 78 * @param allowParallelSyncs if true then allow syncs for different accounts to run 79 * at the same time, each in their own thread. This must be consistent with the setting 80 * in the SyncAdapter's configuration file. 81 */ 82 public AbstractThreadedSyncAdapter(Context context, 83 boolean autoInitialize, boolean allowParallelSyncs) { 84 mContext = context; 85 mISyncAdapterImpl = new ISyncAdapterImpl(); 86 mNumSyncStarts = new AtomicInteger(0); 87 mAutoInitialize = autoInitialize; 88 mAllowParallelSyncs = allowParallelSyncs; 89 } 90 91 public Context getContext() { 92 return mContext; 93 } 94 95 private Account toSyncKey(Account account) { 96 if (mAllowParallelSyncs) { 97 return account; 98 } else { 99 return null; 100 } 101 } 102 103 private class ISyncAdapterImpl extends ISyncAdapter.Stub { 104 public void startSync(ISyncContext syncContext, String authority, Account account, 105 Bundle extras) { 106 final SyncContext syncContextClient = new SyncContext(syncContext); 107 108 boolean alreadyInProgress; 109 // synchronize to make sure that mSyncThreads doesn't change between when we 110 // check it and when we use it 111 final Account threadsKey = toSyncKey(account); 112 synchronized (mSyncThreadLock) { 113 if (!mSyncThreads.containsKey(threadsKey)) { 114 if (mAutoInitialize 115 && extras != null 116 && extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)) { 117 if (ContentResolver.getIsSyncable(account, authority) < 0) { 118 ContentResolver.setIsSyncable(account, authority, 1); 119 } 120 syncContextClient.onFinished(new SyncResult()); 121 return; 122 } 123 SyncThread syncThread = new SyncThread( 124 "SyncAdapterThread-" + mNumSyncStarts.incrementAndGet(), 125 syncContextClient, authority, account, extras); 126 mSyncThreads.put(threadsKey, syncThread); 127 syncThread.start(); 128 alreadyInProgress = false; 129 } else { 130 alreadyInProgress = true; 131 } 132 } 133 134 // do this outside since we don't want to call back into the syncContext while 135 // holding the synchronization lock 136 if (alreadyInProgress) { 137 syncContextClient.onFinished(SyncResult.ALREADY_IN_PROGRESS); 138 } 139 } 140 141 public void cancelSync(ISyncContext syncContext) { 142 // synchronize to make sure that mSyncThreads doesn't change between when we 143 // check it and when we use it 144 SyncThread info = null; 145 synchronized (mSyncThreadLock) { 146 for (SyncThread current : mSyncThreads.values()) { 147 if (current.mSyncContext.getSyncContextBinder() == syncContext.asBinder()) { 148 info = current; 149 break; 150 } 151 } 152 } 153 if (info != null) { 154 if (mAllowParallelSyncs) { 155 onSyncCanceled(info); 156 } else { 157 onSyncCanceled(); 158 } 159 } 160 } 161 162 public void initialize(Account account, String authority) throws RemoteException { 163 Bundle extras = new Bundle(); 164 extras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true); 165 startSync(null, authority, account, extras); 166 } 167 } 168 169 /** 170 * The thread that invokes {@link AbstractThreadedSyncAdapter#onPerformSync}. It also acquires 171 * the provider for this sync before calling onPerformSync and releases it afterwards. Cancel 172 * this thread in order to cancel the sync. 173 */ 174 private class SyncThread extends Thread { 175 private final SyncContext mSyncContext; 176 private final String mAuthority; 177 private final Account mAccount; 178 private final Bundle mExtras; 179 private final Account mThreadsKey; 180 181 private SyncThread(String name, SyncContext syncContext, String authority, 182 Account account, Bundle extras) { 183 super(name); 184 mSyncContext = syncContext; 185 mAuthority = authority; 186 mAccount = account; 187 mExtras = extras; 188 mThreadsKey = toSyncKey(account); 189 } 190 191 public void run() { 192 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 193 194 SyncResult syncResult = new SyncResult(); 195 ContentProviderClient provider = null; 196 try { 197 if (isCanceled()) { 198 return; 199 } 200 provider = mContext.getContentResolver().acquireContentProviderClient(mAuthority); 201 if (provider != null) { 202 AbstractThreadedSyncAdapter.this.onPerformSync(mAccount, mExtras, 203 mAuthority, provider, syncResult); 204 } else { 205 syncResult.databaseError = true; 206 } 207 } finally { 208 if (provider != null) { 209 provider.release(); 210 } 211 if (!isCanceled()) { 212 mSyncContext.onFinished(syncResult); 213 } 214 // synchronize so that the assignment will be seen by other threads 215 // that also synchronize accesses to mSyncThreads 216 synchronized (mSyncThreadLock) { 217 mSyncThreads.remove(mThreadsKey); 218 } 219 } 220 } 221 222 private boolean isCanceled() { 223 return Thread.currentThread().isInterrupted(); 224 } 225 } 226 227 /** 228 * @return a reference to the IBinder of the SyncAdapter service. 229 */ 230 public final IBinder getSyncAdapterBinder() { 231 return mISyncAdapterImpl.asBinder(); 232 } 233 234 /** 235 * Perform a sync for this account. SyncAdapter-specific parameters may 236 * be specified in extras, which is guaranteed to not be null. Invocations 237 * of this method are guaranteed to be serialized. 238 * 239 * @param account the account that should be synced 240 * @param extras SyncAdapter-specific parameters 241 * @param authority the authority of this sync request 242 * @param provider a ContentProviderClient that points to the ContentProvider for this 243 * authority 244 * @param syncResult SyncAdapter-specific parameters 245 */ 246 public abstract void onPerformSync(Account account, Bundle extras, 247 String authority, ContentProviderClient provider, SyncResult syncResult); 248 249 /** 250 * Indicates that a sync operation has been canceled. This will be invoked on a separate 251 * thread than the sync thread and so you must consider the multi-threaded implications 252 * of the work that you do in this method. 253 * <p> 254 * This will only be invoked when the SyncAdapter indicates that it doesn't support 255 * parallel syncs. 256 */ 257 public void onSyncCanceled() { 258 final SyncThread syncThread; 259 synchronized (mSyncThreadLock) { 260 syncThread = mSyncThreads.get(null); 261 } 262 if (syncThread != null) { 263 syncThread.interrupt(); 264 } 265 } 266 267 /** 268 * Indicates that a sync operation has been canceled. This will be invoked on a separate 269 * thread than the sync thread and so you must consider the multi-threaded implications 270 * of the work that you do in this method. 271 * <p> 272 * This will only be invoked when the SyncAdapter indicates that it does support 273 * parallel syncs. 274 * @param thread the Thread of the sync that is to be canceled. 275 */ 276 public void onSyncCanceled(Thread thread) { 277 thread.interrupt(); 278 } 279} 280