package ca.ualberta.cs.cmput301f14t14.questionapp.data.threading; import ca.ualberta.cs.cmput301f14t14.questionapp.data.DataManager; import ca.ualberta.cs.cmput301f14t14.questionapp.data.eventbus.EventBus; import ca.ualberta.cs.cmput301f14t14.questionapp.data.eventbus.events.AbstractEvent; import ca.ualberta.cs.cmput301f14t14.questionapp.data.eventbus.events.AnswerCommentPushDelayedEvent; import ca.ualberta.cs.cmput301f14t14.questionapp.data.eventbus.events.AnswerPushDelayedEvent; import ca.ualberta.cs.cmput301f14t14.questionapp.data.eventbus.events.QuestionCommentPushDelayedEvent; import ca.ualberta.cs.cmput301f14t14.questionapp.data.eventbus.events.QuestionPushDelayedEvent; import android.app.IntentService; import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.IBinder; public class UploaderService extends Service { private DataManager dm = null; private Context context = null; //Had to make this static. Little bit of evil hackery to get "singleton"-like behavior. //See DataManager.java:startUploaderService() for the other half of this spaghetti. public static boolean isServiceAlreadyRunning = false; @Override public IBinder onBind(Intent intent) { // This service does not need to communicate with the rest of the app // in our design, so it's okay to return null (according to docs) return null; } @Override public void onCreate() { //Set up class variables on creation of service context = getApplicationContext(); dm = DataManager.getInstance(context); /* Set */ isServiceAlreadyRunning = true; } @Override public void onDestroy(){ //Clean up. isServiceAlreadyRunning = false; } @Override public int onStartCommand(Intent intent, int flags, int startId) { // Flags is useful for services restarted. // startId is for A unique integer representing this // specific request to start. Use with stopSelfResult(int) //We need to create a thread to run completeQueuedEvents in. (new Thread(new QueueClearer())).start(); // We want this service to continue running until it is explicitly // stopped, so return sticky. return START_STICKY; } /** * Private inner class that implements Runnable. This is needed so that * we can create a thread above (within the service). This service itself * should not implement Runnable as we don't want people willy-nilly running * and Android service in a thread. The service itself should exist in the UI * thread and run its own stuff in other threads. * @author Stefan * */ private class QueueClearer implements Runnable { @Override public void run() { //In here we can have the logic for the thread itself. //(Such as checking for connectvitiy, sleeping, etc.) while (true) { int queuesize = 0; queuesize = completeQueuedEvents(); if (queuesize > 0) { //We've retried what we could, but there are still things left //in the eventbus that we cannot do. //It's okay to sleep and simply try again. //Arbitrary sleep time. Could be 1ms, but that would be equivalent to busy-waiting. try { Thread.sleep(20000); } catch (InterruptedException e) { // It's fine to be interrupted while sleeping. } } else { //We have cleared out everything in the queue. This service no longer needs to exist. //(The service is recreated in EventBus.addEvent()). stopSelf(); } } } private synchronized int completeQueuedEvents() { /* The singleton eventbus contains events that attempted to * be posted to the internet. If posting failed, an event was created * on the eventbus. These queued events should regularly "tried again" * so that we are as frequently as possible trying to update the internet * with our new local information. */ EventBus eventbus = EventBus.getInstance(); //For each event in the event bus, try and do it again. /** There is quite a bit of fancy footwork below, so pay attention. * * We have a BlockingQueue with AbstractEvent elements * * [] [] [] [] [] * H T T' * * Where H = head, T = tail, T' = tail after retrying events. * Recall the head is the oldest, and the tail is the youngest in a queue. * * We want the loop below to only run from H-->T. If we are offline, running through * the entire queue will lead to an infinite loop and a significant drain on battery life. * */ AbstractEvent youngestevent = eventbus.getYoungestEvent(); while(!eventbus.getEventQueue().isEmpty() && !eventbus.getEventQueue().peek().equals(youngestevent) ){ //Sort circuit logic should prevent .peek() from failing. try { //Take the event from the head of the queue //(block this thread if needed), and retry it. eventbus.getEventQueue().take().retry(dm); } catch (InterruptedException e1) { e1.printStackTrace(); } } // Return number of elements still in queue return eventbus.getEventQueue().size(); } } }