/*
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

package com.facebook.drawee.components;

import java.util.HashSet;
import java.util.Set;

import android.os.Handler;
import android.os.Looper;

/**
 * Component that defers {@code release} until after the main Looper has completed its current
 * message. Although we would like for defer {@code release} to happen immediately after the current
 * message is done, this is not guaranteed as there might be other messages after the current one,
 * but before the deferred one, pending in the Looper's queue.
 * <p>
 * onDetach / onAttach events are used for releasing / acquiring resources. However, sometimes we
 * get an onDetach event followed by an onAttach event within the same loop. In order to avoid
 * overaggressive resource releasing / acquiring, we defer releasing. If onAttach happens within
 * the same loop, we will simply cancel corresponding deferred release, avoiding an unnecessary
 * resource release / acquire cycle. If onAttach doesn't happen before the deferred message gets
 * executed, the resources will be released.
 * <p>
 * This class is not thread-safe and should only be used from the main thread (UI thread).
 */
public class DeferredReleaser {

  private static DeferredReleaser sInstance = null;

  public static synchronized DeferredReleaser getInstance() {
    if (sInstance == null) {
      sInstance = new DeferredReleaser();
    }
    return sInstance;
  }

  public interface Releasable {
    public void release();
  }

  private final Set<Releasable> mPendingReleasables;
  private final Handler mUiHandler;

  public DeferredReleaser() {
    mPendingReleasables =  new HashSet<Releasable>();
    mUiHandler = new Handler(Looper.getMainLooper());
  }

  /*
   * Walks through the set of pending releasables, and calls release on them.
   * Resets the pending list to an empty list when done.
   */
  private final Runnable releaseRunnable = new Runnable() {
    @Override
    public void run() {
      for (Releasable releasable : mPendingReleasables) {
        releasable.release();
      }
      mPendingReleasables.clear();
    }
  };

  /**
   * Schedules deferred release.
   * <p>
   * The object will be released after the current Looper's loop,
   * unless {@code cancelDeferredRelease} is called before then.
   * @param releasable Object to release.
   */
  public void scheduleDeferredRelease(Releasable releasable) {
    if (!mPendingReleasables.add(releasable)) {
      return;
    }
    // Posting to the UI queue is an O(n) operation, so we only do it once.
    // The one runnable does all the releases.
    if (mPendingReleasables.size() == 1) {
      mUiHandler.post(releaseRunnable);
    }
  }

  /**
   * Cancels a pending release for this object.
   * @param releasable Object to cancel release of.
   */
  public void cancelDeferredRelease(Releasable releasable) {
    mPendingReleasables.remove(releasable);
  }

}

@@ -446,16 +446,68 @@
 ooper;%0A%0A
+import com.facebook.common.internal.Preconditions;%0A%0A
 /**%0A * C
@@ -2286,24 +2286,50 @@
 oid run() %7B%0A
+      ensureOnUiThread();%0A
       for (R
@@ -2370,24 +2370,24 @@
 easables) %7B%0A
-
         rele
@@ -2745,24 +2745,49 @@
 leasable) %7B%0A
+    ensureOnUiThread();%0A%0A
     if (!mPe
@@ -3228,24 +3228,48 @@
 leasable) %7B%0A
+    ensureOnUiThread();%0A
     mPending
@@ -3305,10 +3305,149 @@
 );%0A  %7D%0A%0A
+  private static void ensureOnUiThread() %7B%0A    Preconditions.checkState(Looper.getMainLooper().getThread() == Thread.currentThread());%0A  %7D%0A
 %7D%0A

Throw if DeferredReleaser is called on non-ui thread