content: /*
   * Copyright (C) 2014 Hari Krishna Dulipudi
   * Copyright (C) 2010 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package dev.dworks.apps.anexplorer.misc;
  
  import android.annotation.TargetApi;
  import android.content.Context;
  import android.content.Loader;
  import android.os.AsyncTask;
  import android.os.Build;
  import android.os.Handler;
  import android.os.OperationCanceledException;
  import android.os.SystemClock;
  import android.util.Log;
  
  import java.io.FileDescriptor;
  import java.io.PrintWriter;
  import java.util.concurrent.CountDownLatch;
  import java.util.concurrent.Executor;
  
  /**
   * Abstract Loader that provides an {@link AsyncTask} to do the work.  See
   * {@link Loader} and {@link android.app.LoaderManager} for more details.
   *
   * <p>Here is an example implementation of an AsyncTaskLoader subclass that
   * loads the currently installed applications from the package manager.  This
   * implementation takes care of retrieving the application labels and sorting
   * its result set from them, monitoring for changes to the installed
   * applications, and rebuilding the list when a change in configuration requires
   * this (such as a locale change).
   *
   * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/LoaderCustom.java
   *      loader}
   *
   * <p>An example implementation of a fragment that uses the above loader to show
   * the currently installed applications in a list is below.
   *
   * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/LoaderCustom.java
   *      fragment}
   *
   * @param <D> the data type to be loaded.
   */
  public abstract class AsyncTaskLoader<D> extends Loader<D> {
      static final String TAG = "AsyncTaskLoader";
      static final boolean DEBUG = false;
  
      final class LoadTask extends AsyncTask<Void, Void, D> implements Runnable {
          private final CountDownLatch mDone = new CountDownLatch(1);
  
          // Set to true to indicate that the task has been posted to a handler for
          // execution at a later time.  Used to throttle updates.
          boolean waiting;
  
          /* Runs on a worker thread */
          @Override
          protected D doInBackground(Void... params) {
              if (DEBUG) Log.v(TAG, this + " >>> doInBackground");
              try {
                  D data = AsyncTaskLoader.this.onLoadInBackground();
                  if (DEBUG) Log.v(TAG, this + "  <<< doInBackground");
                  return data;
              } catch (OperationCanceledException ex) {
                  if (!isCancelled()) {
                      // onLoadInBackground threw a canceled exception spuriously.
                      // This is problematic because it means that the LoaderManager did not
                      // cancel the Loader itself and still expects to receive a result.
                      // Additionally, the Loader's own state will not have been updated to
                      // reflect the fact that the task was being canceled.
                      // So we treat this case as an unhandled exception.
                      throw ex;
                  }
                  if (DEBUG) Log.v(TAG, this + "  <<< doInBackground (was canceled)", ex);
                  return null;
              }
          }
  
          /* Runs on the UI thread */
          @Override
          protected void onPostExecute(D data) {
              if (DEBUG) Log.v(TAG, this + " onPostExecute");
              try {
                  AsyncTaskLoader.this.dispatchOnLoadComplete(this, data);
              } finally {
                  mDone.countDown();
              }
          }
  
          /* Runs on the UI thread */
          @Override
          protected void onCancelled(D data) {
              if (DEBUG) Log.v(TAG, this + " onCancelled");
              try {
                  AsyncTaskLoader.this.dispatchOnCancelled(this, data);
              } finally {
                  mDone.countDown();
              }
          }
  
          /* Runs on the UI thread, when the waiting task is posted to a handler.
           * This method is only executed when task execution was deferred (waiting was true). */
          @Override
          public void run() {
              waiting = false;
              AsyncTaskLoader.this.executePendingTask();
          }
  
          /* Used for testing purposes to wait for the task to complete. */
          public void waitForLoader() {
              try {
                  mDone.await();
              } catch (InterruptedException e) {
                  // Ignore
              }
          }
      }
  
      private final Executor mExecutor;
  
      volatile LoadTask mTask;
      volatile LoadTask mCancellingTask;
  
      long mUpdateThrottle;
      long mLastLoadCompleteTime = -10000;
      Handler mHandler;
  
      public AsyncTaskLoader(Context context) {
          this(context, AsyncTask.THREAD_POOL_EXECUTOR);
      }
  
      public AsyncTaskLoader(Context context, Executor executor) {
          super(context);
          mExecutor = executor;
      }
  
      /**
       * Set amount to throttle updates by.  This is the minimum time from
       * when the last {@link #loadInBackground()} call has completed until
       * a new load is scheduled.
       *
       * @param delayMS Amount of delay, in milliseconds.
       */
      public void setUpdateThrottle(long delayMS) {
          mUpdateThrottle = delayMS;
          if (delayMS != 0) {
              mHandler = new Handler();
          }
      }
  
      @Override
      protected void onForceLoad() {
          super.onForceLoad();
          cancelLoad();
          mTask = new LoadTask();
          if (DEBUG) Log.v(TAG, "Preparing load: mTask=" + mTask);
          executePendingTask();
      }
  
      @Override
      protected boolean onCancelLoad() {
          if (DEBUG) Log.v(TAG, "onCancelLoad: mTask=" + mTask);
          if (mTask != null) {
              if (mCancellingTask != null) {
                  // There was a pending task already waiting for a previous
                  // one being canceled; just drop it.
                  if (DEBUG) Log.v(TAG,
                          "cancelLoad: still waiting for cancelled task; dropping next");
                  if (mTask.waiting) {
                      mTask.waiting = false;
                      mHandler.removeCallbacks(mTask);
                  }
                  mTask = null;
                  return false;
              } else if (mTask.waiting) {
                  // There is a task, but it is waiting for the time it should
                  // execute.  We can just toss it.
                  if (DEBUG) Log.v(TAG, "cancelLoad: task is waiting, dropping it");
                  mTask.waiting = false;
                  mHandler.removeCallbacks(mTask);
                  mTask = null;
                  return false;
              } else {
                  boolean cancelled = mTask.cancel(false);
                  if (DEBUG) Log.v(TAG, "cancelLoad: cancelled=" + cancelled);
                  if (cancelled) {
                      mCancellingTask = mTask;
                      cancelLoadInBackground();
                  }
                  mTask = null;
                  return cancelled;
              }
          }
          return false;
      }
      
      public boolean cancelLoad() {
          return onCancelLoad();
      }
  
      /**
       * Called if the task was canceled before it was completed.  Gives the class a chance
       * to clean up post-cancellation and to properly dispose of the result.
       *
       * @param data The value that was returned by {@link #loadInBackground}, or null
       * if the task threw {@link OperationCanceledException}.
       */
      public void onCanceled(D data) {
      }
  
      void executePendingTask() {
          if (mCancellingTask == null && mTask != null) {
              if (mTask.waiting) {
                  mTask.waiting = false;
                  mHandler.removeCallbacks(mTask);
              }
              if (mUpdateThrottle > 0) {
                  long now = SystemClock.uptimeMillis();
                  if (now < (mLastLoadCompleteTime+mUpdateThrottle)) {
                      // Not yet time to do another load.
                      if (DEBUG) Log.v(TAG, "Waiting until "
                              + (mLastLoadCompleteTime+mUpdateThrottle)
                              + " to execute: " + mTask);
                      mTask.waiting = true;
                      mHandler.postAtTime(mTask, mLastLoadCompleteTime+mUpdateThrottle);
                      return;
                  }
              }
              if (DEBUG) Log.v(TAG, "Executing: " + mTask);
              mTask.executeOnExecutor(mExecutor, (Void[]) null);
          }
      }
  
      @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
      void dispatchOnCancelled(LoadTask task, D data) {
          onCanceled(data);
          if (mCancellingTask == task) {
              if (DEBUG) Log.v(TAG, "Cancelled task is now canceled!");
              if(Utils.hasJellyBeanMR2()){
              	rollbackContentChanged();
              }
              mLastLoadCompleteTime = SystemClock.uptimeMillis();
              mCancellingTask = null;
              if (DEBUG) Log.v(TAG, "Delivering cancellation");
              if(Utils.hasJellyBeanMR2()){
              	deliverCancellation();
              }
              executePendingTask();
          }
      }
  
      @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
      void dispatchOnLoadComplete(LoadTask task, D data) {
          if (mTask != task) {
              if (DEBUG) Log.v(TAG, "Load complete of old task, trying to cancel");
              dispatchOnCancelled(task, data);
          } else {
              if (isAbandoned()) {
                  // This cursor has been abandoned; just cancel the new data.
                  onCanceled(data);
              } else {
              	if(Utils.hasJellyBeanMR2()){
              		commitContentChanged();
              	}
                  mLastLoadCompleteTime = SystemClock.uptimeMillis();
                  mTask = null;
                  if (DEBUG) Log.v(TAG, "Delivering result");
                  deliverResult(data);
              }
          }
      }
  
      /**
       * Called on a worker thread to perform the actual load and to return
       * the result of the load operation.
       *
       * Implementations should not deliver the result directly, but should return them
       * from this method, which will eventually end up calling {@link #deliverResult} on
       * the UI thread.  If implementations need to process the results on the UI thread
       * they may override {@link #deliverResult} and do so there.
       *
       * To support cancellation, this method should periodically check the value of
       * {@link #isLoadInBackgroundCanceled} and terminate when it returns true.
       * Subclasses may also override {@link #cancelLoadInBackground} to interrupt the load
       * directly instead of polling {@link #isLoadInBackgroundCanceled}.
       *
       * When the load is canceled, this method may either return normally or throw
       * {@link OperationCanceledException}.  In either case, the {@link Loader} will
       * call {@link #onCanceled} to perform post-cancellation cleanup and to dispose of the
       * result object, if any.
       *
       * @return The result of the load operation.
       *
       * @throws OperationCanceledException if the load is canceled during execution.
       *
       * @see #isLoadInBackgroundCanceled
       * @see #cancelLoadInBackground
       * @see #onCanceled
       */
      public abstract D loadInBackground();
  
      /**
       * Calls {@link #loadInBackground()}.
       *
       * This method is reserved for use by the loader framework.
       * Subclasses should override {@link #loadInBackground} instead of this method.
       *
       * @return The result of the load operation.
       *
       * @throws OperationCanceledException if the load is canceled during execution.
       *
       * @see #loadInBackground
       */
      protected D onLoadInBackground() {
          return loadInBackground();
      }
  
      /**
       * Called on the main thread to abort a load in progress.
       *
       * Override this method to abort the current invocation of {@link #loadInBackground}
       * that is running in the background on a worker thread.
       *
       * This method should do nothing if {@link #loadInBackground} has not started
       * running or if it has already finished.
       *
       * @see #loadInBackground
       */
      public void cancelLoadInBackground() {
      }
  
      /**
       * Returns true if the current invocation of {@link #loadInBackground} is being canceled.
       *
       * @return True if the current invocation of {@link #loadInBackground} is being canceled.
       *
       * @see #loadInBackground
       */
      public boolean isLoadInBackgroundCanceled() {
          return mCancellingTask != null;
      }
  
      /**
       * Locks the current thread until the loader completes the current load
       * operation. Returns immediately if there is no load operation running.
       * Should not be called from the UI thread: calling it from the UI
       * thread would cause a deadlock.
       * <p>
       * Use for testing only.  <b>Never</b> call this from a UI thread.
       *
       */
      public void waitForLoader() {
          LoadTask task = mTask;
          if (task != null) {
              task.waitForLoader();
          }
      }
  
      @Override
      public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
          super.dump(prefix, fd, writer, args);
          if (mTask != null) {
              writer.print(prefix); writer.print("mTask="); writer.print(mTask);
                      writer.print(" waiting="); writer.println(mTask.waiting);
          }
          if (mCancellingTask != null) {
              writer.print(prefix); writer.print("mCancellingTask="); writer.print(mCancellingTask);
                      writer.print(" waiting="); writer.println(mCancellingTask.waiting);
          }
          /*if (mUpdateThrottle != 0) {
              writer.print(prefix); writer.print("mUpdateThrottle=");
                      TimeUtils.formatDuration(mUpdateThrottle, writer);
                      writer.print(" mLastLoadCompleteTime=");
                      TimeUtils.formatDuration(mLastLoadCompleteTime,
                              SystemClock.uptimeMillis(), writer);
                      writer.println();
          }*/
      }
  }
  