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