blob: 58bd5cda825dc04698a19075b90699d952f527b2 [file] [log] [blame]
Fred Quintana21bb0de2009-06-16 10:24:58 -07001/*
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;
Fred Quintanaf0380042009-10-06 17:05:58 -070021import android.os.IBinder;
Ken Shirriff1719a392009-12-07 15:57:35 -080022import android.os.Process;
Fred Quintanae7424ff2009-10-14 15:59:21 -070023import android.os.RemoteException;
Andy Stadler09b45a32012-05-03 15:00:49 -070024import android.os.Trace;
Fred Quintana21bb0de2009-06-16 10:24:58 -070025
Fred Quintana0c4d04a2010-11-03 17:02:55 -070026import java.util.HashMap;
Fred Quintana21bb0de2009-06-16 10:24:58 -070027import java.util.concurrent.atomic.AtomicInteger;
28
29/**
30 * An abstract implementation of a SyncAdapter that spawns a thread to invoke a sync operation.
Matthew Williams1967c8d2015-06-19 19:03:13 -070031 * If a sync operation is already in progress when a sync request is received, an error will be
32 * returned to the new request and the existing request will be allowed to continue.
33 * However if there is no sync in progress then a thread will be spawned and {@link #onPerformSync}
34 * will be invoked on that thread.
35 * <p>
36 * Syncs can be cancelled at any time by the framework. For example a sync that was not
37 * user-initiated and lasts longer than 30 minutes will be considered timed-out and cancelled.
38 * Similarly the framework will attempt to determine whether or not an adapter is making progress
39 * by monitoring its network activity over the course of a minute. If the network traffic over this
40 * window is close enough to zero the sync will be cancelled. You can also request the sync be
41 * cancelled via {@link ContentResolver#cancelSync(Account, String)} or
42 * {@link ContentResolver#cancelSync(SyncRequest)}.
43 * <p>
44 * A sync is cancelled by issuing a {@link Thread#interrupt()} on the syncing thread. <strong>Either
45 * your code in {@link #onPerformSync(Account, Bundle, String, ContentProviderClient, SyncResult)}
46 * must check {@link Thread#interrupted()}, or you you must override one of
47 * {@link #onSyncCanceled(Thread)}/{@link #onSyncCanceled()}</strong> (depending on whether or not
48 * your adapter supports syncing of multiple accounts in parallel). If your adapter does not
49 * respect the cancel issued by the framework you run the risk of your app's entire process being
50 * killed.
Fred Quintanae6d60ec2011-08-24 11:29:00 -070051 * <p>
52 * In order to be a sync adapter one must extend this class, provide implementations for the
53 * abstract methods and write a service that returns the result of {@link #getSyncAdapterBinder()}
54 * in the service's {@link android.app.Service#onBind(android.content.Intent)} when invoked
55 * with an intent with action <code>android.content.SyncAdapter</code>. This service
56 * must specify the following intent filter and metadata tags in its AndroidManifest.xml file
57 * <pre>
58 * &lt;intent-filter&gt;
59 * &lt;action android:name="android.content.SyncAdapter" /&gt;
60 * &lt;/intent-filter&gt;
61 * &lt;meta-data android:name="android.content.SyncAdapter"
62 * android:resource="@xml/syncadapter" /&gt;
63 * </pre>
64 * The <code>android:resource</code> attribute must point to a resource that looks like:
65 * <pre>
66 * &lt;sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
67 * android:contentAuthority="authority"
68 * android:accountType="accountType"
69 * android:userVisible="true|false"
70 * android:supportsUploading="true|false"
71 * android:allowParallelSyncs="true|false"
72 * android:isAlwaysSyncable="true|false"
73 * android:syncAdapterSettingsAction="ACTION_OF_SETTINGS_ACTIVITY"
74 * /&gt;
75 * </pre>
76 * <ul>
77 * <li>The <code>android:contentAuthority</code> and <code>android:accountType</code> attributes
78 * indicate which content authority and for which account types this sync adapter serves.
79 * <li><code>android:userVisible</code> defaults to true and controls whether or not this sync
80 * adapter shows up in the Sync Settings screen.
81 * <li><code>android:supportsUploading</code> defaults
82 * to true and if true an upload-only sync will be requested for all syncadapters associated
83 * with an authority whenever that authority's content provider does a
84 * {@link ContentResolver#notifyChange(android.net.Uri, android.database.ContentObserver, boolean)}
85 * with syncToNetwork set to true.
86 * <li><code>android:allowParallelSyncs</code> defaults to false and if true indicates that
87 * the sync adapter can handle syncs for multiple accounts at the same time. Otherwise
88 * the SyncManager will wait until the sync adapter is not in use before requesting that
89 * it sync an account's data.
90 * <li><code>android:isAlwaysSyncable</code> defaults to false and if true tells the SyncManager
91 * to intialize the isSyncable state to 1 for that sync adapter for each account that is added.
92 * <li><code>android:syncAdapterSettingsAction</code> defaults to null and if supplied it
93 * specifies an Intent action of an activity that can be used to adjust the sync adapter's
94 * sync settings. The activity must live in the same package as the sync adapter.
95 * </ul>
Fred Quintana21bb0de2009-06-16 10:24:58 -070096 */
97public abstract class AbstractThreadedSyncAdapter {
Fred Quintana97ef7632009-12-10 10:33:18 -080098 /**
99 * Kernel event log tag. Also listed in data/etc/event-log-tags.
Joe Onoratod3ad6962010-09-16 13:38:25 -0400100 * @deprecated Private constant. May go away in the next release.
Fred Quintana97ef7632009-12-10 10:33:18 -0800101 */
Joe Onoratod3ad6962010-09-16 13:38:25 -0400102 @Deprecated
Fred Quintana97ef7632009-12-10 10:33:18 -0800103 public static final int LOG_SYNC_DETAILS = 2743;
104
Fred Quintana21bb0de2009-06-16 10:24:58 -0700105 private final Context mContext;
106 private final AtomicInteger mNumSyncStarts;
107 private final ISyncAdapterImpl mISyncAdapterImpl;
108
Fred Quintana3cff76a2009-08-26 18:49:19 -0700109 // all accesses to this member variable must be synchronized on mSyncThreadLock
Fred Quintana0c4d04a2010-11-03 17:02:55 -0700110 private final HashMap<Account, SyncThread> mSyncThreads = new HashMap<Account, SyncThread>();
Fred Quintana3cff76a2009-08-26 18:49:19 -0700111 private final Object mSyncThreadLock = new Object();
Fred Quintana21bb0de2009-06-16 10:24:58 -0700112
Fred Quintana4a6679b2009-08-17 13:05:39 -0700113 private final boolean mAutoInitialize;
Fred Quintana0c4d04a2010-11-03 17:02:55 -0700114 private boolean mAllowParallelSyncs;
Fred Quintana21bb0de2009-06-16 10:24:58 -0700115
116 /**
117 * Creates an {@link AbstractThreadedSyncAdapter}.
Fred Quintana4a6679b2009-08-17 13:05:39 -0700118 * @param context the {@link android.content.Context} that this is running within.
119 * @param autoInitialize if true then sync requests that have
120 * {@link ContentResolver#SYNC_EXTRAS_INITIALIZE} set will be internally handled by
121 * {@link AbstractThreadedSyncAdapter} by calling
122 * {@link ContentResolver#setIsSyncable(android.accounts.Account, String, int)} with 1 if it
123 * is currently set to <0.
Fred Quintana21bb0de2009-06-16 10:24:58 -0700124 */
Fred Quintana4a6679b2009-08-17 13:05:39 -0700125 public AbstractThreadedSyncAdapter(Context context, boolean autoInitialize) {
Fred Quintana0c4d04a2010-11-03 17:02:55 -0700126 this(context, autoInitialize, false /* allowParallelSyncs */);
127 }
128
129 /**
130 * Creates an {@link AbstractThreadedSyncAdapter}.
131 * @param context the {@link android.content.Context} that this is running within.
132 * @param autoInitialize if true then sync requests that have
133 * {@link ContentResolver#SYNC_EXTRAS_INITIALIZE} set will be internally handled by
134 * {@link AbstractThreadedSyncAdapter} by calling
135 * {@link ContentResolver#setIsSyncable(android.accounts.Account, String, int)} with 1 if it
136 * is currently set to <0.
137 * @param allowParallelSyncs if true then allow syncs for different accounts to run
138 * at the same time, each in their own thread. This must be consistent with the setting
139 * in the SyncAdapter's configuration file.
140 */
141 public AbstractThreadedSyncAdapter(Context context,
142 boolean autoInitialize, boolean allowParallelSyncs) {
Fred Quintana21bb0de2009-06-16 10:24:58 -0700143 mContext = context;
144 mISyncAdapterImpl = new ISyncAdapterImpl();
145 mNumSyncStarts = new AtomicInteger(0);
Fred Quintana4a6679b2009-08-17 13:05:39 -0700146 mAutoInitialize = autoInitialize;
Fred Quintana0c4d04a2010-11-03 17:02:55 -0700147 mAllowParallelSyncs = allowParallelSyncs;
Fred Quintana21bb0de2009-06-16 10:24:58 -0700148 }
149
Fred Quintanac298a852009-08-27 18:28:17 -0700150 public Context getContext() {
151 return mContext;
152 }
153
Fred Quintana0c4d04a2010-11-03 17:02:55 -0700154 private Account toSyncKey(Account account) {
155 if (mAllowParallelSyncs) {
156 return account;
157 } else {
158 return null;
159 }
160 }
161
Fred Quintanaf0380042009-10-06 17:05:58 -0700162 private class ISyncAdapterImpl extends ISyncAdapter.Stub {
Matthew Williamsfa774182013-06-18 15:44:11 -0700163 @Override
Fred Quintana21bb0de2009-06-16 10:24:58 -0700164 public void startSync(ISyncContext syncContext, String authority, Account account,
165 Bundle extras) {
166 final SyncContext syncContextClient = new SyncContext(syncContext);
167
168 boolean alreadyInProgress;
Fred Quintana0c4d04a2010-11-03 17:02:55 -0700169 // synchronize to make sure that mSyncThreads doesn't change between when we
Fred Quintana21bb0de2009-06-16 10:24:58 -0700170 // check it and when we use it
Fred Quintana0c4d04a2010-11-03 17:02:55 -0700171 final Account threadsKey = toSyncKey(account);
Fred Quintana3cff76a2009-08-26 18:49:19 -0700172 synchronized (mSyncThreadLock) {
Fred Quintana0c4d04a2010-11-03 17:02:55 -0700173 if (!mSyncThreads.containsKey(threadsKey)) {
Fred Quintana4a6679b2009-08-17 13:05:39 -0700174 if (mAutoInitialize
175 && extras != null
176 && extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)) {
Daniel Karlberg8014cbff2012-08-15 10:00:41 +0200177 try {
178 if (ContentResolver.getIsSyncable(account, authority) < 0) {
179 ContentResolver.setIsSyncable(account, authority, 1);
180 }
181 } finally {
182 syncContextClient.onFinished(new SyncResult());
Fred Quintana4a6679b2009-08-17 13:05:39 -0700183 }
Fred Quintana4a6679b2009-08-17 13:05:39 -0700184 return;
185 }
Fred Quintana0c4d04a2010-11-03 17:02:55 -0700186 SyncThread syncThread = new SyncThread(
Fred Quintana21bb0de2009-06-16 10:24:58 -0700187 "SyncAdapterThread-" + mNumSyncStarts.incrementAndGet(),
188 syncContextClient, authority, account, extras);
Fred Quintana0c4d04a2010-11-03 17:02:55 -0700189 mSyncThreads.put(threadsKey, syncThread);
190 syncThread.start();
Fred Quintana21bb0de2009-06-16 10:24:58 -0700191 alreadyInProgress = false;
192 } else {
193 alreadyInProgress = true;
194 }
195 }
196
197 // do this outside since we don't want to call back into the syncContext while
198 // holding the synchronization lock
199 if (alreadyInProgress) {
200 syncContextClient.onFinished(SyncResult.ALREADY_IN_PROGRESS);
201 }
202 }
203
Matthew Williamsfa774182013-06-18 15:44:11 -0700204 @Override
Fred Quintana21bb0de2009-06-16 10:24:58 -0700205 public void cancelSync(ISyncContext syncContext) {
Fred Quintana0c4d04a2010-11-03 17:02:55 -0700206 // synchronize to make sure that mSyncThreads doesn't change between when we
Fred Quintana21bb0de2009-06-16 10:24:58 -0700207 // check it and when we use it
Fred Quintana0c4d04a2010-11-03 17:02:55 -0700208 SyncThread info = null;
Fred Quintana3cff76a2009-08-26 18:49:19 -0700209 synchronized (mSyncThreadLock) {
Fred Quintana0c4d04a2010-11-03 17:02:55 -0700210 for (SyncThread current : mSyncThreads.values()) {
211 if (current.mSyncContext.getSyncContextBinder() == syncContext.asBinder()) {
212 info = current;
213 break;
214 }
215 }
Fred Quintanad5e4fdc2010-03-30 15:16:21 -0700216 }
Fred Quintana0c4d04a2010-11-03 17:02:55 -0700217 if (info != null) {
218 if (mAllowParallelSyncs) {
219 onSyncCanceled(info);
220 } else {
221 onSyncCanceled();
222 }
Fred Quintana21bb0de2009-06-16 10:24:58 -0700223 }
224 }
Fred Quintanae7424ff2009-10-14 15:59:21 -0700225
226 public void initialize(Account account, String authority) throws RemoteException {
227 Bundle extras = new Bundle();
228 extras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
229 startSync(null, authority, account, extras);
230 }
Fred Quintana21bb0de2009-06-16 10:24:58 -0700231 }
232
233 /**
Fred Quintanaf0380042009-10-06 17:05:58 -0700234 * The thread that invokes {@link AbstractThreadedSyncAdapter#onPerformSync}. It also acquires
235 * the provider for this sync before calling onPerformSync and releases it afterwards. Cancel
236 * this thread in order to cancel the sync.
Fred Quintana21bb0de2009-06-16 10:24:58 -0700237 */
238 private class SyncThread extends Thread {
239 private final SyncContext mSyncContext;
240 private final String mAuthority;
241 private final Account mAccount;
242 private final Bundle mExtras;
Fred Quintana0c4d04a2010-11-03 17:02:55 -0700243 private final Account mThreadsKey;
Fred Quintana21bb0de2009-06-16 10:24:58 -0700244
245 private SyncThread(String name, SyncContext syncContext, String authority,
246 Account account, Bundle extras) {
247 super(name);
248 mSyncContext = syncContext;
249 mAuthority = authority;
250 mAccount = account;
251 mExtras = extras;
Fred Quintana0c4d04a2010-11-03 17:02:55 -0700252 mThreadsKey = toSyncKey(account);
Fred Quintana21bb0de2009-06-16 10:24:58 -0700253 }
254
Andy Stadler09b45a32012-05-03 15:00:49 -0700255 @Override
Fred Quintana21bb0de2009-06-16 10:24:58 -0700256 public void run() {
Fred Quintana75d797c2009-08-25 15:05:02 -0700257 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
258
Andy Stadler09b45a32012-05-03 15:00:49 -0700259 // Trace this sync instance. Note, conceptually this should be in
260 // SyncStorageEngine.insertStartSyncEvent(), but the trace functions require unique
261 // threads in order to track overlapping operations, so we'll do it here for now.
262 Trace.traceBegin(Trace.TRACE_TAG_SYNC_MANAGER, mAuthority);
263
Fred Quintana21bb0de2009-06-16 10:24:58 -0700264 SyncResult syncResult = new SyncResult();
265 ContentProviderClient provider = null;
266 try {
Alon Albert9257ec02011-02-07 10:53:26 -0800267 if (isCanceled()) {
268 return;
269 }
Fred Quintana21bb0de2009-06-16 10:24:58 -0700270 provider = mContext.getContentResolver().acquireContentProviderClient(mAuthority);
271 if (provider != null) {
Fred Quintanaf0380042009-10-06 17:05:58 -0700272 AbstractThreadedSyncAdapter.this.onPerformSync(mAccount, mExtras,
Fred Quintana21bb0de2009-06-16 10:24:58 -0700273 mAuthority, provider, syncResult);
274 } else {
Fred Quintanaf0380042009-10-06 17:05:58 -0700275 syncResult.databaseError = true;
Fred Quintana21bb0de2009-06-16 10:24:58 -0700276 }
Dianne Hackbornd01ed462015-06-22 17:41:44 -0700277 } catch (SecurityException e) {
278 AbstractThreadedSyncAdapter.this.onSecurityException(mAccount, mExtras,
279 mAuthority, syncResult);
280 syncResult.databaseError = true;
Fred Quintana21bb0de2009-06-16 10:24:58 -0700281 } finally {
Andy Stadler09b45a32012-05-03 15:00:49 -0700282 Trace.traceEnd(Trace.TRACE_TAG_SYNC_MANAGER);
283
Fred Quintana21bb0de2009-06-16 10:24:58 -0700284 if (provider != null) {
285 provider.release();
286 }
287 if (!isCanceled()) {
288 mSyncContext.onFinished(syncResult);
289 }
290 // synchronize so that the assignment will be seen by other threads
Fred Quintana0c4d04a2010-11-03 17:02:55 -0700291 // that also synchronize accesses to mSyncThreads
Fred Quintana3cff76a2009-08-26 18:49:19 -0700292 synchronized (mSyncThreadLock) {
Fred Quintana0c4d04a2010-11-03 17:02:55 -0700293 mSyncThreads.remove(mThreadsKey);
Fred Quintana21bb0de2009-06-16 10:24:58 -0700294 }
295 }
296 }
297
298 private boolean isCanceled() {
299 return Thread.currentThread().isInterrupted();
300 }
301 }
302
303 /**
Fred Quintanaf0380042009-10-06 17:05:58 -0700304 * @return a reference to the IBinder of the SyncAdapter service.
Fred Quintana21bb0de2009-06-16 10:24:58 -0700305 */
Fred Quintanaf0380042009-10-06 17:05:58 -0700306 public final IBinder getSyncAdapterBinder() {
307 return mISyncAdapterImpl.asBinder();
Fred Quintana21bb0de2009-06-16 10:24:58 -0700308 }
309
310 /**
311 * Perform a sync for this account. SyncAdapter-specific parameters may
312 * be specified in extras, which is guaranteed to not be null. Invocations
313 * of this method are guaranteed to be serialized.
314 *
315 * @param account the account that should be synced
316 * @param extras SyncAdapter-specific parameters
317 * @param authority the authority of this sync request
318 * @param provider a ContentProviderClient that points to the ContentProvider for this
319 * authority
320 * @param syncResult SyncAdapter-specific parameters
321 */
Fred Quintanaf0380042009-10-06 17:05:58 -0700322 public abstract void onPerformSync(Account account, Bundle extras,
Fred Quintana21bb0de2009-06-16 10:24:58 -0700323 String authority, ContentProviderClient provider, SyncResult syncResult);
Fred Quintana274dc9d2009-12-11 13:17:08 -0800324
325 /**
Dianne Hackbornd01ed462015-06-22 17:41:44 -0700326 * Report that there was a security exception when opening the content provider
327 * prior to calling {@link #onPerformSync}. This will be treated as a sync
328 * database failure.
329 *
330 * @param account the account that attempted to sync
331 * @param extras SyncAdapter-specific parameters
332 * @param authority the authority of the failed sync request
333 * @param syncResult SyncAdapter-specific parameters
334 */
335 public void onSecurityException(Account account, Bundle extras,
336 String authority, SyncResult syncResult) {
337 }
338
339 /**
Fred Quintana274dc9d2009-12-11 13:17:08 -0800340 * Indicates that a sync operation has been canceled. This will be invoked on a separate
341 * thread than the sync thread and so you must consider the multi-threaded implications
342 * of the work that you do in this method.
Fred Quintana0c4d04a2010-11-03 17:02:55 -0700343 * <p>
344 * This will only be invoked when the SyncAdapter indicates that it doesn't support
345 * parallel syncs.
Fred Quintana274dc9d2009-12-11 13:17:08 -0800346 */
Fred Quintanad5e4fdc2010-03-30 15:16:21 -0700347 public void onSyncCanceled() {
348 final SyncThread syncThread;
349 synchronized (mSyncThreadLock) {
Fred Quintana0c4d04a2010-11-03 17:02:55 -0700350 syncThread = mSyncThreads.get(null);
Fred Quintanad5e4fdc2010-03-30 15:16:21 -0700351 }
352 if (syncThread != null) {
353 syncThread.interrupt();
354 }
Fred Quintana274dc9d2009-12-11 13:17:08 -0800355 }
Fred Quintana0c4d04a2010-11-03 17:02:55 -0700356
357 /**
358 * Indicates that a sync operation has been canceled. This will be invoked on a separate
359 * thread than the sync thread and so you must consider the multi-threaded implications
360 * of the work that you do in this method.
361 * <p>
362 * This will only be invoked when the SyncAdapter indicates that it does support
363 * parallel syncs.
364 * @param thread the Thread of the sync that is to be canceled.
365 */
366 public void onSyncCanceled(Thread thread) {
367 thread.interrupt();
368 }
Fred Quintanaf0380042009-10-06 17:05:58 -0700369}