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