AbstractThreadedSyncAdapter.java revision d5e4fdc8a4743abc0d9fe3cb952a78f9ad078c6b
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.concurrent.atomic.AtomicInteger;
26
27/**
28 * An abstract implementation of a SyncAdapter that spawns a thread to invoke a sync operation.
29 * If a sync operation is already in progress when a startSync() request is received then an error
30 * will be returned to the new request and the existing request will be allowed to continue.
31 * When a startSync() is received and there is no sync operation in progress then a thread
32 * will be started to run the operation and {@link #onPerformSync} will be invoked on that thread.
33 * If a cancelSync() is received that matches an existing sync operation then the thread
34 * that is running that sync operation will be interrupted, which will indicate to the thread
35 * that the sync has been canceled.
36 */
37public abstract class AbstractThreadedSyncAdapter {
38    /**
39     * Kernel event log tag.  Also listed in data/etc/event-log-tags.
40     * @Deprecated
41     */
42    public static final int LOG_SYNC_DETAILS = 2743;
43
44    private final Context mContext;
45    private final AtomicInteger mNumSyncStarts;
46    private final ISyncAdapterImpl mISyncAdapterImpl;
47
48    // all accesses to this member variable must be synchronized on mSyncThreadLock
49    private SyncThread mSyncThread;
50    private final Object mSyncThreadLock = new Object();
51
52    private final boolean mAutoInitialize;
53
54    /**
55     * Creates an {@link AbstractThreadedSyncAdapter}.
56     * @param context the {@link android.content.Context} that this is running within.
57     * @param autoInitialize if true then sync requests that have
58     * {@link ContentResolver#SYNC_EXTRAS_INITIALIZE} set will be internally handled by
59     * {@link AbstractThreadedSyncAdapter} by calling
60     * {@link ContentResolver#setIsSyncable(android.accounts.Account, String, int)} with 1 if it
61     * is currently set to <0.
62     */
63    public AbstractThreadedSyncAdapter(Context context, boolean autoInitialize) {
64        mContext = context;
65        mISyncAdapterImpl = new ISyncAdapterImpl();
66        mNumSyncStarts = new AtomicInteger(0);
67        mSyncThread = null;
68        mAutoInitialize = autoInitialize;
69    }
70
71    public Context getContext() {
72        return mContext;
73    }
74
75    private class ISyncAdapterImpl extends ISyncAdapter.Stub {
76        public void startSync(ISyncContext syncContext, String authority, Account account,
77                Bundle extras) {
78            final SyncContext syncContextClient = new SyncContext(syncContext);
79
80            boolean alreadyInProgress;
81            // synchronize to make sure that mSyncThread doesn't change between when we
82            // check it and when we use it
83            synchronized (mSyncThreadLock) {
84                if (mSyncThread == null) {
85                    if (mAutoInitialize
86                            && extras != null
87                            && extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)) {
88                        if (ContentResolver.getIsSyncable(account, authority) < 0) {
89                            ContentResolver.setIsSyncable(account, authority, 1);
90                        }
91                        syncContextClient.onFinished(new SyncResult());
92                        return;
93                    }
94                    mSyncThread = new SyncThread(
95                            "SyncAdapterThread-" + mNumSyncStarts.incrementAndGet(),
96                            syncContextClient, authority, account, extras);
97                    mSyncThread.start();
98                    alreadyInProgress = false;
99                } else {
100                    alreadyInProgress = true;
101                }
102            }
103
104            // do this outside since we don't want to call back into the syncContext while
105            // holding the synchronization lock
106            if (alreadyInProgress) {
107                syncContextClient.onFinished(SyncResult.ALREADY_IN_PROGRESS);
108            }
109        }
110
111        public void cancelSync(ISyncContext syncContext) {
112            // synchronize to make sure that mSyncThread doesn't change between when we
113            // check it and when we use it
114            final SyncThread syncThread;
115            synchronized (mSyncThreadLock) {
116                syncThread = mSyncThread;
117            }
118            if (syncThread != null
119                    && syncThread.mSyncContext.getSyncContextBinder() == syncContext.asBinder()) {
120                onSyncCanceled();
121            }
122        }
123
124        public void initialize(Account account, String authority) throws RemoteException {
125            Bundle extras = new Bundle();
126            extras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
127            startSync(null, authority, account, extras);
128        }
129    }
130
131    /**
132     * The thread that invokes {@link AbstractThreadedSyncAdapter#onPerformSync}. It also acquires
133     * the provider for this sync before calling onPerformSync and releases it afterwards. Cancel
134     * this thread in order to cancel the sync.
135     */
136    private class SyncThread extends Thread {
137        private final SyncContext mSyncContext;
138        private final String mAuthority;
139        private final Account mAccount;
140        private final Bundle mExtras;
141
142        private SyncThread(String name, SyncContext syncContext, String authority,
143                Account account, Bundle extras) {
144            super(name);
145            mSyncContext = syncContext;
146            mAuthority = authority;
147            mAccount = account;
148            mExtras = extras;
149        }
150
151        public void run() {
152            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
153
154            if (isCanceled()) {
155                return;
156            }
157
158            SyncResult syncResult = new SyncResult();
159            ContentProviderClient provider = null;
160            try {
161                provider = mContext.getContentResolver().acquireContentProviderClient(mAuthority);
162                if (provider != null) {
163                    AbstractThreadedSyncAdapter.this.onPerformSync(mAccount, mExtras,
164                            mAuthority, provider, syncResult);
165                } else {
166                    syncResult.databaseError = true;
167                }
168            } finally {
169                if (provider != null) {
170                    provider.release();
171                }
172                if (!isCanceled()) {
173                    mSyncContext.onFinished(syncResult);
174                }
175                // synchronize so that the assignment will be seen by other threads
176                // that also synchronize accesses to mSyncThread
177                synchronized (mSyncThreadLock) {
178                    mSyncThread = null;
179                }
180            }
181        }
182
183        private boolean isCanceled() {
184            return Thread.currentThread().isInterrupted();
185        }
186    }
187
188    /**
189     * @return a reference to the IBinder of the SyncAdapter service.
190     */
191    public final IBinder getSyncAdapterBinder() {
192        return mISyncAdapterImpl.asBinder();
193    }
194
195    /**
196     * Perform a sync for this account. SyncAdapter-specific parameters may
197     * be specified in extras, which is guaranteed to not be null. Invocations
198     * of this method are guaranteed to be serialized.
199     *
200     * @param account the account that should be synced
201     * @param extras SyncAdapter-specific parameters
202     * @param authority the authority of this sync request
203     * @param provider a ContentProviderClient that points to the ContentProvider for this
204     *   authority
205     * @param syncResult SyncAdapter-specific parameters
206     */
207    public abstract void onPerformSync(Account account, Bundle extras,
208            String authority, ContentProviderClient provider, SyncResult syncResult);
209
210    /**
211     * Indicates that a sync operation has been canceled. This will be invoked on a separate
212     * thread than the sync thread and so you must consider the multi-threaded implications
213     * of the work that you do in this method.
214     *
215     */
216    public void onSyncCanceled() {
217        final SyncThread syncThread;
218        synchronized (mSyncThreadLock) {
219            syncThread = mSyncThread;
220        }
221        if (syncThread != null) {
222            syncThread.interrupt();
223        }
224    }
225}
226