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