/**
   * * Remove a url from the cache
   *
   * @param url
   * @return The bitmap removed, if any.
   */
  public static Bitmap remove(String url) {
    new File(getFilenameForUrl(url)).delete();

    Drawable drawable = mLiveCache.remove(url);
    if (drawable instanceof ZombieDrawable) {
      ZombieDrawable zombie = (ZombieDrawable) drawable;
      Bitmap ret = zombie.getBitmap();
      zombie.headshot();
      return ret;
    }

    return null;
  }
  /**
   * Download and shrink an Image located at a specified URL, and display it in the provided {@link
   * ImageView}.
   *
   * @param context A {@link Context} to allow setUrlDrawable to load and save files.
   * @param imageView The {@link ImageView} to display the image to after it is loaded.
   * @param url The URL of the image that should be loaded.
   * @param defaultDrawable A {@link Drawable} that should be displayed in {@code imageView} while
   *     the image has not been loaded. This image will also be displayed if the image fails to
   *     load. This can be set to {@code null}.
   * @param cacheDurationMs The length of time, in milliseconds, that this image should be cached
   *     locally.
   * @param callback An instance of {@link UrlImageViewCallback} that is called when the image
   *     successfully finishes loading. This value can be null.
   */
  private static void setUrlDrawable(
      final Context context,
      final ImageView imageView,
      final String url,
      final Drawable defaultDrawable,
      final long cacheDurationMs,
      final UrlImageViewCallback callback) {
    cleanup(context);
    // disassociate this ImageView from any pending downloads
    if (isNullOrEmpty(url)) {
      if (imageView != null) {
        imageView.setImageDrawable(defaultDrawable);
      }
      return;
    }

    final int tw;
    final int th;
    if (mMetrics == null) prepareResources(context);
    tw = mMetrics.widthPixels;
    th = mMetrics.heightPixels;

    final String filename = context.getFileStreamPath(getFilenameForUrl(url)).getAbsolutePath();
    final File file = new File(filename);

    if (mDeadCache == null) {
      mDeadCache = new UrlLruCache(getHeapSize(context) / 8);
    }
    Drawable drawable;
    final BitmapDrawable bd = mDeadCache.remove(url);
    if (bd != null) {
      // this drawable was resurrected, it should not be in the live cache
      clog("zombie load: " + url);
      Assert.assertTrue(url, !mAllCache.contains(bd));
      drawable = new ZombieDrawable(url, bd);
    } else {
      drawable = mLiveCache.get(url);
    }

    if (drawable != null) {
      clog("Cache hit on: " + url);
      // if the file age is older than the cache duration, force a refresh.
      // note that the file must exist, otherwise it is using a default.
      // not checking for file existence would do a network call on every
      // 404 or failed load.
      if (file.exists() && !checkCacheDuration(file, cacheDurationMs)) {
        clog("Cache hit, but file is stale. Forcing reload: " + url);
        if (drawable instanceof ZombieDrawable) ((ZombieDrawable) drawable).headshot();
        drawable = null;
      } else {
        clog("Using cached: " + url);
      }
    }

    if (drawable != null) {
      if (imageView != null) {
        imageView.setImageDrawable(drawable);
      }
      if (callback != null) {
        callback.onLoaded(imageView, drawable, url, true);
      }
      return;
    }

    // oh noes, at this point we definitely do not have the file available in memory
    // let's prepare for an asynchronous load of the image.

    // null it while it is downloading
    if (imageView != null) {
      imageView.setImageDrawable(defaultDrawable);
    }

    // since listviews reuse their views, we need to
    // take note of which url this view is waiting for.
    // This may change rapidly as the list scrolls or is filtered, etc.
    clog("Waiting for " + url);
    if (imageView != null) {
      mPendingViews.put(imageView, url);
    }

    final ArrayList<ImageView> currentDownload = mPendingDownloads.get(url);
    if (currentDownload != null) {
      // Also, multiple vies may be waiting for this url.
      // So, let's maintain a list of these views.
      // When the url is downloaded, it sets the imagedrawable for
      // every view in the list. It needs to also validate that
      // the imageview is still waiting for this url.
      if (imageView != null) {
        currentDownload.add(imageView);
      }
      return;
    }

    final ArrayList<ImageView> downloads = new ArrayList<ImageView>();
    if (imageView != null) {
      downloads.add(imageView);
    }
    mPendingDownloads.put(url, downloads);

    final int targetWidth = tw <= 0 ? Integer.MAX_VALUE : tw;
    final int targetHeight = th <= 0 ? Integer.MAX_VALUE : th;
    final Loader loader =
        new Loader() {
          @Override
          public void run() {
            try {
              result = loadDrawableFromStream(context, url, filename, targetWidth, targetHeight);
            } catch (final Exception ex) {
            }
          }
        };

    final Runnable completion =
        new Runnable() {
          @Override
          public void run() {
            Assert.assertEquals(Looper.myLooper(), Looper.getMainLooper());
            Drawable usableResult = loader.result;
            if (usableResult == null) {
              usableResult = defaultDrawable;
            }
            mPendingDownloads.remove(url);
            mLiveCache.put(url, usableResult);
            if (callback != null && imageView == null)
              callback.onLoaded(null, loader.result, url, false);
            int waitingCount = 0;
            for (final ImageView iv : downloads) {
              // validate the url it is waiting for
              final String pendingUrl = mPendingViews.get(iv);
              if (!url.equals(pendingUrl)) {
                clog("Ignoring out of date request to update view for " + url);
                continue;
              }
              waitingCount++;
              mPendingViews.remove(iv);
              if (usableResult != null) {
                //                        System.out.println(String.format("imageView: %dx%d,
                // %dx%d", imageView.getMeasuredWidth(), imageView.getMeasuredHeight(),
                // imageView.getWidth(), imageView.getHeight()));
                iv.setImageDrawable(usableResult);
                //                        System.out.println(String.format("imageView: %dx%d,
                // %dx%d", imageView.getMeasuredWidth(), imageView.getMeasuredHeight(),
                // imageView.getWidth(), imageView.getHeight()));
                if (callback != null && iv == imageView)
                  callback.onLoaded(iv, loader.result, url, false);
              }
            }
            clog("Populated: " + waitingCount);
          }
        };

    if (file.exists()) {
      try {
        if (checkCacheDuration(file, cacheDurationMs)) {
          clog(
              "File Cache hit on: "
                  + url
                  + ". "
                  + (System.currentTimeMillis() - file.lastModified())
                  + "ms old.");

          final AsyncTask<Void, Void, Void> fileloader =
              new AsyncTask<Void, Void, Void>() {
                @Override
                protected Void doInBackground(final Void... params) {
                  loader.run();
                  return null;
                }

                @Override
                protected void onPostExecute(final Void result) {
                  completion.run();
                }
              };
          executeTask(fileloader);
          return;
        } else {
          clog("File cache has expired. Refreshing.");
        }
      } catch (final Exception ex) {
      }
    }

    mDownloader.download(context, url, filename, loader, completion);
  }
  /**
   * Download and shrink an Image located at a specified URL, and display it in the provided {@link
   * ImageView}.
   *
   * @param context A {@link Context} to allow setUrlDrawable to load and save files.
   * @param imageView The {@link ImageView} to display the image to after it is loaded.
   * @param url The URL of the image that should be loaded.
   * @param defaultDrawable A {@link Drawable} that should be displayed in {@code imageView} while
   *     the image has not been loaded. This image will also be displayed if the image fails to
   *     load. This can be set to {@code null}.
   * @param cacheDurationMs The length of time, in milliseconds, that this image should be cached
   *     locally.
   * @param callback An instance of {@link UrlImageViewCallback} that is called when the image
   *     successfully finishes loading. This value can be null.
   */
  private static void setUrlDrawable(
      final Context context,
      final ImageView imageView,
      final String url,
      final Drawable defaultDrawable,
      final long cacheDurationMs,
      final UrlImageViewCallback callback) {
    assert (Looper.getMainLooper().getThread() == Thread.currentThread())
        : "setUrlDrawable and loadUrlDrawable should only be called from the main thread.";
    cleanup(context);
    // disassociate this ImageView from any pending downloads
    if (isNullOrEmpty(url)) {
      if (imageView != null) {
        mPendingViews.remove(imageView);
        imageView.setImageDrawable(defaultDrawable);
      }
      return;
    }

    final int tw;
    final int th;
    if (mMetrics == null) prepareResources(context);
    tw = mMetrics.widthPixels;
    th = mMetrics.heightPixels;

    final String filename = context.getFileStreamPath(getFilenameForUrl(url)).getAbsolutePath();
    final File file = new File(filename);

    // check the dead and live cache to see if we can find this url's bitmap
    if (mDeadCache == null) {
      mDeadCache = new LruBitmapCache(getHeapSize(context) / 8);
    }
    Drawable drawable = null;
    Bitmap bitmap = mDeadCache.remove(url);
    if (bitmap != null) {
      clog("zombie load: " + url);
    } else {
      drawable = mLiveCache.get(url);
    }

    // if something was found, verify it was fresh.
    if (drawable != null || bitmap != null) {
      clog("Cache hit on: " + url);
      // if the file age is older than the cache duration, force a refresh.
      // note that the file must exist, otherwise it is using a default.
      // not checking for file existence would do a network call on every
      // 404 or failed load.
      if (file.exists() && !checkCacheDuration(file, cacheDurationMs)) {
        clog("Cache hit, but file is stale. Forcing reload: " + url);
        if (drawable != null && drawable instanceof ZombieDrawable)
          ((ZombieDrawable) drawable).headshot();
        drawable = null;
        bitmap = null;
      } else {
        clog("Using cached: " + url);
      }
    }

    // if the bitmap is fresh, set the imageview
    if (drawable != null || bitmap != null) {
      if (imageView != null) {
        mPendingViews.remove(imageView);
        if (drawable instanceof ZombieDrawable)
          drawable = ((ZombieDrawable) drawable).clone(mResources);
        else if (bitmap != null) drawable = new ZombieDrawable(url, mResources, bitmap);

        imageView.setImageDrawable(drawable);
      }
      // invoke any bitmap callbacks
      if (callback != null) {
        // when invoking the callback from cache, check to see if this was
        // a drawable that was successfully loaded from the filesystem or url.
        // this will be indicated by it being a ZombieDrawable (ie, something we are managing).
        // The default drawables will be BitmapDrawables (or whatever else the user passed in).
        if (bitmap == null && drawable instanceof ZombieDrawable)
          bitmap = ((ZombieDrawable) drawable).getBitmap();
        callback.onLoaded(imageView, bitmap, url, true);
      }
      return;
    }

    // oh noes, at this point we definitely do not have the file available in memory
    // let's prepare for an asynchronous load of the image.

    // null it while it is downloading
    // since listviews reuse their views, we need to
    // take note of which url this view is waiting for.
    // This may change rapidly as the list scrolls or is filtered, etc.
    clog("Waiting for " + url + " " + imageView);
    if (imageView != null) {
      imageView.setImageDrawable(defaultDrawable);
      mPendingViews.put(imageView, url);
    }

    final ArrayList<ImageView> currentDownload = mPendingDownloads.get(url);
    if (currentDownload != null && currentDownload.size() != 0) {
      // Also, multiple vies may be waiting for this url.
      // So, let's maintain a list of these views.
      // When the url is downloaded, it sets the imagedrawable for
      // every view in the list. It needs to also validate that
      // the imageview is still waiting for this url.
      if (imageView != null) {
        currentDownload.add(imageView);
      }
      return;
    }

    final ArrayList<ImageView> downloads = new ArrayList<ImageView>();
    if (imageView != null) {
      downloads.add(imageView);
    }
    mPendingDownloads.put(url, downloads);

    final int targetWidth = tw <= 0 ? Integer.MAX_VALUE : tw;
    final int targetHeight = th <= 0 ? Integer.MAX_VALUE : th;
    final Loader loader =
        new Loader() {
          @Override
          public void onDownloadComplete(
              UrlDownloader downloader, InputStream in, String existingFilename) {
            try {
              assert (in == null || existingFilename == null);
              if (in == null && existingFilename == null) return;
              String targetFilename = filename;
              if (in != null) {
                in = new BufferedInputStream(in, 8192);
                OutputStream fout = new BufferedOutputStream(new FileOutputStream(filename), 8192);
                copyStream(in, fout);
                fout.close();
              } else {
                targetFilename = existingFilename;
              }
              result =
                  loadBitmapFromStream(context, url, targetFilename, targetWidth, targetHeight);
            } catch (final Exception ex) {
              // always delete busted files when we throw.
              new File(filename).delete();
              if (Constants.LOG_ENABLED) Log.e(Constants.LOGTAG, "Error loading " + url, ex);
            } finally {
              // if we're not supposed to cache this thing, delete the temp file.
              if (downloader != null && !downloader.allowCache()) new File(filename).delete();
            }
          }
        };

    final Runnable completion =
        new Runnable() {
          @Override
          public void run() {
            assert (Looper.myLooper().equals(Looper.getMainLooper()));
            Bitmap bitmap = loader.result;
            Drawable usableResult = null;
            if (bitmap != null) {
              usableResult = new ZombieDrawable(url, mResources, bitmap);
            }
            if (usableResult == null) {
              clog("No usable result, defaulting " + url);
              usableResult = defaultDrawable;
              mLiveCache.put(url, usableResult);
            }
            mPendingDownloads.remove(url);
            //                mLiveCache.put(url, usableResult);
            if (callback != null && imageView == null)
              callback.onLoaded(null, loader.result, url, false);
            int waitingCount = 0;
            for (final ImageView iv : downloads) {
              // validate the url it is waiting for
              final String pendingUrl = mPendingViews.get(iv);
              if (!url.equals(pendingUrl)) {
                clog(
                    "Ignoring out of date request to update view for "
                        + url
                        + " "
                        + pendingUrl
                        + " "
                        + iv);
                continue;
              }
              waitingCount++;
              mPendingViews.remove(iv);
              if (usableResult != null) {
                //                        System.out.println(String.format("imageView: %dx%d,
                // %dx%d", imageView.getMeasuredWidth(), imageView.getMeasuredHeight(),
                // imageView.getWidth(), imageView.getHeight()));
                iv.setImageDrawable(usableResult);
                //                        System.out.println(String.format("imageView: %dx%d,
                // %dx%d", imageView.getMeasuredWidth(), imageView.getMeasuredHeight(),
                // imageView.getWidth(), imageView.getHeight()));
                // onLoaded is called with the loader's result (not what is actually used). null
                // indicates failure.
              }
              if (callback != null && iv == imageView)
                callback.onLoaded(iv, loader.result, url, false);
            }
            clog("Populated: " + waitingCount);
          }
        };

    if (file.exists()) {
      try {
        if (checkCacheDuration(file, cacheDurationMs)) {
          clog(
              "File Cache hit on: "
                  + url
                  + ". "
                  + (System.currentTimeMillis() - file.lastModified())
                  + "ms old.");

          final AsyncTask<Void, Void, Void> fileloader =
              new AsyncTask<Void, Void, Void>() {
                @Override
                protected Void doInBackground(final Void... params) {
                  loader.onDownloadComplete(null, null, filename);
                  return null;
                }

                @Override
                protected void onPostExecute(final Void result) {
                  completion.run();
                }
              };
          executeTask(fileloader);
          return;
        } else {
          clog("File cache has expired. Refreshing.");
        }
      } catch (final Exception ex) {
      }
    }

    for (UrlDownloader downloader : mDownloaders) {
      if (downloader.canDownloadUrl(url)) {
        try {
          downloader.download(context, url, filename, loader, completion);
        } catch (Exception e) {
          clog("Can't download from url: " + url + " Exception: " + e.getMessage());
          mPendingDownloads.remove(url);
          if (imageView != null) {
            mPendingViews.remove(imageView);
          }
        }
        return;
      }
    }

    imageView.setImageDrawable(defaultDrawable);
  }