content: /*
   * Copyright (C) 2013 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 com.android.bitmap;
  
  import android.util.Log;
  import android.util.LruCache;
  
  import com.android.bitmap.ReusableBitmap.NullReusableBitmap;
  import com.android.bitmap.util.Trace;
  
  /**
   * This subclass provides custom pool behavior. The pool can be set to block on {@link #poll()} if
   * nothing can be returned. This is useful if you know you will incur high costs upon receiving
   * nothing from the pool, and you do not want to incur those costs at the critical moment when the
   * UI is animating.
   *
   * This subclass provides custom cache behavior. Null values can be cached. Later,
   * when the same key is used to retrieve the value, a {@link NullReusableBitmap} singleton will
   * be returned.
   */
  public class UnrefedBitmapCache extends UnrefedPooledCache<RequestKey, ReusableBitmap>
          implements BitmapCache {
      private boolean mBlocking = false;
      private final Object mLock = new Object();
  
      private LruCache<RequestKey, NullReusableBitmap> mNullRequests;
  
      private final static boolean DEBUG = DecodeTask.DEBUG;
      private final static String TAG = UnrefedBitmapCache.class.getSimpleName();
  
      public UnrefedBitmapCache(final int targetSizeBytes, final float nonPooledFraction,
              final int nullCapacity) {
          super(targetSizeBytes, nonPooledFraction);
  
          if (nullCapacity > 0) {
              mNullRequests = new LruCache<RequestKey, NullReusableBitmap>(nullCapacity);
          }
      }
  
      /**
       * Declare that {@link #poll()} should now block until it can return something.
       */
      @Override
      public void setBlocking(final boolean blocking) {
          synchronized (mLock) {
              if (DEBUG) {
                  Log.d(TAG, String.format("AltBitmapCache: block %b", blocking));
              }
              mBlocking = blocking;
              if (!mBlocking) {
                  // no longer blocking. Notify every thread.
                  mLock.notifyAll();
              }
          }
      }
  
      @Override
      protected int sizeOf(final ReusableBitmap value) {
          return value.getByteCount();
      }
  
      /**
       * If {@link #setBlocking(boolean)} has been called with true, this method will block until a
       * resource is available.
       * @return an available resource, or null if none are available. Null will never be returned
       * until blocking is set to false.
       */
      @Override
      public ReusableBitmap poll() {
          ReusableBitmap bitmap;
          synchronized (mLock) {
              while ((bitmap = super.poll()) == null && mBlocking) {
                  if (DEBUG) {
                      Log.d(TAG, String.format(
                              "AltBitmapCache: %s waiting", Thread.currentThread().getName()));
                  }
                  Trace.beginSection("sleep");
                  try {
                      // block
                      mLock.wait();
                      if (DEBUG) {
                          Log.d(TAG, String.format("AltBitmapCache: %s notified",
                                  Thread.currentThread().getName()));
                      }
                  } catch (InterruptedException ignored) {
                  }
                  Trace.endSection();
              }
          }
          return bitmap;
      }
  
      @Override
      public void offer(final ReusableBitmap value) {
          synchronized (mLock) {
              super.offer(value);
              if (DEBUG) {
                  Log.d(TAG, "AltBitmapCache: offer +1");
              }
              // new resource gained. Notify one thread.
              mLock.notify();
          }
      }
  
      @Override
      public ReusableBitmap get(final RequestKey key, final boolean incrementRefCount) {
          if (mNullRequests != null && mNullRequests.get(key) != null) {
              return NullReusableBitmap.getInstance();
          }
          return super.get(key, incrementRefCount);
      }
  
      /**
       * Note: The cache only supports same-sized bitmaps.
       */
      @Override
      public ReusableBitmap put(final RequestKey key, final ReusableBitmap value) {
          if (mNullRequests != null && (value == null || value == NullReusableBitmap.getInstance())) {
              mNullRequests.put(key, NullReusableBitmap.getInstance());
              return null;
          }
  
          return super.put(key, value);
      }
  }
  