gt:                 bitmapItem = mMemoryCache.get(bucketKey);
context: /*
   * Copyright (C) 2014 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.tv.settings.widget;
  
  import android.app.ActivityManager;
  import android.content.Context;
  import android.graphics.Bitmap;
  import android.util.Log;
  import android.util.LruCache;
  import android.widget.ImageView;
  
  import com.android.tv.settings.util.AccountImageChangeObserver;
  import com.android.tv.settings.util.UriUtils;
  import com.android.tv.settings.R;
  
  import java.lang.ref.SoftReference;
  import java.util.concurrent.Executor;
  import java.util.concurrent.Executors;
  
  /**
   * Downloader class which loads a resource URI into an image view.
   * <p>
   * This class adds a cache over BitmapWorkerTask.
   */
  public class BitmapDownloader {
  
      private static final String TAG = "BitmapDownloader";
  
      private static final boolean DEBUG = false;
  
      private static final int CORE_POOL_SIZE = 5;
  
      private static final Executor BITMAP_DOWNLOADER_THREAD_POOL_EXECUTOR =
              Executors.newFixedThreadPool(CORE_POOL_SIZE);
  
      // 1/4 of max memory is used for bitmap mem cache
      private static final int MEM_TO_CACHE = 4;
  
      // hard limit for bitmap mem cache in MB
      private static final int CACHE_HARD_LIMIT = 32;
  
      /**
       * bitmap cache item structure saved in LruCache
       */
      private static class BitmapItem {
          /**
           * cached bitmap
           */
          Bitmap mBitmap;
          /**
           * indicate if the bitmap is scaled down from original source (never scale up)
           */
          boolean mScaled;
  
          public BitmapItem(Bitmap bitmap, boolean scaled) {
              mBitmap = bitmap;
              mScaled = scaled;
          }
      }
  
      private LruCache<String, BitmapItem> mMemoryCache;
  
      private static BitmapDownloader sBitmapDownloader;
  
      private static final Object sBitmapDownloaderLock = new Object();
  
      // Bitmap cache also uses size of Bitmap as part of key.
      // Bitmap cache is divided into following buckets by height:
      // TODO: we currently care more about height, what about width in key?
      // height <= 128, 128 < height <= 512, height > 512
      // Different bitmap cache buckets save different bitmap cache items.
      // Bitmaps within same bucket share the largest cache item.
      private static final int[] SIZE_BUCKET = new int[]{128, 512, Integer.MAX_VALUE};
  
      public static abstract class BitmapCallback {
          SoftReference<BitmapWorkerTask> mTask;
  
          public abstract void onBitmapRetrieved(Bitmap bitmap);
      }
  
      /**
       * get the singleton BitmapDownloader for the application
       */
      public final static BitmapDownloader getInstance(Context context) {
          if (sBitmapDownloader == null) {
              synchronized(sBitmapDownloaderLock) {
                  if (sBitmapDownloader == null) {
                      sBitmapDownloader = new BitmapDownloader(context);
                  }
              }
          }
          return sBitmapDownloader;
      }
  
      public BitmapDownloader(Context context) {
          int memClass = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE))
                  .getMemoryClass();
          memClass = memClass / MEM_TO_CACHE;
          if (memClass > CACHE_HARD_LIMIT) {
              memClass = CACHE_HARD_LIMIT;
          }
          int cacheSize = 1024 * 1024 * memClass;
          mMemoryCache = new LruCache<String, BitmapItem>(cacheSize) {
              @Override
              protected int sizeOf(String key, BitmapItem bitmap) {
                  return bitmap.mBitmap.getByteCount();
              }
          };
      }
  
      /**
       * load bitmap in current thread, will *block* current thread.
       * FIXME: Should avoid using this function at all cost.
       * @deprecated
       */
      @Deprecated
      public final Bitmap loadBitmapBlocking(BitmapWorkerOptions options) {
          final boolean hasAccountImageUri = UriUtils.isAccountImageUri(options.getResourceUri());
          Bitmap bitmap = null;
          if (hasAccountImageUri) {
              AccountImageChangeObserver.getInstance().registerChangeUriIfPresent(options);
          } else {
              bitmap = getBitmapFromMemCache(options);
          }
  
          if (bitmap == null) {
              BitmapWorkerTask task = new BitmapWorkerTask(null) {
                  @Override
                  protected Bitmap doInBackground(BitmapWorkerOptions... params) {
                      final Bitmap bitmap = super.doInBackground(params);
                      if (bitmap != null && !hasAccountImageUri) {
                          addBitmapToMemoryCache(params[0], bitmap, isScaled());
                      }
                      return bitmap;
                  }
              };
  
              return task.doInBackground(options);
          }
          return bitmap;
      }
  
      /**
       * Loads the bitmap into the image view.
       */
      public void loadBitmap(BitmapWorkerOptions options, final ImageView imageView) {
          cancelDownload(imageView);
          final boolean hasAccountImageUri = UriUtils.isAccountImageUri(options.getResourceUri());
          Bitmap bitmap = null;
          if (hasAccountImageUri) {
              AccountImageChangeObserver.getInstance().registerChangeUriIfPresent(options);
          } else {
              bitmap = getBitmapFromMemCache(options);
          }
  
          if (bitmap != null) {
              imageView.setImageBitmap(bitmap);
          } else {
              BitmapWorkerTask task = new BitmapWorkerTask(imageView) {
                  @Override
                  protected Bitmap doInBackground(BitmapWorkerOptions... params) {
                      Bitmap bitmap = super.doInBackground(params);
                      if (bitmap != null && !hasAccountImageUri) {
                          addBitmapToMemoryCache(params[0], bitmap, isScaled());
                      }
                      return bitmap;
                  }
              };
              imageView.setTag(R.id.imageDownloadTask, new SoftReference<BitmapWorkerTask>(task));
              task.execute(options);
          }
      }
  
      /**
       * Loads the bitmap.
       * <p>
       * This will be sent back to the callback object.
       */
      public void getBitmap(BitmapWorkerOptions options, final BitmapCallback callback) {
          cancelDownload(callback);
          final boolean hasAccountImageUri = UriUtils.isAccountImageUri(options.getResourceUri());
          final Bitmap bitmap = hasAccountImageUri ? null : getBitmapFromMemCache(options);
          if (hasAccountImageUri) {
              AccountImageChangeObserver.getInstance().registerChangeUriIfPresent(options);
          }
  
          BitmapWorkerTask task = new BitmapWorkerTask(null) {
              @Override
              protected Bitmap doInBackground(BitmapWorkerOptions... params) {
                  if (bitmap != null) {
                      return bitmap;
                  }
                  final Bitmap bitmap = super.doInBackground(params);
                  if (bitmap != null && !hasAccountImageUri) {
                      addBitmapToMemoryCache(params[0], bitmap, isScaled());
                  }
                  return bitmap;
              }
  
              @Override
              protected void onPostExecute(Bitmap bitmap) {
                  callback.onBitmapRetrieved(bitmap);
              }
          };
          callback.mTask = new SoftReference<BitmapWorkerTask>(task);
          task.executeOnExecutor(BITMAP_DOWNLOADER_THREAD_POOL_EXECUTOR, options);
      }
  
      /**
       * Cancel download<p>
       * @param key {@link BitmapCallback} or {@link ImageView}
       */
      public boolean cancelDownload(Object key) {
          BitmapWorkerTask task = null;
          if (key instanceof ImageView) {
              ImageView imageView = (ImageView)key;
              SoftReference<BitmapWorkerTask> softReference =
                      (SoftReference<BitmapWorkerTask>) imageView.getTag(R.id.imageDownloadTask);
              if (softReference != null) {
                  task = softReference.get();
                  softReference.clear();
              }
          } else if (key instanceof BitmapCallback) {
              BitmapCallback callback = (BitmapCallback)key;
              if (callback.mTask != null) {
                  task = callback.mTask.get();
                  callback.mTask = null;
              }
          }
          if (task != null) {
              return task.cancel(true);
          }
          return false;
      }
  
      private static String getBucketKey(String baseKey, Bitmap.Config bitmapConfig, int width) {
          for (int i = 0; i < SIZE_BUCKET.length; i++) {
              if (width <= SIZE_BUCKET[i]) {
                  return new StringBuilder(baseKey.length() + 16).append(baseKey)
                          .append(":").append(bitmapConfig == null ? "" : bitmapConfig.ordinal())
                          .append(":").append(SIZE_BUCKET[i]).toString();
              }
          }
          // should never happen because last bucket is Integer.MAX_VALUE
          throw new RuntimeException();
      }
  
      private void addBitmapToMemoryCache(BitmapWorkerOptions key, Bitmap bitmap, boolean isScaled) {
          if (!key.isMemCacheEnabled()) {
              return;
          }
          String bucketKey = getBucketKey(
                  key.getCacheKey(), key.getBitmapConfig(), bitmap.getHeight());
          BitmapItem bitmapItem = mMemoryCache.get(bucketKey);
          if (bitmapItem != null) {
              Bitmap currentBitmap = bitmapItem.mBitmap;
              // If somebody else happened to get a larger one in the bucket, discard our bitmap.
              // TODO: need a better way to prevent current downloading for the same Bitmap
              if (currentBitmap.getWidth() >= bitmap.getWidth() && currentBitmap.getHeight()
                      >= bitmap.getHeight()) {
                  return;
              }
          }
          if (DEBUG) {
              Log.d(TAG, "add cache "+bucketKey+" isScaled = "+isScaled);
          }
          bitmapItem = new BitmapItem(bitmap, isScaled);
          mMemoryCache.put(bucketKey, bitmapItem);
      }
  
      private Bitmap getBitmapFromMemCache(BitmapWorkerOptions key) {
          if (key.getHeight() != BitmapWorkerOptions.MAX_IMAGE_DIMENSION_PX) {
              // 1. find the bitmap in the size bucket
              String bucketKey =
                      getBucketKey(key.getCacheKey(), key.getBitmapConfig(), key.getHeight());
              BitmapItem bitmapItem = mMemoryCache.get(bucketKey);
              if (bitmapItem != null) {
                  Bitmap bitmap = bitmapItem.mBitmap;
                  // now we have the bitmap in the bucket, use it when the bitmap is not scaled or
                  // if the size is larger than or equals to the output size
                  if (!bitmapItem.mScaled) {
                      return bitmap;
                  }
                  if (bitmap.getHeight() >= key.getHeight()) {
                      return bitmap;
                  }
              }
              // 2. find un-scaled bitmap in smaller buckets.  If the un-scaled bitmap exists
              // in higher buckets,  we still need to scale it down.  Right now we just
              // return null and let the BitmapWorkerTask to do the same job again.
              // TODO: use the existing unscaled bitmap and we don't need to load it from resource
              // or network again.
              for (int i = SIZE_BUCKET.length - 1; i >= 0; i--) {
                  if (SIZE_BUCKET[i] >= key.getHeight()) {
                      continue;
                  }
                  bucketKey = getBucketKey(key.getCacheKey(), key.getBitmapConfig(), SIZE_BUCKET[i]);
  