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