1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
|
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.content;
import android.app.Service;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Process;
import android.os.Trace;
import android.util.SparseArray;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
/**
* Simplified @link android.content.AbstractThreadedSyncAdapter. Folds that
* behaviour into a service to which the system can bind when requesting an
* anonymous (providerless/accountless) sync.
* <p>
* In order to perform an anonymous sync operation you must extend this service, implementing the
* abstract methods. This service must be declared in the application's manifest as usual. You
* can use this service for other work, however you <b> must not </b> override the onBind() method
* unless you know what you're doing, which limits the usefulness of this service for other work.
* <p>A {@link SyncService} can either be active or inactive. Different to an
* {@link AbstractThreadedSyncAdapter}, there is no
* {@link ContentResolver#setSyncAutomatically(android.accounts.Account account, String provider, boolean sync)},
* as well as no concept of initialisation (you can handle your own if needed).
*
* <pre>
* <service android:name=".MySyncService"/>
* </pre>
* Like @link android.content.AbstractThreadedSyncAdapter this service supports
* multiple syncs at the same time. Each incoming startSync() with a unique tag
* will spawn a thread to do the work of that sync. If startSync() is called
* with a tag that already exists, a SyncResult.ALREADY_IN_PROGRESS is returned.
* Remember that your service will spawn multiple threads if you schedule multiple syncs
* at once, so if you mutate local objects you must ensure synchronization.
*/
public abstract class SyncService extends Service {
private static final String TAG = "SyncService";
private final SyncAdapterImpl mSyncAdapter = new SyncAdapterImpl();
/** Keep track of on-going syncs, keyed by bundle. */
@GuardedBy("mSyncThreadLock")
private final SparseArray<SyncThread>
mSyncThreads = new SparseArray<SyncThread>();
/** Lock object for accessing the SyncThreads HashMap. */
private final Object mSyncThreadLock = new Object();
/**
* Default key for if this sync service does not support parallel operations. Currently not
* sure if null keys will make it into the ArrayMap for KLP, so keeping our default for now.
*/
private static final int KEY_DEFAULT = 0;
/** Identifier for this sync service. */
private ComponentName mServiceComponent;
/** {@hide} */
public IBinder onBind(Intent intent) {
mServiceComponent = new ComponentName(this, getClass());
return mSyncAdapter.asBinder();
}
/** {@hide} */
private class SyncAdapterImpl extends ISyncServiceAdapter.Stub {
@Override
public void startSync(ISyncContext syncContext, Bundle extras) {
// Wrap the provided Sync Context because it may go away by the time
// we call it.
final SyncContext syncContextClient = new SyncContext(syncContext);
boolean alreadyInProgress = false;
final int extrasAsKey = extrasToKey(extras);
synchronized (mSyncThreadLock) {
if (mSyncThreads.get(extrasAsKey) == null) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "starting sync for : " + mServiceComponent);
}
// Start sync.
SyncThread syncThread = new SyncThread(syncContextClient, extras);
mSyncThreads.put(extrasAsKey, syncThread);
syncThread.start();
} else {
// Don't want to call back to SyncManager while still
// holding lock.
alreadyInProgress = true;
}
}
if (alreadyInProgress) {
syncContextClient.onFinished(SyncResult.ALREADY_IN_PROGRESS);
}
}
/**
* Used by the SM to cancel a specific sync using the
* com.android.server.content.SyncManager.ActiveSyncContext as a handle.
*/
@Override
public void cancelSync(ISyncContext syncContext) {
SyncThread runningSync = null;
synchronized (mSyncThreadLock) {
for (int i = 0; i < mSyncThreads.size(); i++) {
SyncThread thread = mSyncThreads.valueAt(i);
if (thread.mSyncContext.getSyncContextBinder() == syncContext.asBinder()) {
runningSync = thread;
break;
}
}
}
if (runningSync != null) {
runningSync.interrupt();
}
}
}
/**
*
* @param extras Bundle for which to compute hash
* @return an integer hash that is equal to that of another bundle if they both contain the
* same key -> value mappings, however, not necessarily in order.
* Based on the toString() representation of the value mapped.
*/
private int extrasToKey(Bundle extras) {
int hash = KEY_DEFAULT; // Empty bundle, or no parallel operations enabled.
if (parallelSyncsEnabled()) {
for (String key : extras.keySet()) {
String mapping = key + " " + extras.get(key).toString();
hash += mapping.hashCode();
}
}
return hash;
}
/**
* {@hide}
* Similar to {@link android.content.AbstractThreadedSyncAdapter.SyncThread}. However while
* the ATSA considers an already in-progress sync to be if the account provided is currently
* syncing, this anonymous sync has no notion of account and considers a sync unique if the
* provided bundle is different.
*/
private class SyncThread extends Thread {
private final SyncContext mSyncContext;
private final Bundle mExtras;
private final int mThreadsKey;
public SyncThread(SyncContext syncContext, Bundle extras) {
mSyncContext = syncContext;
mExtras = extras;
mThreadsKey = extrasToKey(extras);
}
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
Trace.traceBegin(Trace.TRACE_TAG_SYNC_MANAGER, getApplication().getPackageName());
SyncResult syncResult = new SyncResult();
try {
if (isCancelled()) return;
// Run the sync.
SyncService.this.onPerformSync(mExtras, syncResult);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_SYNC_MANAGER);
if (!isCancelled()) {
mSyncContext.onFinished(syncResult);
}
// Synchronize so that the assignment will be seen by other
// threads that also synchronize accesses to mSyncThreads.
synchronized (mSyncThreadLock) {
mSyncThreads.remove(mThreadsKey);
}
}
}
private boolean isCancelled() {
return Thread.currentThread().isInterrupted();
}
}
/**
* Initiate an anonymous sync using this service. SyncAdapter-specific
* parameters may be specified in extras, which is guaranteed to not be
* null.
*/
public abstract void onPerformSync(Bundle extras, SyncResult syncResult);
/**
* Override this function to indicated whether you want to support parallel syncs.
* <p>If you override and return true multiple threads will be spawned within your Service to
* handle each concurrent sync request.
*
* @return false to indicate that this service does not support parallel operations by default.
*/
protected boolean parallelSyncsEnabled() {
return false;
}
}
|