• 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 {
150         @Override
startSync(ISyncContext syncContext, String authority, Account account, Bundle extras)151         public void startSync(ISyncContext syncContext, String authority, Account account,
152                 Bundle extras) {
153             final SyncContext syncContextClient = new SyncContext(syncContext);
154 
155             boolean alreadyInProgress;
156             // synchronize to make sure that mSyncThreads doesn't change between when we
157             // check it and when we use it
158             final Account threadsKey = toSyncKey(account);
159             synchronized (mSyncThreadLock) {
160                 if (!mSyncThreads.containsKey(threadsKey)) {
161                     if (mAutoInitialize
162                             && extras != null
163                             && extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)) {
164                         try {
165                             if (ContentResolver.getIsSyncable(account, authority) < 0) {
166                                 ContentResolver.setIsSyncable(account, authority, 1);
167                             }
168                         } finally {
169                             syncContextClient.onFinished(new SyncResult());
170                         }
171                         return;
172                     }
173                     SyncThread syncThread = new SyncThread(
174                             "SyncAdapterThread-" + mNumSyncStarts.incrementAndGet(),
175                             syncContextClient, authority, account, extras);
176                     mSyncThreads.put(threadsKey, syncThread);
177                     syncThread.start();
178                     alreadyInProgress = false;
179                 } else {
180                     alreadyInProgress = true;
181                 }
182             }
183 
184             // do this outside since we don't want to call back into the syncContext while
185             // holding the synchronization lock
186             if (alreadyInProgress) {
187                 syncContextClient.onFinished(SyncResult.ALREADY_IN_PROGRESS);
188             }
189         }
190 
191         @Override
cancelSync(ISyncContext syncContext)192         public void cancelSync(ISyncContext syncContext) {
193             // synchronize to make sure that mSyncThreads doesn't change between when we
194             // check it and when we use it
195             SyncThread info = null;
196             synchronized (mSyncThreadLock) {
197                 for (SyncThread current : mSyncThreads.values()) {
198                     if (current.mSyncContext.getSyncContextBinder() == syncContext.asBinder()) {
199                         info = current;
200                         break;
201                     }
202                 }
203             }
204             if (info != null) {
205                 if (mAllowParallelSyncs) {
206                     onSyncCanceled(info);
207                 } else {
208                     onSyncCanceled();
209                 }
210             }
211         }
212 
initialize(Account account, String authority)213         public void initialize(Account account, String authority) throws RemoteException {
214             Bundle extras = new Bundle();
215             extras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
216             startSync(null, authority, account, extras);
217         }
218     }
219 
220     /**
221      * The thread that invokes {@link AbstractThreadedSyncAdapter#onPerformSync}. It also acquires
222      * the provider for this sync before calling onPerformSync and releases it afterwards. Cancel
223      * this thread in order to cancel the sync.
224      */
225     private class SyncThread extends Thread {
226         private final SyncContext mSyncContext;
227         private final String mAuthority;
228         private final Account mAccount;
229         private final Bundle mExtras;
230         private final Account mThreadsKey;
231 
SyncThread(String name, SyncContext syncContext, String authority, Account account, Bundle extras)232         private SyncThread(String name, SyncContext syncContext, String authority,
233                 Account account, Bundle extras) {
234             super(name);
235             mSyncContext = syncContext;
236             mAuthority = authority;
237             mAccount = account;
238             mExtras = extras;
239             mThreadsKey = toSyncKey(account);
240         }
241 
242         @Override
run()243         public void run() {
244             Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
245 
246             // Trace this sync instance.  Note, conceptually this should be in
247             // SyncStorageEngine.insertStartSyncEvent(), but the trace functions require unique
248             // threads in order to track overlapping operations, so we'll do it here for now.
249             Trace.traceBegin(Trace.TRACE_TAG_SYNC_MANAGER, mAuthority);
250 
251             SyncResult syncResult = new SyncResult();
252             ContentProviderClient provider = null;
253             try {
254                 if (isCanceled()) {
255                     return;
256                 }
257                 provider = mContext.getContentResolver().acquireContentProviderClient(mAuthority);
258                 if (provider != null) {
259                     AbstractThreadedSyncAdapter.this.onPerformSync(mAccount, mExtras,
260                             mAuthority, provider, syncResult);
261                 } else {
262                     syncResult.databaseError = true;
263                 }
264             } finally {
265                 Trace.traceEnd(Trace.TRACE_TAG_SYNC_MANAGER);
266 
267                 if (provider != null) {
268                     provider.release();
269                 }
270                 if (!isCanceled()) {
271                     mSyncContext.onFinished(syncResult);
272                 }
273                 // synchronize so that the assignment will be seen by other threads
274                 // that also synchronize accesses to mSyncThreads
275                 synchronized (mSyncThreadLock) {
276                     mSyncThreads.remove(mThreadsKey);
277                 }
278             }
279         }
280 
isCanceled()281         private boolean isCanceled() {
282             return Thread.currentThread().isInterrupted();
283         }
284     }
285 
286     /**
287      * @return a reference to the IBinder of the SyncAdapter service.
288      */
getSyncAdapterBinder()289     public final IBinder getSyncAdapterBinder() {
290         return mISyncAdapterImpl.asBinder();
291     }
292 
293     /**
294      * Perform a sync for this account. SyncAdapter-specific parameters may
295      * be specified in extras, which is guaranteed to not be null. Invocations
296      * of this method are guaranteed to be serialized.
297      *
298      * @param account the account that should be synced
299      * @param extras SyncAdapter-specific parameters
300      * @param authority the authority of this sync request
301      * @param provider a ContentProviderClient that points to the ContentProvider for this
302      *   authority
303      * @param syncResult SyncAdapter-specific parameters
304      */
onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult)305     public abstract void onPerformSync(Account account, Bundle extras,
306             String authority, ContentProviderClient provider, SyncResult syncResult);
307 
308     /**
309      * Indicates that a sync operation has been canceled. This will be invoked on a separate
310      * thread than the sync thread and so you must consider the multi-threaded implications
311      * of the work that you do in this method.
312      * <p>
313      * This will only be invoked when the SyncAdapter indicates that it doesn't support
314      * parallel syncs.
315      */
onSyncCanceled()316     public void onSyncCanceled() {
317         final SyncThread syncThread;
318         synchronized (mSyncThreadLock) {
319             syncThread = mSyncThreads.get(null);
320         }
321         if (syncThread != null) {
322             syncThread.interrupt();
323         }
324     }
325 
326     /**
327      * Indicates that a sync operation has been canceled. This will be invoked on a separate
328      * thread than the sync thread and so you must consider the multi-threaded implications
329      * of the work that you do in this method.
330      * <p>
331      * This will only be invoked when the SyncAdapter indicates that it does support
332      * parallel syncs.
333      * @param thread the Thread of the sync that is to be canceled.
334      */
onSyncCanceled(Thread thread)335     public void onSyncCanceled(Thread thread) {
336         thread.interrupt();
337     }
338 }
339