@Override
  public void download(
      final Context context,
      final String url,
      final String filename,
      final UrlDownloaderCallback callback,
      final Runnable completion) {
    final AsyncTask<Void, Void, Void> downloader =
        new AsyncTask<Void, Void, Void>() {
          @Override
          protected Void doInBackground(final Void... params) {
            try {
              final ContentResolver cr = context.getContentResolver();
              InputStream is = cr.openInputStream(Uri.parse(url));
              callback.onDownloadComplete(ContentUrlDownloader.this, is, null);
              return null;
            } catch (final Throwable e) {
              e.printStackTrace();
              return null;
            }
          }

          @Override
          protected void onPostExecute(final Void result) {
            completion.run();
          }
        };

    UrlImageViewHelper.executeTask(downloader);
  }
        @Override
        public void download(
            final Context context,
            final String url,
            final String filename,
            final Runnable loader,
            final Runnable completion) {
          AsyncTask<Void, Void, Void> downloader =
              new AsyncTask<Void, Void, Void>() {
                @Override
                protected Void doInBackground(Void... params) {
                  InputStream is = null;
                  FileOutputStream fos = null;
                  AndroidHttpClient client = null;
                  try {
                    if (url.startsWith(ContactsContract.Contacts.CONTENT_URI.toString())) {
                      ContentResolver cr = context.getContentResolver();
                      is =
                          ContactsContract.Contacts.openContactPhotoInputStream(cr, Uri.parse(url));
                    } else {
                      client = AndroidHttpClient.newInstance(context.getPackageName());
                      HttpGet get = new HttpGet(url);
                      HttpParams httpParams = new BasicHttpParams();
                      HttpClientParams.setRedirecting(httpParams, true);

                      if (mRequestPropertiesCallback != null) {
                        ArrayList<NameValuePair> props =
                            mRequestPropertiesCallback.getHeadersForRequest(context, url);
                        if (props != null) {
                          for (NameValuePair pair : props) {
                            httpParams.setParameter(pair.getName(), pair.getValue());
                          }
                        }
                      }

                      get.setParams(httpParams);
                      HttpResponse resp = client.execute(get);
                      int status = resp.getStatusLine().getStatusCode();

                      if (status != HttpURLConnection.HTTP_OK) {
                        return null;
                      }
                      HttpEntity entity = resp.getEntity();
                      is = entity.getContent();
                    }

                    if (is != null) {
                      fos = new FileOutputStream(filename);
                      copyStream(is, fos);
                    }
                    loader.run();
                    return null;
                  } catch (Throwable e) {
                    Log.e(getClass().getSimpleName(), "Error thrown while getting drawable", e);
                    return null;
                  } finally {
                    if (client != null) {
                      client.close();
                    }
                    if (is != null) {
                      try {
                        is.close();
                      } catch (IOException e) {
                        Log.e(getClass().getSimpleName(), "Failed to close input stream", e);
                      }
                    }
                    if (fos != null) {
                      try {
                        fos.close();
                      } catch (IOException e) {
                        Log.e(getClass().getSimpleName(), "Failed to close file output stream");
                      }
                    }
                  }
                }

                protected void onPostExecute(Void result) {
                  completion.run();
                }
              };
          executeTask(downloader);
        }
  private static void setUrlDrawable(
      final Context context,
      ImageView imageView,
      final String url,
      final Drawable defaultDrawable,
      long cacheDurationMs,
      final UrlImageViewCallback callback) {
    cleanup(context);
    // disassociate this ImageView from any pending downloads
    if (isNullOrEmpty(url)) {
      if (imageView != null) imageView.setImageDrawable(defaultDrawable);
      return;
    }

    WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    Display display = wm.getDefaultDisplay();
    int tw = display.getWidth();
    int th = display.getHeight();

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

    if (drawable != null) {
      if (Constants.LOG_ENABLED) Log.i(Constants.LOGTAG, "Cache hit on: " + url);
      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.

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

    // 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.
    if (Constants.LOG_ENABLED) Log.i(Constants.LOGTAG, "Waiting for " + url);
    if (imageView != null) mPendingViews.put(imageView, url);

    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 (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);
            for (ImageView iv : downloads) {
              // validate the url it is waiting for
              String pendingUrl = mPendingViews.get(iv);
              if (!url.equals(pendingUrl)) {
                if (Constants.LOG_ENABLED)
                  Log.i(Constants.LOGTAG, "Ignoring out of date request to update view for " + url);
                continue;
              }
              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) callback.onLoaded(iv, loader.result, url, false);
              }
            }
          }
        };

    File file = new File(filename);
    if (file.exists()) {
      try {
        if (cacheDurationMs == CACHE_DURATION_INFINITE
            || System.currentTimeMillis() < file.lastModified() + cacheDurationMs) {
          if (Constants.LOG_ENABLED)
            Log.i(
                Constants.LOGTAG,
                "File Cache hit on: "
                    + url
                    + ". "
                    + (System.currentTimeMillis() - file.lastModified())
                    + "ms old.");

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

                protected void onPostExecute(Void result) {
                  completion.run();
                }
              };
          executeTask(fileloader);
          return;
        } else {
          if (Constants.LOG_ENABLED) Log.i(Constants.LOGTAG, "File cache has expired. Refreshing.");
        }
      } catch (Exception ex) {
      }
    }

    mDownloader.download(context, url, filename, loader, completion);
  }