AbstractThreadedSyncAdapter.java revision 97ef7637c6799e72956db8e08192539f1b1942f6
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.net.TrafficStats;
21import android.os.Bundle;
22import android.os.IBinder;
23import android.os.Process;
24import android.os.RemoteException;
25import android.util.EventLog;
26
27import java.util.concurrent.atomic.AtomicInteger;
28
29/**
30 * An abstract implementation of a SyncAdapter that spawns a thread to invoke a sync operation.
31 * If a sync operation is already in progress when a startSync() request is received then an error
32 * will be returned to the new request and the existing request will be allowed to continue.
33 * When a startSync() is received and there is no sync operation in progress then a thread
34 * will be started to run the operation and {@link #onPerformSync} will be invoked on that thread.
35 * If a cancelSync() is received that matches an existing sync operation then the thread
36 * that is running that sync operation will be interrupted, which will indicate to the thread
37 * that the sync has been canceled.
38 */
39public abstract class AbstractThreadedSyncAdapter {
40    /**
41     * Kernel event log tag.  Also listed in data/etc/event-log-tags.
42     * @Deprecated
43     */
44    public static final int LOG_SYNC_DETAILS = 2743;
45
46    private final Context mContext;
47    private final AtomicInteger mNumSyncStarts;
48    private final ISyncAdapterImpl mISyncAdapterImpl;
49
50    // all accesses to this member variable must be synchronized on mSyncThreadLock
51    private SyncThread mSyncThread;
52    private final Object mSyncThreadLock = new Object();
53
54    private final boolean mAutoInitialize;
55
56    /**
57     * Creates an {@link AbstractThreadedSyncAdapter}.
58     * @param context the {@link android.content.Context} that this is running within.
59     * @param autoInitialize if true then sync requests that have
60     * {@link ContentResolver#SYNC_EXTRAS_INITIALIZE} set will be internally handled by
61     * {@link AbstractThreadedSyncAdapter} by calling
62     * {@link ContentResolver#setIsSyncable(android.accounts.Account, String, int)} with 1 if it
63     * is currently set to <0.
64     */
65    public AbstractThreadedSyncAdapter(Context context, boolean autoInitialize) {
66        mContext = context;
67        mISyncAdapterImpl = new ISyncAdapterImpl();
68        mNumSyncStarts = new AtomicInteger(0);
69        mSyncThread = null;
70        mAutoInitialize = autoInitialize;
71    }
72
73    public Context getContext() {
74        return mContext;
75    }
76
77    private class ISyncAdapterImpl extends ISyncAdapter.Stub {
78        public void startSync(ISyncContext syncContext, String authority, Account account,
79                Bundle extras) {
80            final SyncContext syncContextClient = new SyncContext(syncContext);
81
82            boolean alreadyInProgress;
83            // synchronize to make sure that mSyncThread doesn't change between when we
84            // check it and when we use it
85            synchronized (mSyncThreadLock) {
86                if (mSyncThread == null) {
87                    if (mAutoInitialize
88                            && extras != null
89                            && extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)) {
90                        if (ContentResolver.getIsSyncable(account, authority) < 0) {
91                            ContentResolver.setIsSyncable(account, authority, 1);
92                        }
93                        syncContextClient.onFinished(new SyncResult());
94                        return;
95                    }
96                    mSyncThread = new SyncThread(
97                            "SyncAdapterThread-" + mNumSyncStarts.incrementAndGet(),
98                            syncContextClient, authority, account, extras);
99                    mSyncThread.start();
100                    alreadyInProgress = false;
101                } else {
102                    alreadyInProgress = true;
103                }
104            }
105
106            // do this outside since we don't want to call back into the syncContext while
107            // holding the synchronization lock
108            if (alreadyInProgress) {
109                syncContextClient.onFinished(SyncResult.ALREADY_IN_PROGRESS);
110            }
111        }
112
113        public void cancelSync(ISyncContext syncContext) {
114            // synchronize to make sure that mSyncThread doesn't change between when we
115            // check it and when we use it
116            synchronized (mSyncThreadLock) {
117                if (mSyncThread != null
118                        && mSyncThread.mSyncContext.getSyncContextBinder()
119                        == syncContext.asBinder()) {
120                    mSyncThread.interrupt();
121                }
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