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