blob: 4df998cddc4ed6e834012574fec5f0fcf123202b [file] [log] [blame]
Matthew Williamsfa774182013-06-18 15:44:11 -07001/*
2 * Copyright (C) 2013 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.app.Service;
20import android.os.Bundle;
21import android.os.IBinder;
22import android.os.Process;
23import android.os.Trace;
Matthew Williams56dbf8f2013-07-26 12:56:39 -070024import android.util.SparseArray;
25import android.util.Log;
Matthew Williamsfa774182013-06-18 15:44:11 -070026
27import com.android.internal.annotations.GuardedBy;
28
Matthew Williamsfa774182013-06-18 15:44:11 -070029/**
30 * Simplified @link android.content.AbstractThreadedSyncAdapter. Folds that
31 * behaviour into a service to which the system can bind when requesting an
32 * anonymous (providerless/accountless) sync.
33 * <p>
Matthew Williams56dbf8f2013-07-26 12:56:39 -070034 * In order to perform an anonymous sync operation you must extend this service, implementing the
35 * abstract methods. This service must be declared in the application's manifest as usual. You
36 * can use this service for other work, however you <b> must not </b> override the onBind() method
37 * unless you know what you're doing, which limits the usefulness of this service for other work.
38 * <p>A {@link SyncService} can either be active or inactive. Different to an
39 * {@link AbstractThreadedSyncAdapter}, there is no
40 * {@link ContentResolver#setSyncAutomatically(android.accounts.Account account, String provider, boolean sync)},
41 * as well as no concept of initialisation (you can handle your own if needed).
Matthew Williamsfa774182013-06-18 15:44:11 -070042 *
43 * <pre>
Matthew Williams56dbf8f2013-07-26 12:56:39 -070044 * &lt;service android:name=".MySyncService"/&gt;
Matthew Williamsfa774182013-06-18 15:44:11 -070045 * </pre>
46 * Like @link android.content.AbstractThreadedSyncAdapter this service supports
47 * multiple syncs at the same time. Each incoming startSync() with a unique tag
48 * will spawn a thread to do the work of that sync. If startSync() is called
49 * with a tag that already exists, a SyncResult.ALREADY_IN_PROGRESS is returned.
50 * Remember that your service will spawn multiple threads if you schedule multiple syncs
51 * at once, so if you mutate local objects you must ensure synchronization.
52 */
53public abstract class SyncService extends Service {
Matthew Williams56dbf8f2013-07-26 12:56:39 -070054 private static final String TAG = "SyncService";
Matthew Williamsfa774182013-06-18 15:44:11 -070055
Matthew Williams56dbf8f2013-07-26 12:56:39 -070056 private final SyncAdapterImpl mSyncAdapter = new SyncAdapterImpl();
Matthew Williamsfa774182013-06-18 15:44:11 -070057
Matthew Williams56dbf8f2013-07-26 12:56:39 -070058 /** Keep track of on-going syncs, keyed by bundle. */
59 @GuardedBy("mSyncThreadLock")
60 private final SparseArray<SyncThread>
61 mSyncThreads = new SparseArray<SyncThread>();
Matthew Williamsfa774182013-06-18 15:44:11 -070062 /** Lock object for accessing the SyncThreads HashMap. */
63 private final Object mSyncThreadLock = new Object();
Matthew Williams56dbf8f2013-07-26 12:56:39 -070064 /**
65 * Default key for if this sync service does not support parallel operations. Currently not
66 * sure if null keys will make it into the ArrayMap for KLP, so keeping our default for now.
67 */
68 private static final int KEY_DEFAULT = 0;
69 /** Identifier for this sync service. */
70 private ComponentName mServiceComponent;
Matthew Williamsfa774182013-06-18 15:44:11 -070071
Matthew Williams56dbf8f2013-07-26 12:56:39 -070072 /** {@hide} */
Matthew Williamsfa774182013-06-18 15:44:11 -070073 public IBinder onBind(Intent intent) {
Matthew Williams56dbf8f2013-07-26 12:56:39 -070074 mServiceComponent = new ComponentName(this, getClass());
Matthew Williamsfa774182013-06-18 15:44:11 -070075 return mSyncAdapter.asBinder();
76 }
77
78 /** {@hide} */
Matthew Williams56dbf8f2013-07-26 12:56:39 -070079 private class SyncAdapterImpl extends ISyncServiceAdapter.Stub {
Matthew Williamsfa774182013-06-18 15:44:11 -070080 @Override
81 public void startSync(ISyncContext syncContext, Bundle extras) {
82 // Wrap the provided Sync Context because it may go away by the time
83 // we call it.
84 final SyncContext syncContextClient = new SyncContext(syncContext);
85 boolean alreadyInProgress = false;
Matthew Williams56dbf8f2013-07-26 12:56:39 -070086 final int extrasAsKey = extrasToKey(extras);
Matthew Williamsfa774182013-06-18 15:44:11 -070087 synchronized (mSyncThreadLock) {
Matthew Williams8ef22042013-07-26 12:56:39 -070088 if (mSyncThreads.get(extrasAsKey) == null) {
89 if (Log.isLoggable(TAG, Log.VERBOSE)) {
90 Log.v(TAG, "starting sync for : " + mServiceComponent);
91 }
Matthew Williams56dbf8f2013-07-26 12:56:39 -070092 // Start sync.
93 SyncThread syncThread = new SyncThread(syncContextClient, extras);
94 mSyncThreads.put(extrasAsKey, syncThread);
95 syncThread.start();
96 } else {
Matthew Williamsfa774182013-06-18 15:44:11 -070097 // Don't want to call back to SyncManager while still
98 // holding lock.
99 alreadyInProgress = true;
Matthew Williamsfa774182013-06-18 15:44:11 -0700100 }
101 }
102 if (alreadyInProgress) {
103 syncContextClient.onFinished(SyncResult.ALREADY_IN_PROGRESS);
104 }
105 }
106
107 /**
Matthew Williams56dbf8f2013-07-26 12:56:39 -0700108 * Used by the SM to cancel a specific sync using the
109 * com.android.server.content.SyncManager.ActiveSyncContext as a handle.
Matthew Williamsfa774182013-06-18 15:44:11 -0700110 */
111 @Override
112 public void cancelSync(ISyncContext syncContext) {
Matthew Williams56dbf8f2013-07-26 12:56:39 -0700113 SyncThread runningSync = null;
Matthew Williamsfa774182013-06-18 15:44:11 -0700114 synchronized (mSyncThreadLock) {
Matthew Williams56dbf8f2013-07-26 12:56:39 -0700115 for (int i = 0; i < mSyncThreads.size(); i++) {
116 SyncThread thread = mSyncThreads.valueAt(i);
Matthew Williamsfa774182013-06-18 15:44:11 -0700117 if (thread.mSyncContext.getSyncContextBinder() == syncContext.asBinder()) {
118 runningSync = thread;
119 break;
120 }
121 }
122 }
123 if (runningSync != null) {
124 runningSync.interrupt();
125 }
126 }
127 }
128
129 /**
Matthew Williams56dbf8f2013-07-26 12:56:39 -0700130 *
131 * @param extras Bundle for which to compute hash
132 * @return an integer hash that is equal to that of another bundle if they both contain the
133 * same key -> value mappings, however, not necessarily in order.
134 * Based on the toString() representation of the value mapped.
135 */
136 private int extrasToKey(Bundle extras) {
137 int hash = KEY_DEFAULT; // Empty bundle, or no parallel operations enabled.
138 if (parallelSyncsEnabled()) {
139 for (String key : extras.keySet()) {
140 String mapping = key + " " + extras.get(key).toString();
141 hash += mapping.hashCode();
142 }
143 }
144 return hash;
145 }
146
147 /**
Matthew Williamsfa774182013-06-18 15:44:11 -0700148 * {@hide}
149 * Similar to {@link android.content.AbstractThreadedSyncAdapter.SyncThread}. However while
150 * the ATSA considers an already in-progress sync to be if the account provided is currently
Matthew Williams56dbf8f2013-07-26 12:56:39 -0700151 * syncing, this anonymous sync has no notion of account and considers a sync unique if the
152 * provided bundle is different.
Matthew Williamsfa774182013-06-18 15:44:11 -0700153 */
Matthew Williams56dbf8f2013-07-26 12:56:39 -0700154 private class SyncThread extends Thread {
Matthew Williamsfa774182013-06-18 15:44:11 -0700155 private final SyncContext mSyncContext;
156 private final Bundle mExtras;
Matthew Williams56dbf8f2013-07-26 12:56:39 -0700157 private final int mThreadsKey;
Matthew Williamsfa774182013-06-18 15:44:11 -0700158
Matthew Williams56dbf8f2013-07-26 12:56:39 -0700159 public SyncThread(SyncContext syncContext, Bundle extras) {
Matthew Williamsfa774182013-06-18 15:44:11 -0700160 mSyncContext = syncContext;
161 mExtras = extras;
Matthew Williams56dbf8f2013-07-26 12:56:39 -0700162 mThreadsKey = extrasToKey(extras);
Matthew Williamsfa774182013-06-18 15:44:11 -0700163 }
164
165 @Override
166 public void run() {
167 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
168
169 Trace.traceBegin(Trace.TRACE_TAG_SYNC_MANAGER, getApplication().getPackageName());
170
171 SyncResult syncResult = new SyncResult();
172 try {
Matthew Williams56dbf8f2013-07-26 12:56:39 -0700173 if (isCancelled()) return;
174 // Run the sync.
Matthew Williamsfa774182013-06-18 15:44:11 -0700175 SyncService.this.onPerformSync(mExtras, syncResult);
176 } finally {
177 Trace.traceEnd(Trace.TRACE_TAG_SYNC_MANAGER);
178 if (!isCancelled()) {
179 mSyncContext.onFinished(syncResult);
180 }
181 // Synchronize so that the assignment will be seen by other
Matthew Williams56dbf8f2013-07-26 12:56:39 -0700182 // threads that also synchronize accesses to mSyncThreads.
Matthew Williamsfa774182013-06-18 15:44:11 -0700183 synchronized (mSyncThreadLock) {
Matthew Williams56dbf8f2013-07-26 12:56:39 -0700184 mSyncThreads.remove(mThreadsKey);
Matthew Williamsfa774182013-06-18 15:44:11 -0700185 }
186 }
187 }
188
189 private boolean isCancelled() {
190 return Thread.currentThread().isInterrupted();
191 }
192 }
193
194 /**
195 * Initiate an anonymous sync using this service. SyncAdapter-specific
196 * parameters may be specified in extras, which is guaranteed to not be
197 * null.
198 */
199 public abstract void onPerformSync(Bundle extras, SyncResult syncResult);
200
Matthew Williams56dbf8f2013-07-26 12:56:39 -0700201 /**
202 * Override this function to indicated whether you want to support parallel syncs.
203 * <p>If you override and return true multiple threads will be spawned within your Service to
204 * handle each concurrent sync request.
205 *
206 * @return false to indicate that this service does not support parallel operations by default.
207 */
208 protected boolean parallelSyncsEnabled() {
209 return false;
210 }
Matthew Williamsfa774182013-06-18 15:44:11 -0700211}