AbstractThreadedSyncAdapter.java revision 3cff76aaa893049d02467a231d477e86a0f80daa
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    class ISyncAdapterImpl extends ISyncAdapter.Stub {
68        public void startSync(ISyncContext syncContext, String authority, Account account,
69                Bundle extras) {
70            final SyncContext syncContextClient = new SyncContext(syncContext);
71
72            boolean alreadyInProgress;
73            // synchronize to make sure that mSyncThread doesn't change between when we
74            // check it and when we use it
75            synchronized (mSyncThreadLock) {
76                if (mSyncThread == null) {
77                    if (mAutoInitialize
78                            && extras != null
79                            && extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)) {
80                        if (ContentResolver.getIsSyncable(account, authority) < 0) {
81                            ContentResolver.setIsSyncable(account, authority, 1);
82                        }
83                        syncContextClient.onFinished(new SyncResult());
84                        return;
85                    }
86                    mSyncThread = new SyncThread(
87                            "SyncAdapterThread-" + mNumSyncStarts.incrementAndGet(),
88                            syncContextClient, authority, account, extras);
89                    mSyncThread.start();
90                    alreadyInProgress = false;
91                } else {
92                    alreadyInProgress = true;
93                }
94            }
95
96            // do this outside since we don't want to call back into the syncContext while
97            // holding the synchronization lock
98            if (alreadyInProgress) {
99                syncContextClient.onFinished(SyncResult.ALREADY_IN_PROGRESS);
100            }
101        }
102
103        public void cancelSync(ISyncContext syncContext) {
104            // synchronize to make sure that mSyncThread doesn't change between when we
105            // check it and when we use it
106            synchronized (mSyncThreadLock) {
107                if (mSyncThread != null
108                        && mSyncThread.mSyncContext.getISyncContext() == syncContext) {
109                    mSyncThread.interrupt();
110                }
111            }
112        }
113    }
114
115    /**
116     * The thread that invokes performSync(). It also acquires the provider for this sync
117     * before calling performSync and releases it afterwards. Cancel this thread in order to
118     * cancel the sync.
119     */
120    private class SyncThread extends Thread {
121        private final SyncContext mSyncContext;
122        private final String mAuthority;
123        private final Account mAccount;
124        private final Bundle mExtras;
125
126        private SyncThread(String name, SyncContext syncContext, String authority,
127                Account account, Bundle extras) {
128            super(name);
129            mSyncContext = syncContext;
130            mAuthority = authority;
131            mAccount = account;
132            mExtras = extras;
133        }
134
135        public void run() {
136            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
137
138            if (isCanceled()) {
139                return;
140            }
141
142            SyncResult syncResult = new SyncResult();
143            ContentProviderClient provider = null;
144            try {
145                provider = mContext.getContentResolver().acquireContentProviderClient(mAuthority);
146                if (provider != null) {
147                    AbstractThreadedSyncAdapter.this.performSync(mAccount, mExtras,
148                            mAuthority, provider, syncResult);
149                } else {
150                    // TODO(fredq) update the syncResults to indicate that we were unable to
151                    // find the provider. maybe with a ProviderError?
152                }
153            } finally {
154                if (provider != null) {
155                    provider.release();
156                }
157                if (!isCanceled()) {
158                    mSyncContext.onFinished(syncResult);
159                }
160                // synchronize so that the assignment will be seen by other threads
161                // that also synchronize accesses to mSyncThread
162                synchronized (mSyncThreadLock) {
163                    mSyncThread = null;
164                }
165            }
166        }
167
168        private boolean isCanceled() {
169            return Thread.currentThread().isInterrupted();
170        }
171    }
172
173    /**
174     * @return a reference to the ISyncAdapter interface into this SyncAdapter implementation.
175     */
176    public final ISyncAdapter getISyncAdapter() {
177        return mISyncAdapterImpl;
178    }
179
180    /**
181     * Perform a sync for this account. SyncAdapter-specific parameters may
182     * be specified in extras, which is guaranteed to not be null. Invocations
183     * of this method are guaranteed to be serialized.
184     *
185     * @param account the account that should be synced
186     * @param extras SyncAdapter-specific parameters
187     * @param authority the authority of this sync request
188     * @param provider a ContentProviderClient that points to the ContentProvider for this
189     *   authority
190     * @param syncResult SyncAdapter-specific parameters
191     */
192    public abstract void performSync(Account account, Bundle extras,
193            String authority, ContentProviderClient provider, SyncResult syncResult);
194}