package com.fsck.k9.service;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import android.content.Context;
import android.content.Intent;
import android.os.SystemClock;
import com.fsck.k9.mail.power.TracingPowerManager.TracingWakeLock;
import timber.log.Timber;
import static java.lang.Thread.currentThread;
public class SleepService extends CoreService {
private static String ALARM_FIRED = "com.fsck.k9.service.SleepService.ALARM_FIRED";
private static String LATCH_ID = "com.fsck.k9.service.SleepService.LATCH_ID_EXTRA";
private static ConcurrentHashMap<Integer, SleepDatum> sleepData = new ConcurrentHashMap<Integer, SleepDatum>();
private static AtomicInteger latchId = new AtomicInteger();
public static void sleep(Context context, long sleepTime, TracingWakeLock wakeLock, long wakeLockTimeout) {
Integer id = latchId.getAndIncrement();
Timber.d("SleepService Preparing CountDownLatch with id = %d, thread %s", id, currentThread().getName());
SleepDatum sleepDatum = new SleepDatum();
CountDownLatch latch = new CountDownLatch(1);
sleepDatum.latch = latch;
sleepDatum.reacquireLatch = new CountDownLatch(1);
sleepData.put(id, sleepDatum);
Intent i = new Intent(context, SleepService.class);
i.putExtra(LATCH_ID, id);
i.setAction(ALARM_FIRED + "." + id);
long startTime = SystemClock.elapsedRealtime();
long nextTime = startTime + sleepTime;
BootReceiver.scheduleIntent(context, nextTime, i);
if (wakeLock != null) {
sleepDatum.wakeLock = wakeLock;
sleepDatum.timeout = wakeLockTimeout;
wakeLock.release();
}
try {
boolean countedDown = latch.await(sleepTime, TimeUnit.MILLISECONDS);
if (!countedDown) {
Timber.d("SleepService latch timed out for id = %d, thread %s", id, currentThread().getName());
}
} catch (InterruptedException ie) {
Timber.e(ie, "SleepService Interrupted while awaiting latch");
}
SleepDatum releaseDatum = sleepData.remove(id);
if (releaseDatum == null) {
try {
Timber.d("SleepService waiting for reacquireLatch for id = %d, thread %s",
id, currentThread().getName());
if (!sleepDatum.reacquireLatch.await(5000, TimeUnit.MILLISECONDS)) {
Timber.w("SleepService reacquireLatch timed out for id = %d, thread %s",
id, currentThread().getName());
} else {
Timber.d("SleepService reacquireLatch finished for id = %d, thread %s",
id, currentThread().getName());
}
} catch (InterruptedException ie) {
Timber.e(ie, "SleepService Interrupted while awaiting reacquireLatch");
}
} else {
reacquireWakeLock(releaseDatum);
}
long endTime = SystemClock.elapsedRealtime();
long actualSleep = endTime - startTime;
if (actualSleep < sleepTime) {
Timber.w("SleepService sleep time too short: requested was %d, actual was %d", sleepTime, actualSleep);
} else {
Timber.d("SleepService requested sleep time was %d, actual was %d", sleepTime, actualSleep);
}
}
private static void endSleep(Integer id) {
if (id != -1) {
SleepDatum sleepDatum = sleepData.remove(id);
if (sleepDatum != null) {
CountDownLatch latch = sleepDatum.latch;
if (latch == null) {
Timber.e("SleepService No CountDownLatch available with id = %s", id);
} else {
Timber.d("SleepService Counting down CountDownLatch with id = %d", id);
latch.countDown();
}
reacquireWakeLock(sleepDatum);
sleepDatum.reacquireLatch.countDown();
} else {
Timber.d("SleepService Sleep for id %d already finished", id);
}
}
}
private static void reacquireWakeLock(SleepDatum sleepDatum) {
TracingWakeLock wakeLock = sleepDatum.wakeLock;
if (wakeLock != null) {
synchronized (wakeLock) {
long timeout = sleepDatum.timeout;
Timber.d("SleepService Acquiring wakeLock for %d ms", timeout);
wakeLock.acquire(timeout);
}
}
}
@Override
public int startService(Intent intent, int startId) {
try {
if (intent.getAction().startsWith(ALARM_FIRED)) {
Integer id = intent.getIntExtra(LATCH_ID, -1);
endSleep(id);
}
return START_NOT_STICKY;
}
finally {
stopSelf(startId);
}
}
private static class SleepDatum {
CountDownLatch latch;
TracingWakeLock wakeLock;
long timeout;
CountDownLatch reacquireLatch;
}
}