/**
 * 2011 Foxykeep (http://datadroid.foxykeep.com)
 * <p>
 * Licensed under the Beerware License : <br />
 * As long as you retain this notice you can do whatever you want with this stuff. If we meet some
 * day, and you think this stuff is worth it, you can buy me a beer in return
 */

package de.mygrades.main;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;

import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/**
 * MultiThreadIntentService is a base class for {@link Service}s that handle asynchronous requests
 * (expressed as {@link Intent}s) on demand. Clients send requests through
 * {@link android.content.Context#startService(Intent)} calls; the service is started as needed,
 * handles each Intent in turn using a worker thread, and stops itself when it runs out of work.
 * <p>
 * This "work queue processor" pattern is commonly used to offload tasks from an application's main
 * thread. The MultiThreadedIntentService class exists to simplify this pattern and take care of the
 * mechanics. To use it, extend MultiThreadedIntentService and implement
 * {@link #onHandleIntent(Intent)}. MultiThreadedIntentService will receive the Intents, launch a
 * worker thread, and stop the service as appropriate.
 * <p>
 * All requests are handled on multiple worker threads -- they may take as long as necessary (and
 * will not block the application's main loop). By default only one concurrent worker thread is
 * used. You can modify the number of current worker threads by overriding
 * {@link #getMaximumNumberOfThreads()}.
 * <p>
 * For obvious efficiency reasons, MultiThreadedIntentService won't stop itself as soon as all tasks
 * has been processed. It will only stop itself after a certain delay (about 30s). This optimization
 * prevents the system from creating new instances over and over again when tasks are sent.
 *
 * @author Foxykeep
 */
public abstract class MultiThreadedIntentService extends Service {

    private static final long STOP_SELF_DELAY = TimeUnit.SECONDS.toMillis(30L);

    private ExecutorService mThreadPool;
    private boolean mRedelivery;

    private ArrayList<Future<?>> mFutureList;

    private Handler mHandler;

    private final Runnable mStopSelfRunnable = new Runnable() {
        @Override
        public void run() {
            stopSelf();
        }
    };

    private final Runnable mWorkDoneRunnable = new Runnable() {
        @Override
        public void run() {
            if (Looper.getMainLooper().getThread() != Thread.currentThread()) {
                throw new IllegalStateException(
                        "This runnable can only be called in the Main thread!");
            }

            final ArrayList<Future<?>> futureList = mFutureList;
            for (int i = 0; i < futureList.size(); i++) {
                if (futureList.get(i).isDone()) {
                    futureList.remove(i);
                    i--;
                }
            }

            if (futureList.isEmpty()) {
                mHandler.postDelayed(mStopSelfRunnable, STOP_SELF_DELAY);
            }
        }
    };

    /**
     * Sets intent redelivery preferences. Usually called from the constructor with your preferred
     * semantics.
     * <p>
     * If enabled is true, {@link #onStartCommand(Intent, int, int)} will return
     * {@link Service#START_REDELIVER_INTENT}, so if this process dies before
     * {@link #onHandleIntent(Intent)} returns, the process will be restarted and the intent
     * redelivered. If multiple Intents have been sent, only the most recent one is guaranteed to be
     * redelivered.
     * <p>
     * If enabled is false (the default), {@link #onStartCommand(Intent, int, int)} will return
     * {@link Service#START_NOT_STICKY}, and if the process dies, the Intent dies along with it.
     */
    public void setIntentRedelivery(boolean enabled) {
        mRedelivery = enabled;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        int maximumNumberOfThreads = getMaximumNumberOfThreads();
        if (maximumNumberOfThreads <= 0) {
            throw new IllegalArgumentException("Maximum number of threads must be " +
                    "strictly positive");
        }
        mThreadPool = Executors.newFixedThreadPool(maximumNumberOfThreads);
        mHandler = new Handler();
        mFutureList = new ArrayList<Future<?>>();
    }

    @Override
    @SuppressWarnings("deprecation")
    public void onStart(Intent intent, int startId) {
        mHandler.removeCallbacks(mStopSelfRunnable);
        mFutureList.add(mThreadPool.submit(new IntentRunnable(intent)));
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mThreadPool.shutdown();
    }

    /**
     * Unless you provide binding for your service, you don't need to implement this method, because
     * the default implementation returns null.
     *
     * @see android.app.Service#onBind
     */
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    /**
     * Define the maximum number of concurrent worker threads used to execute the incoming Intents.
     * <p>
     * By default only one concurrent worker thread is used at the same time. Overrides this method
     * in subclasses to change this number.
     * <p>
     * This method is called once in the {@link #onCreate()}. Modifying the value returned after the
     * {@link #onCreate()} is called will have no effect.
     *
     * @return The maximum number of concurrent worker threads
     */
    protected int getMaximumNumberOfThreads() {
        return 10;
    }

    private class IntentRunnable implements Runnable {
        private final Intent mIntent;

        public IntentRunnable(Intent intent) {
            mIntent = intent;
        }

        public void run() {
            onHandleIntent(mIntent);
            mHandler.removeCallbacks(mWorkDoneRunnable);
            mHandler.post(mWorkDoneRunnable);
        }
    }

    /**
     * This method is invoked on the worker thread with a request to process. The processing happens
     * on a worker thread that runs independently from other application logic. When all requests
     * have been handled, the IntentService stops itself, so you should not call {@link #stopSelf}.
     *
     * @param intent The value passed to {@link Context#startService(Intent)}.
     */
    abstract protected void onHandleIntent(Intent intent);
}
@@ -1632,79 +1632,61 @@
 p). 
-By default only one concurrent worker thread is%0A * used. You can modify
+A cached thread pool is used to dynamically adjust%0A *
  the
@@ -1696,24 +1696,27 @@
 mber of 
+con
 current 
 worker t
@@ -1699,32 +1699,37 @@
 r of concurrent 
+used 
 worker threads b
@@ -1730,62 +1730,8 @@
 eads
- by overriding%0A * %7B@link #getMaximumNumberOfThreads()%7D
 .%0A *
@@ -2051,16 +2051,43 @@
 Foxykeep
+, modified by Tilman Ginzel
 %0A */%0Apub
@@ -4178,255 +4178,8 @@
 ();%0A
-        int maximumNumberOfThreads = getMaximumNumberOfThreads();%0A        if (maximumNumberOfThreads %3C= 0) %7B%0A            throw new IllegalArgumentException(%22Maximum number of threads must be %22 +%0A                    %22strictly positive%22);%0A        %7D%0A
     
@@ -4213,11 +4213,12 @@
 .new
-Fix
+Cach
 edTh
@@ -4230,30 +4230,8 @@
 ool(
-maximumNumberOfThreads
 );%0A 
@@ -5126,32 +5126,32 @@
 ntent intent) %7B%0A
+
         return n
@@ -5166,593 +5166,8 @@
  %7D%0A%0A
-    /**%0A     * Define the maximum number of concurrent worker threads used to execute the incoming Intents.%0A     * %3Cp%3E%0A     * By default only one concurrent worker thread is used at the same time. Overrides this method%0A     * in subclasses to change this number.%0A     * %3Cp%3E%0A     * This method is called once in the %7B@link #onCreate()%7D. Modifying the value returned after the%0A     * %7B@link #onCreate()%7D is called will have no effect.%0A     *%0A     * @return The maximum number of concurrent worker threads%0A     */%0A    protected int getMaximumNumberOfThreads() %7B%0A        return 10;%0A    %7D%0A%0A
     

Use cached thread pool instead of fixed in MultiThreadedIntentService