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