-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
/
CoreService.java
437 lines (386 loc) · 16.3 KB
/
CoreService.java
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
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
package com.fsck.k9.service;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicInteger;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.os.PowerManager;
import android.util.Log;
import com.fsck.k9.K9;
import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.mail.power.TracingPowerManager;
import com.fsck.k9.mail.power.TracingPowerManager.TracingWakeLock;
/**
* {@code CoreService} is the base class for all K-9 Services.
*
* <p>
* An Android service is a way to model a part of an application that needs to accomplish certain
* tasks without the UI part of the application being necessarily active (of course an application
* could also be a pure service, without any UI; this is not the case of K-9). By declaring a
* service and starting it, the OS knows that the application has work to do and should avoid
* killing the process.
* </p><p>
* A service's main purpose is to do some task (usually in the background) which requires one or
* more threads. The thread that starts the service is the same as the UI thread of the process. It
* should thus not be used to run the tasks.
* </p><p>
* CoreService is providing the execution plumbing for background tasks including the required
* thread and task queuing for all K9 services to use.
* </p><p>
* A service is supposed to run only as long as it has some work to do whether that work is active
* processing or some just some monitoring, like listening on a network port for incoming connections
* or listing on an open network connection for incoming data (push mechanism).
* </p><p>
* To make sure the service is running only when required, is must be shutdown after tasks are
* done. As the execution of tasks is abstracted away in this class, it also handles proper
* shutdown. If a Service doesn't want this, it needs to call {@code enableAutoShutdown(true)} in
* its {@link Service#onCreate()} method.
* </p><p>
* While a service is running its tasks, it is usually not a good idea to let the device go to
* sleep mode. Wake locks are used to avoid this. CoreService provides a central registry
* (singleton) that can be used application-wide to store wake locks.
* </p><p>
* In short, CoreService provides the following features to K-9 Services:
* <ul>
* <li>task execution and queuing</li>
* <li>Service life cycle management (ensures the service is stopped when not needed anymore);
* enabled by default</li>
* <li>wake lock registry and management</li>
* </ul>
*/
public abstract class CoreService extends Service {
public static final String WAKE_LOCK_ID = "com.fsck.k9.service.CoreService.wakeLockId";
private static ConcurrentHashMap<Integer, TracingWakeLock> sWakeLocks =
new ConcurrentHashMap<Integer, TracingWakeLock>();
private static AtomicInteger sWakeLockSeq = new AtomicInteger(0);
/**
* Threadpool that is used to execute and queue background actions inside the service.
*/
private ExecutorService mThreadPool = null;
/**
* String of the class name used in debug messages.
*/
private final String className = getClass().getName();
/**
* {@code true} if the {@code Service}'s {@link #onDestroy()} method was called. {@code false}
* otherwise.
*
* <p>
* <strong>Note:</strong>
* This is used to ignore (expected) {@link RejectedExecutionException}s thrown by
* {@link ExecutorService#execute(Runnable)} after the service (and with that, the thread pool)
* was shut down.
* </p>
*/
private volatile boolean mShutdown = false;
/**
* Controls the auto shutdown mechanism of the service.
*
* <p>
* The default service life-cycle model is that the service should run only as long as a task
* is running. If a service should behave differently, disable auto shutdown using
* {@link #setAutoShutdown(boolean)}.
* </p>
*/
private boolean mAutoShutdown = true;
/**
* This variable is part of the auto shutdown feature and determines whether the service has to
* be shutdown at the end of the {@link #onStart(Intent, int)} method or not.
*/
protected boolean mImmediateShutdown = true;
/**
* Adds an existing wake lock identified by its registry ID to the specified intent.
*
* @param context
* A {@link Context} instance. Never {@code null}.
* @param intent
* The {@link Intent} to add the wake lock registry ID as extra to. Never {@code null}.
* @param wakeLockId
* The wake lock registry ID of an existing wake lock or {@code null}.
* @param createIfNotExists
* If {@code wakeLockId} is {@code null} and this parameter is {@code true} a new wake
* lock is created, registered, and added to {@code intent}.
*/
protected static void addWakeLockId(Context context, Intent intent, Integer wakeLockId,
boolean createIfNotExists) {
if (wakeLockId != null) {
intent.putExtra(BootReceiver.WAKE_LOCK_ID, wakeLockId);
return;
}
if (createIfNotExists) {
addWakeLock(context,intent);
}
}
/**
* Adds a new wake lock to the specified intent.
*
* <p>
* This will add the wake lock to the central wake lock registry managed by this class.
* </p>
*
* @param context
* A {@link Context} instance. Never {@code null}.
* @param intent
* The {@link Intent} to add the wake lock registry ID as extra to. Never {@code null}.
*/
protected static void addWakeLock(Context context, Intent intent) {
TracingWakeLock wakeLock = acquireWakeLock(context, "CoreService addWakeLock",
K9.MAIL_SERVICE_WAKE_LOCK_TIMEOUT);
Integer tmpWakeLockId = registerWakeLock(wakeLock);
intent.putExtra(WAKE_LOCK_ID, tmpWakeLockId);
}
/**
* Registers a wake lock with the wake lock registry.
*
* @param wakeLock
* The {@link TracingWakeLock} instance that should be registered with the wake lock
* registry. Never {@code null}.
*
* @return The ID that identifies this wake lock in the registry.
*/
protected static Integer registerWakeLock(TracingWakeLock wakeLock) {
// Get a new wake lock ID
Integer tmpWakeLockId = sWakeLockSeq.getAndIncrement();
// Store the wake lock in the registry
sWakeLocks.put(tmpWakeLockId, wakeLock);
return tmpWakeLockId;
}
/**
* Acquires a wake lock.
*
* @param context
* A {@link Context} instance. Never {@code null}.
* @param tag
* The tag to supply to {@link TracingPowerManager}.
* @param timeout
* The wake lock timeout.
*
* @return A new {@link TracingWakeLock} instance.
*/
protected static TracingWakeLock acquireWakeLock(Context context, String tag, long timeout) {
TracingPowerManager pm = TracingPowerManager.getPowerManager(context);
TracingWakeLock wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, tag);
wakeLock.setReferenceCounted(false);
wakeLock.acquire(timeout);
return wakeLock;
}
@Override
public void onCreate() {
if (K9.DEBUG) {
Log.i(K9.LOG_TAG, "CoreService: " + className + ".onCreate()");
}
mThreadPool = Executors.newFixedThreadPool(1); // Must be single threaded
}
@Override
public final int onStartCommand(Intent intent, int flags, int startId) {
/*
* When a process is killed due to low memory, it's later restarted and services that were
* started with START_STICKY are started with the intent being null.
*
* For now we just ignore these restart events. This should be fine because all necessary
* services are started from K9.onCreate() when the Application object is initialized.
*
* See issue 3750
*/
if (intent == null) {
stopSelf(startId);
return START_NOT_STICKY;
}
// Acquire new wake lock
TracingWakeLock wakeLock = acquireWakeLock(this, "CoreService onStart",
K9.MAIL_SERVICE_WAKE_LOCK_TIMEOUT);
if (K9.DEBUG) {
Log.i(K9.LOG_TAG, "CoreService: " + className + ".onStart(" + intent + ", " + startId + ")");
}
// If we were started by BootReceiver, release the wake lock acquired there.
int wakeLockId = intent.getIntExtra(BootReceiver.WAKE_LOCK_ID, -1);
if (wakeLockId != -1) {
BootReceiver.releaseWakeLock(this, wakeLockId);
}
// If we were passed an ID from our own wake lock registry, retrieve that wake lock and
// release it.
int coreWakeLockId = intent.getIntExtra(WAKE_LOCK_ID, -1);
if (coreWakeLockId != -1) {
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "Got core wake lock id " + coreWakeLockId);
}
// Remove wake lock from the registry
TracingWakeLock coreWakeLock = sWakeLocks.remove(coreWakeLockId);
// Release wake lock
if (coreWakeLock != null) {
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "Found core wake lock with id " + coreWakeLockId +
", releasing");
}
coreWakeLock.release();
}
}
// Run the actual start-code of the service
mImmediateShutdown = true;
int startFlag;
try {
startFlag = startService(intent, startId);
} finally {
try {
// Release the wake lock acquired at the start of this method
wakeLock.release();
} catch (Exception e) { /* ignore */ }
try {
// If there is no outstanding work to be done in a background thread we can stop
// this service.
if (mAutoShutdown && mImmediateShutdown && startId != -1) {
stopSelf(startId);
startFlag = START_NOT_STICKY;
}
} catch (Exception e) { /* ignore */ }
}
return startFlag;
}
/**
* Execute a task in the background thread.
*
* @param context
* A {@link Context} instance. Never {@code null}.
* @param runner
* The code to be executed in the background thread.
* @param wakeLockTime
* The timeout for the wake lock that will be acquired by this method.
* @param startId
* The {@code startId} value received in {@link #onStart(Intent, int)} or {@code null}
* if you don't want the service to be shut down after {@code runner} has been executed
* (e.g. because you need to run another background task).<br>
* If this parameter is {@code null} you need to call {@code setAutoShutdown(false)}
* otherwise the auto shutdown code will stop the service.
*/
public void execute(Context context, final Runnable runner, int wakeLockTime,
final Integer startId) {
boolean serviceShutdownScheduled = false;
final boolean autoShutdown = mAutoShutdown;
// Acquire a new wakelock
final TracingWakeLock wakeLock = acquireWakeLock(context, "CoreService execute",
wakeLockTime);
// Wrap the supplied runner with code to release the wake lock and stop the service if
// appropriate.
Runnable myRunner = new Runnable() {
public void run() {
try {
// Get the sync status
boolean oldIsSyncDisabled = MailService.isSyncDisabled();
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "CoreService (" + className + ") running Runnable " +
runner.hashCode() + " with startId " + startId);
}
// Run the supplied code
runner.run();
// If the sync status changed while runner was executing, notify
// MessagingController
if (MailService.isSyncDisabled() != oldIsSyncDisabled) {
MessagingController.getInstance(getApplication()).systemStatusChanged();
}
} finally {
// Making absolutely sure stopSelf() will be called
try {
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "CoreService (" + className + ") completed " +
"Runnable " + runner.hashCode() + " with startId " + startId);
}
wakeLock.release();
} finally {
if (autoShutdown && startId != null) {
stopSelf(startId);
}
}
}
}
};
// TODO: remove this. we never set mThreadPool to null
if (mThreadPool == null) {
Log.e(K9.LOG_TAG, "CoreService.execute (" + className + ") called with no thread " +
"pool available; running Runnable " + runner.hashCode() +
" in calling thread");
synchronized (this) {
myRunner.run();
serviceShutdownScheduled = startId != null;
}
} else {
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "CoreService (" + className + ") queueing Runnable " +
runner.hashCode() + " with startId " + startId);
}
try {
mThreadPool.execute(myRunner);
serviceShutdownScheduled = startId != null;
} catch (RejectedExecutionException e) {
// Ignore RejectedExecutionException after we shut down the thread pool in
// onDestroy(). Still, this should not happen!
if (!mShutdown) {
throw e;
}
Log.i(K9.LOG_TAG, "CoreService: " + className + " is shutting down, ignoring " +
"rejected execution exception: " + e.getMessage());
}
}
mImmediateShutdown = !serviceShutdownScheduled;
}
/**
* Subclasses need to implement this instead of overriding {@link #onStartCommand(Intent, int, int)}.
*
* <p>
* This allows {@link CoreService} to manage the service lifecycle, incl. wake lock management.
* </p>
* @param intent
* The Intent supplied to {@link Context#startService(Intent)}.
* @param startId
* A unique integer representing this specific request to start. Use with
* {@link #stopSelfResult(int)}.
*
* @return The return value indicates what semantics the system should use for the service's
* current started state. It may be one of the constants associated with the
* {@link Service#START_CONTINUATION_MASK} bits.
*/
public abstract int startService(Intent intent, int startId);
@Override
public void onLowMemory() {
Log.w(K9.LOG_TAG, "CoreService: " + className + ".onLowMemory() - Running low on memory");
}
/**
* Clean up when the service is stopped.
*/
@Override
public void onDestroy() {
if (K9.DEBUG) {
Log.i(K9.LOG_TAG, "CoreService: " + className + ".onDestroy()");
}
// Shut down thread pool
mShutdown = true;
mThreadPool.shutdown();
}
/**
* Return whether or not auto shutdown is enabled.
*
* @return {@code true} iff auto shutdown is enabled.
*/
protected boolean isAutoShutdown() {
return mAutoShutdown;
}
/**
* Enable or disable auto shutdown (enabled by default).
*
* @param autoShutdown
* {@code true} to enable auto shutdown. {@code false} to disable.
*
* @see #mAutoShutdown
*/
protected void setAutoShutdown(boolean autoShutdown) {
mAutoShutdown = autoShutdown;
}
@Override
public IBinder onBind(Intent intent) {
// Unused
return null;
}
}