• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 import android.os.Trace;
25 
26 import java.util.HashMap;
27 import java.util.concurrent.atomic.AtomicInteger;
28 
29 /**
30  * An abstract implementation of a SyncAdapter that spawns a thread to invoke a sync operation.
31  * If a sync operation is already in progress when a startSync() request is received then an error
32  * will be returned to the new request and the existing request will be allowed to continue.
33  * When a startSync() is received and there is no sync operation in progress then a thread
34  * will be started to run the operation and {@link #onPerformSync} will be invoked on that thread.
35  * If a cancelSync() is received that matches an existing sync operation then the thread
36  * that is running that sync operation will be interrupted, which will indicate to the thread
37  * that the sync has been canceled.
38  * <p>
39  * In order to be a sync adapter one must extend this class, provide implementations for the
40  * abstract methods and write a service that returns the result of {@link #getSyncAdapterBinder()}
41  * in the service's {@link android.app.Service#onBind(android.content.Intent)} when invoked
42  * with an intent with action <code>android.content.SyncAdapter</code>. This service
43  * must specify the following intent filter and metadata tags in its AndroidManifest.xml file
44  * <pre>
45  *   &lt;intent-filter&gt;
46  *     &lt;action android:name="android.content.SyncAdapter" /&gt;
47  *   &lt;/intent-filter&gt;
48  *   &lt;meta-data android:name="android.content.SyncAdapter"
49  *             android:resource="@xml/syncadapter" /&gt;
50  * </pre>
51  * The <code>android:resource</code> attribute must point to a resource that looks like:
52  * <pre>
53  * &lt;sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
54  *    android:contentAuthority="authority"
55  *    android:accountType="accountType"
56  *    android:userVisible="true|false"
57  *    android:supportsUploading="true|false"
58  *    android:allowParallelSyncs="true|false"
59  *    android:isAlwaysSyncable="true|false"
60  *    android:syncAdapterSettingsAction="ACTION_OF_SETTINGS_ACTIVITY"
61  * /&gt;
62  * </pre>
63  * <ul>
64  * <li>The <code>android:contentAuthority</code> and <code>android:accountType</code> attributes
65  * indicate which content authority and for which account types this sync adapter serves.
66  * <li><code>android:userVisible</code> defaults to true and controls whether or not this sync
67  * adapter shows up in the Sync Settings screen.
68  * <li><code>android:supportsUploading</code> defaults
69  * to true and if true an upload-only sync will be requested for all syncadapters associated
70  * with an authority whenever that authority's content provider does a
71  * {@link ContentResolver#notifyChange(android.net.Uri, android.database.ContentObserver, boolean)}
72  * with syncToNetwork set to true.
73  * <li><code>android:allowParallelSyncs</code> defaults to false and if true indicates that
74  * the sync adapter can handle syncs for multiple accounts at the same time. Otherwise
75  * the SyncManager will wait until the sync adapter is not in use before requesting that
76  * it sync an account's data.
77  * <li><code>android:isAlwaysSyncable</code> defaults to false and if true tells the SyncManager
78  * to intialize the isSyncable state to 1 for that sync adapter for each account that is added.
79  * <li><code>android:syncAdapterSettingsAction</code> defaults to null and if supplied it
80  * specifies an Intent action of an activity that can be used to adjust the sync adapter's
81  * sync settings. The activity must live in the same package as the sync adapter.
82  * </ul>
83  */
84 public abstract class AbstractThreadedSyncAdapter {
85     /**
86      * Kernel event log tag.  Also listed in data/etc/event-log-tags.
87      * @deprecated Private constant.  May go away in the next release.
88      */
89     @Deprecated
90     public static final int LOG_SYNC_DETAILS = 2743;
91 
92     private final Context mContext;
93     private final AtomicInteger mNumSyncStarts;
94     private final ISyncAdapterImpl mISyncAdapterImpl;
95 
96     // all accesses to this member variable must be synchronized on mSyncThreadLock
97     private final HashMap<Account, SyncThread> mSyncThreads = new HashMap<Account, SyncThread>();
98     private final Object mSyncThreadLock = new Object();
99 
100     private final boolean mAutoInitialize;
101     private boolean mAllowParallelSyncs;
102 
103     /**
104      * Creates an {@link AbstractThreadedSyncAdapter}.
105      * @param context the {@link android.content.Context} that this is running within.
106      * @param autoInitialize if true then sync requests that have
107      * {@link ContentResolver#SYNC_EXTRAS_INITIALIZE} set will be internally handled by
108      * {@link AbstractThreadedSyncAdapter} by calling
109      * {@link ContentResolver#setIsSyncable(android.accounts.Account, String, int)} with 1 if it
110      * is currently set to <0.
111      */
AbstractThreadedSyncAdapter(Context context, boolean autoInitialize)112     public AbstractThreadedSyncAdapter(Context context, boolean autoInitialize) {
113         this(context, autoInitialize, false /* allowParallelSyncs */);
114     }
115 
116     /**
117      * Creates an {@link AbstractThreadedSyncAdapter}.
118      * @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.
124      * @param allowParallelSyncs if true then allow syncs for different accounts to run
125      * at the same time, each in their own thread. This must be consistent with the setting
126      * in the SyncAdapter's configuration file.
127      */
AbstractThreadedSyncAdapter(Context context, boolean autoInitialize, boolean allowParallelSyncs)128     public AbstractThreadedSyncAdapter(Context context,
129             boolean autoInitialize, boolean allowParallelSyncs) {
130         mContext = context;
131         mISyncAdapterImpl = new ISyncAdapterImpl();
132         mNumSyncStarts = new AtomicInteger(0);
133         mAutoInitialize = autoInitialize;
134         mAllowParallelSyncs = allowParallelSyncs;
135     }
136 
getContext()137     public Context getContext() {
138         return mContext;
139     }
140 
toSyncKey(Account account)141     private Account toSyncKey(Account account) {
142         if (mAllowParallelSyncs) {
143             return account;
144         } else {
145             return null;
146         }
147     }
148 
149     private class ISyncAdapterImpl extends ISyncAdapter.Stub {
startSync(ISyncContext syncContext, String authority, Account account, Bundle extras)150         public void startSync(ISyncContext syncContext, String authority, Account account,
151                 Bundle extras) {
152             final SyncContext syncContextClient = new SyncContext(syncContext);
153 
154             boolean alreadyInProgress;
155             // synchronize to make sure that mSyncThreads doesn't change between when we
156             // check it and when we use it
157             final Account threadsKey = toSyncKey(account);
158             synchronized (mSyncThreadLock) {
159                 if (!mSyncThreads.containsKey(threadsKey)) {
160                     if (mAutoInitialize
161                             && extras != null
162                             && extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)) {
163                         if (ContentResolver.getIsSyncable(account, authority) < 0) {
164                             ContentResolver.setIsSyncable(account, authority, 1);
165                         }
166                         syncContextClient.onFinished(new SyncResult());
167                         return;
168                     }
169                     SyncThread syncThread = new SyncThread(
170                             "SyncAdapterThread-" + mNumSyncStarts.incrementAndGet(),
171                             syncContextClient, authority, account, extras);
172                     mSyncThreads.put(threadsKey, syncThread);
173                     syncThread.start();
174                     alreadyInProgress = false;
175                 } else {
176                     alreadyInProgress = true;
177                 }
178             }
179 
180             // do this outside since we don't want to call back into the syncContext while
181             // holding the synchronization lock
182             if (alreadyInProgress) {
183                 syncContextClient.onFinished(SyncResult.ALREADY_IN_PROGRESS);
184             }
185         }
186 
cancelSync(ISyncContext syncContext)187         public void cancelSync(ISyncContext syncContext) {
188             // synchronize to make sure that mSyncThreads doesn't change between when we
189             // check it and when we use it
190             SyncThread info = null;
191             synchronized (mSyncThreadLock) {
192                 for (SyncThread current : mSyncThreads.values()) {
193                     if (current.mSyncContext.getSyncContextBinder() == syncContext.asBinder()) {
194                         info = current;
195                         break;
196                     }
197                 }
198             }
199             if (info != null) {
200                 if (mAllowParallelSyncs) {
201                     onSyncCanceled(info);
202                 } else {
203                     onSyncCanceled();
204                 }
205             }
206         }
207 
initialize(Account account, String authority)208         public void initialize(Account account, String authority) throws RemoteException {
209             Bundle extras = new Bundle();
210             extras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
211             startSync(null, authority, account, extras);
212         }
213     }
214 
215     /**
216      * The thread that invokes {@link AbstractThreadedSyncAdapter#onPerformSync}. It also acquires
217      * the provider for this sync before calling onPerformSync and releases it afterwards. Cancel
218      * this thread in order to cancel the sync.
219      */
220     private class SyncThread extends Thread {
221         private final SyncContext mSyncContext;
222         private final String mAuthority;
223         private final Account mAccount;
224         private final Bundle mExtras;
225         private final Account mThreadsKey;
226 
SyncThread(String name, SyncContext syncContext, String authority, Account account, Bundle extras)227         private SyncThread(String name, SyncContext syncContext, String authority,
228                 Account account, Bundle extras) {
229             super(name);
230             mSyncContext = syncContext;
231             mAuthority = authority;
232             mAccount = account;
233             mExtras = extras;
234             mThreadsKey = toSyncKey(account);
235         }
236 
237         @Override
run()238         public void run() {
239             Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
240 
241             // Trace this sync instance.  Note, conceptually this should be in
242             // SyncStorageEngine.insertStartSyncEvent(), but the trace functions require unique
243             // threads in order to track overlapping operations, so we'll do it here for now.
244             Trace.traceBegin(Trace.TRACE_TAG_SYNC_MANAGER, mAuthority);
245 
246             SyncResult syncResult = new SyncResult();
247             ContentProviderClient provider = null;
248             try {
249                 if (isCanceled()) {
250                     return;
251                 }
252                 provider = mContext.getContentResolver().acquireContentProviderClient(mAuthority);
253                 if (provider != null) {
254                     AbstractThreadedSyncAdapter.this.onPerformSync(mAccount, mExtras,
255                             mAuthority, provider, syncResult);
256                 } else {
257                     syncResult.databaseError = true;
258                 }
259             } finally {
260                 Trace.traceEnd(Trace.TRACE_TAG_SYNC_MANAGER);
261 
262                 if (provider != null) {
263                     provider.release();
264                 }
265                 if (!isCanceled()) {
266                     mSyncContext.onFinished(syncResult);
267                 }
268                 // synchronize so that the assignment will be seen by other threads
269                 // that also synchronize accesses to mSyncThreads
270                 synchronized (mSyncThreadLock) {
271                     mSyncThreads.remove(mThreadsKey);
272                 }
273             }
274         }
275 
isCanceled()276         private boolean isCanceled() {
277             return Thread.currentThread().isInterrupted();
278         }
279     }
280 
281     /**
282      * @return a reference to the IBinder of the SyncAdapter service.
283      */
getSyncAdapterBinder()284     public final IBinder getSyncAdapterBinder() {
285         return mISyncAdapterImpl.asBinder();
286     }
287 
288     /**
289      * Perform a sync for this account. SyncAdapter-specific parameters may
290      * be specified in extras, which is guaranteed to not be null. Invocations
291      * of this method are guaranteed to be serialized.
292      *
293      * @param account the account that should be synced
294      * @param extras SyncAdapter-specific parameters
295      * @param authority the authority of this sync request
296      * @param provider a ContentProviderClient that points to the ContentProvider for this
297      *   authority
298      * @param syncResult SyncAdapter-specific parameters
299      */
onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult)300     public abstract void onPerformSync(Account account, Bundle extras,
301             String authority, ContentProviderClient provider, SyncResult syncResult);
302 
303     /**
304      * Indicates that a sync operation has been canceled. This will be invoked on a separate
305      * thread than the sync thread and so you must consider the multi-threaded implications
306      * of the work that you do in this method.
307      * <p>
308      * This will only be invoked when the SyncAdapter indicates that it doesn't support
309      * parallel syncs.
310      */
onSyncCanceled()311     public void onSyncCanceled() {
312         final SyncThread syncThread;
313         synchronized (mSyncThreadLock) {
314             syncThread = mSyncThreads.get(null);
315         }
316         if (syncThread != null) {
317             syncThread.interrupt();
318         }
319     }
320 
321     /**
322      * Indicates that a sync operation has been canceled. This will be invoked on a separate
323      * thread than the sync thread and so you must consider the multi-threaded implications
324      * of the work that you do in this method.
325      * <p>
326      * This will only be invoked when the SyncAdapter indicates that it does support
327      * parallel syncs.
328      * @param thread the Thread of the sync that is to be canceled.
329      */
onSyncCanceled(Thread thread)330     public void onSyncCanceled(Thread thread) {
331         thread.interrupt();
332     }
333 }
334