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