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