/FileExtraction/Java_unseen/PowerExplorer_PowerFileExplorer/PowerFileExplorer/src/main/java/com/amaze/filemanager/utils/ServiceWatcherUtil.java
package com.amaze.filemanager.utils;

/**
 * Created by vishal on 4/1/17.
 *
 * Helper class providing helper methods to manage Service startup and it's progress
 * Be advised - this class can only handle progress with one object at a time. Hence, class also provides
 * convenience methods to serialize the service startup.
 */

import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.HandlerThread;
import android.support.v4.app.NotificationCompat;

import net.gnu.explorer.R;

import java.util.ArrayList;

public class ServiceWatcherUtil {

    private Handler handler;
    private static HandlerThread handlerThread;
    private ProgressHandler progressHandler;
    long totalSize;
    private Runnable runnable;

    private static ArrayList<Intent> pendingIntents = new ArrayList<>();

    // position of byte in total byte size to be copied
    public static long POSITION = 0L;

    private static int HAULT_COUNTER = -1;

    public static final int ID_NOTIFICATION_WAIT =  9248;

    /**
     *
     * @param progressHandler to publish progress after certain delay
     * @param totalSize total size of files to be performed, so we know when to halt the watcher
     */
    public ServiceWatcherUtil(ProgressHandler progressHandler, long totalSize) {
        this.progressHandler = progressHandler;
        this.totalSize = totalSize;
        POSITION = 0L;
        HAULT_COUNTER = -1;

        handlerThread = new HandlerThread("service_progress_watcher");
        handlerThread.start();
        handler = new Handler(handlerThread.getLooper());
    }

    /**
     * Watches over the service progress without interrupting the worker thread in respective services
     * Method frees up all the resources and handlers after operation completes.
     */
    public void watch() {
        runnable = new Runnable() {
            @Override
            public void run() {

                // we don't have a file name yet, wait for service to set
                if (progressHandler.getFileName()==null) 
					handler.postDelayed(this, 1000);

                progressHandler.addWrittenLength(POSITION);

                if (POSITION == totalSize || progressHandler.getCancelled()) {
                    // process complete, free up resources
                    // we've finished the work or process cancelled
                    handler.removeCallbacks(this);
                    handlerThread.quit();
                    return;
                }

                if (POSITION == progressHandler.getWrittenSize()) {
                    HAULT_COUNTER++;

                    if (HAULT_COUNTER>10) {
                        // we suspect the progress has been haulted for some reason, stop the watcher

                        // workaround for decryption when we have a length retreived by
                        // CipherInputStream less than the orginal stream, and hence the total size
                        // we passed at the beginning is never reached
                        progressHandler.addWrittenLength(totalSize);
                        handler.removeCallbacks(this);
                        handlerThread.quit();
                        return;
                    }
                }
                handler.postDelayed(this, 1000);
            }
        };

        handler.postDelayed(runnable, 1000);
    }

    /**
     * Manually call runnable, before the delay. Fixes race condition which can arise when
     * service has finished execution and stopping self, but the runnable is yet scheduled to be posted.
     * Thus avoids posting any callback after service has stopped.
     */
    public void stopWatch() {
        if (handlerThread.isAlive()) 
			handler.post(runnable);
    }

    /**
     * Convenience method to check whether another service is working in background
     * If a service is found working (by checking {@link #handlerThread} for it's state)
     * then we wait for an interval of 5 secs, before checking on it again.
     *
     * Be advised - this method is not sure to start a new service, especially when app has been closed
     * as there are higher chances for android system to GC the thread when it is running low on memory
     *
     * @param context
     * @param intent
     */
    public static synchronized void runService(final Context context, final Intent intent) {

        /*if (handlerThread==null || !handlerThread.isAlive()) {
            // we're not bound, no need to proceed further and waste up resources
            // start the service directly

            *//**
             * We can actually end up racing at this point with the {@link HandlerThread} started
             * in {@link #init(Context)}. If older service has returned, we already have the runnable
             * waiting to execute in #init, and user is in app, and starts another service, and
             * as this block executes the {@link android.app.Service#onStartCommand(Intent, int, int)}
             * we end up with a context switch to 'service_startup_watcher' in #init, it also starts
             * a new service (as {@link #progressHandler} is not alive yet).
             * Though chances are very slim, but even if this condition occurs, only the progress will
             * be flawed, but the actual operation will go fine, due to android's native serial service
             * execution. #nough' said!
             *//*
            context.startService(intent);
            return;
        }*/

        pendingIntents.add(intent);
		if (pendingIntents.size()==1) {
            init(context);
        }
        
    }

    /**
     * Helper method to {@link #runService(Context, Intent)}
     * Starts the wait watcher thread if not already started.
     * Halting condition depends on the state of {@link #handlerThread}
     * @param context
     */
    private static synchronized void init(final Context context) {

        final HandlerThread waitingThread = new HandlerThread("service_startup_watcher");
        waitingThread.start();
        final Handler handler = new Handler(waitingThread.getLooper());
        final NotificationManager notificationManager = (NotificationManager)
                context.getSystemService(Context.NOTIFICATION_SERVICE);
        final NotificationCompat.Builder mBuilder=new NotificationCompat.Builder(context);
        mBuilder.setContentTitle(context.getString(R.string.waiting_title));
        mBuilder.setContentText(context.getString(R.string.waiting_content));
        mBuilder.setAutoCancel(false);
        mBuilder.setSmallIcon(R.drawable.ic_all_inclusive_white_36dp);
        mBuilder.setProgress(0, 0, true);

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                if (handlerThread==null || !handlerThread.isAlive()) {
                    // service is been finished, let's start this one
                    // pop recent intent from pendingIntents
                    final int size = pendingIntents.size();
					if (size > 0) {
						context.startService(pendingIntents.remove(size - 1));
					}

                    if (pendingIntents.size()==0) {
                        // we've done all the work, free up resources (if not already killed by system)
                        notificationManager.cancel(ID_NOTIFICATION_WAIT);
                        handler.removeCallbacks(this);
                        waitingThread.quit();
                        return;
                    } else {
                        notificationManager.notify(ID_NOTIFICATION_WAIT, mBuilder.build());
                    }
                }
                handler.postDelayed(this, 1000);
            }
        };
        handler.postDelayed(runnable, 0);
    }
}
