package edu.mit.media.funf;
import static edu.mit.media.funf.Utils.TAG;
import java.util.Random;
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
public abstract class CustomizedIntentService extends Service {
protected static final int EXTERNAL_MESSAGE = 0;
protected static final int INTERNAL_MESSAGE = 1;
protected static final int MESSAGE_QUIT = 2;
protected static final int MESSAGE_PAUSE = 3;
private static final long DEFAULT_MILLIS_TO_WAIT = 5000L;
// Times since bootup to controll order at front of que,
// implemented as a hack because putting message at front of queue does not work as expected
private static final long
PRIORITY_BEFORE_FRONT = 1,
PRIORITY_FRONT = 2,
PRIORITY_AFTER_FRONT = 3;
private int startId = 0;
private volatile HandlerThread thread;
private volatile Looper mServiceLooper;
private volatile ServiceHandler mServiceHandler;
private String mName;
private Intent intentToWaitFor;
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_QUIT:
mServiceLooper.quit();
break;
case MESSAGE_PAUSE:
Long millisToWait = (Long)msg.obj;
if (millisToWait == null) {
millisToWait = DEFAULT_MILLIS_TO_WAIT;
}
try {
synchronized (this) {
this.wait(millisToWait);
}
} catch (InterruptedException e) {
Log.w(TAG, "Service handler thread interrupted!");
}
break;
default:
Log.d(TAG, "Handling msg " + msg.arg1);
Log.d(TAG, "Handling message @ " + System.currentTimeMillis() +": " + msg.obj);
onHandleIntent((Intent)msg.obj);
if (!hasMessages()) {
onEndOfQueue();
}
break;
}
}
private boolean hasMessages() {
// TODO: this may be to intensive to run on every message, consider a better implementation
//startId == msg.arg1
return mServiceHandler.hasMessages(EXTERNAL_MESSAGE)
|| mServiceHandler.hasMessages(INTERNAL_MESSAGE)
|| mServiceHandler.hasMessages(MESSAGE_PAUSE)
|| mServiceHandler.hasMessages(MESSAGE_QUIT);
}
}
public CustomizedIntentService() {
this(null);
}
/**
* Creates an IntentService. Invoked by your subclass's constructor.
*
* @param name Used to name the worker thread, important only for debugging.
*/
public CustomizedIntentService(String name) {
super();
mName = (name == null) ? getClass().getName() : name;
}
@Override
public void onCreate() {
super.onCreate();
thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
protected void pauseQueueUntilIntentReceived(Intent intent, Long timeout) {
if (!mServiceHandler.hasMessages(MESSAGE_QUIT)) {
intentToWaitFor = (intent == null) ? new Intent() : intent;
Message msg = mServiceHandler.obtainMessage();
msg.what = MESSAGE_PAUSE;
mServiceHandler.sendMessageAtTime(msg, PRIORITY_BEFORE_FRONT); // Very front of queue
}
}
protected boolean queueIntent(Intent intent) {
return queueIntent(intent, false);
}
protected boolean queueIntent(Intent intent, boolean atFront) {
Message msg = mServiceHandler.obtainMessage();
msg.what = INTERNAL_MESSAGE;
msg.arg1 = new Random().nextInt();
msg.obj = intent;
Log.d(TAG, "Internal Queue Message: "+ ((intent == null) ? "<quit>" : (intent.getComponent() + " " + intent.getAction())));
if (atFront) {
return mServiceHandler.sendMessageAtTime(msg, PRIORITY_FRONT); // HACK: because of the implementation of postMessageAtFrontOfQueue = sendMessageAtTime(msg, 0L)
} else {
this.startId = msg.arg1; // TODO: figure out how to create priority queue, so we know how to specify start id
return mServiceHandler.sendMessage(msg);
}
}
private boolean isIntentThisIsWaitingFor(Intent intent) {
return intentToWaitFor != null
&& ((intentToWaitFor.getComponent() == null && intentToWaitFor.getAction() == null)
|| intentToWaitFor.filterEquals(intent));
}
@Override
public void onStart(Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.obj = intent;
msg.what = EXTERNAL_MESSAGE;
msg.arg1 = startId;
// Awaken handler thread if this is the intent we are waiting for
if (isIntentThisIsWaitingFor(intent)) {
Log.d(TAG, "GOT intent we were waiting for: " + intent.getComponent() + " " + intent.getAction());
intentToWaitFor = null;
boolean success = mServiceHandler.sendMessageAtTime(msg, PRIORITY_BEFORE_FRONT);
Log.d(TAG, "Successfully queued at front the intent we were waiting for. " + success);
mServiceHandler.removeMessages(MESSAGE_PAUSE);
synchronized (mServiceHandler) {
mServiceHandler.notify();
}
} else {
this.startId = msg.arg1;
mServiceHandler.sendMessage(msg);
}
Log.d(TAG, "onStart Message: " + intent.getComponent() + " " + intent.getAction());
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
onStart(intent, startId);
return getRedeliveryType(intent);
}
@Override
public void onDestroy() {
Log.d(TAG, "Destroying service " + getClass().getName());
// Pause long enough to allow subclasses to queue up things before QUIT message
pauseQueueUntilIntentReceived(new Intent("NON_EXISTENT_INTENT"), 100L);
// Send quit message at front of queue
Message msg = mServiceHandler.obtainMessage();
msg.what = MESSAGE_QUIT;
mServiceHandler.sendMessageAtTime(msg, PRIORITY_AFTER_FRONT); // So that it occurs after all messages "At front of queue"
onBeforeDestroy();
mServiceHandler.removeMessages(MESSAGE_PAUSE);
synchronized (mServiceHandler) {
mServiceHandler.notify();
}
// Wait for queue to finish
try {
thread.join(2000);
} catch (InterruptedException e) {
}
if (thread.isAlive()) {
Log.d(TAG, "Message thread did not die in time: " + getClass().getName());
mServiceLooper.quit();
}
}
/**
* Allows the subclass to queue up messages on message thread before the service is destroyed.
* Useful for must have cleanup options.
*/
public void onBeforeDestroy() {
// Default implementation does nothing
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
protected boolean isIntentHandlerThread() {
Looper looper = Looper.myLooper();
return looper != null && looper.equals(mServiceLooper);
}
/**
* This method is invoked on the worker thread with a request to process.
* Only one Intent is processed at a time, but the processing happens on a
* worker thread that runs independently from other application logic.
* So, if this code takes a long time, it will hold up other requests to
* the same IntentService, but it will not hold up anything else.
*
* @param intent The value passed to {@link
* android.content.Context#startService(Intent)}.
*/
protected abstract void onHandleIntent(Intent intent);
/**
* Called when service has reached the end of the queue. The default implementation calls stopSelf().
* Subclasses can override this to prevent class from stopping, or to perform cleanup on handler thread before onDestory.
* @return
*/
protected void onEndOfQueue() {
stopSelf();
}
/**
* Returns the redelivery type based on the intent passed in. Subclasses can use this method to determine which intents get redilvered, and which do not.
* Default is redelivery.
* @param intent
* @return
*/
protected int getRedeliveryType(Intent intent) {
return START_REDELIVER_INTENT;
}
}