com.rabross.android.minecraftskinwidget.ImageDownloader.java Source code

Java tutorial

Introduction

Here is the source code for com.rabross.android.minecraftskinwidget.ImageDownloader.java

Source

/*
 * Copyright (C) 2010 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.rabross.android.minecraftskinwidget;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.net.http.AndroidHttpClient;
import android.os.AsyncTask;
import android.util.Log;

/**
 * This helper class download images from the Internet and binds those with the
 * provided ImageView.
 * 
 * <p>
 * It requires the INTERNET permission, which should be added to your
 * application's manifest file.
 * </p>
 * 
 * A local cache of downloaded images is maintained internally to improve
 * performance.
 * 
 * Additions by Robert Ross rab@rabross.com
 */
public class ImageDownloader {

    private static final String TAG = ImageDownloader.class.getSimpleName();

    enum DownloadStatus {
        SUCCESS, FAIL, NON_PREMIUM, NETWORK_ERROR
    }

    private ImageLoadingListener mLoadingListener;
    private BitmapDownloaderTask mBitmapDownloaderTask;

    private Context mContext;

    public ImageDownloader(Context aContext, ImageLoadingListener aLoadingListener) {
        mContext = aContext;
        mLoadingListener = aLoadingListener;
    }

    /**
     * Download the specified image from the Internet and binds it to the
     * provided ImageView. The binding is immediate if the image is found in the
     * cache and will be done asynchronously otherwise. A null bitmap will be
     * associated to the ImageView if an error occurs.
     * 
     * @param name
     *            The URL of the image to download.
     * @param imageView
     *            The ImageView to bind the downloaded image to.
     */
    public void download(String name, String url) {

        Bitmap bitmap = getBitmapFromDiskCache(name, url);

        if (bitmap == null) {
            forceDownload(name, url);
            return;
        }

        mLoadingListener.handleLoadingFinish(bitmap);
    }

    /**
     * Same as download but the image is always downloaded and the cache is not
     * used. Kept private at the moment as its interest is not clear.
     */
    public void forceDownload(String name, String url) {

        mBitmapDownloaderTask = new BitmapDownloaderTask();
        mBitmapDownloaderTask.execute(name, url);
    }

    Bitmap downloadBitmap(String name, String aUrl) {

        String url = aUrl + name;

        // AndroidHttpClient is not allowed to be used from the main thread
        final HttpClient client = AndroidHttpClient.newInstance("Android");
        final HttpGet getRequest = new HttpGet(url);

        try {
            HttpResponse response = client.execute(getRequest);
            final int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode != HttpStatus.SC_OK) {
                Log.w("ImageDownloader", "Error " + statusCode + " while retrieving bitmap from " + url);
                return null;
            }

            final HttpEntity entity = response.getEntity();
            if (entity != null) {
                InputStream inputStream = null;
                try {
                    inputStream = entity.getContent();
                    return BitmapFactory.decodeStream(new FlushedInputStream(inputStream));
                } finally {
                    if (inputStream != null) {
                        inputStream.close();
                    }
                    entity.consumeContent();
                }
            }
        } catch (IOException e) {
            getRequest.abort();
            Log.w(TAG, "I/O error while retrieving bitmap", e);
        } catch (IllegalStateException e) {
            getRequest.abort();
            Log.w(TAG, "Incorrect URL");
        } catch (Exception e) {
            getRequest.abort();
            Log.w(TAG, "Error while retrieving bitmap", e);
        } finally {
            if ((client instanceof AndroidHttpClient)) {
                ((AndroidHttpClient) client).close();
            }
        }
        return null;
    }

    /*
     * An InputStream that skips the exact number of bytes provided, unless it
     * reaches EOF.
     */
    static class FlushedInputStream extends FilterInputStream {
        public FlushedInputStream(InputStream inputStream) {
            super(inputStream);
        }

        @Override
        public long skip(long n) throws IOException {
            long totalBytesSkipped = 0L;
            while (totalBytesSkipped < n) {
                long bytesSkipped = in.skip(n - totalBytesSkipped);
                if (bytesSkipped == 0L) {
                    int b = read();
                    if (b < 0) {
                        break; // we reached EOF
                    } else {
                        bytesSkipped = 1; // we read one byte
                    }
                }
                totalBytesSkipped += bytesSkipped;
            }
            return totalBytesSkipped;
        }
    }

    /**
     * The actual AsyncTask that will asynchronously download the image.
     */
    class BitmapDownloaderTask extends AsyncTask<String, Void, DownloadStatus> {
        private String name;
        private String url;
        private Bitmap mBitmap;

        @Override
        protected void onCancelled() {
            mLoadingListener.handleLoadingCancel();
            super.onCancelled();
        }

        @Override
        protected void onPreExecute() {
            mLoadingListener.handleLoading();
            super.onPreExecute();
        }

        /**
         * Actual download method.
         */
        @Override
        protected DownloadStatus doInBackground(String... params) {
            name = params[0];
            url = params[1];

            if (!Utilities.isConnectingToInternet(mContext)) {
                return DownloadStatus.NETWORK_ERROR;
            }

            if (new PremiumChecker().checkPremium(name)) {
                if ((mBitmap = downloadBitmap(name, url)) != null)
                    return DownloadStatus.SUCCESS;
            } else {
                return DownloadStatus.NON_PREMIUM;
            }

            return DownloadStatus.FAIL;
        }

        /**
         * Once the image is downloaded, associates it to the imageView
         */
        @Override
        protected void onPostExecute(DownloadStatus status) {
            if (isCancelled()) {
                return;
            }

            switch (status) {
            case FAIL:
                mLoadingListener.handleLoadingError();
                break;
            case NON_PREMIUM:
                mLoadingListener.handlePremiumError();
                break;
            case SUCCESS:
                putBitmapInDiskCache(name, url, mBitmap);
                mLoadingListener.handleLoadingFinish(mBitmap);
                break;
            case NETWORK_ERROR:
                mLoadingListener.handleNetworkError();
                break;

            default:
                break;
            }
        }
    }

    /**
     * Write bitmap associated with a url to disk cache
     */
    private void putBitmapInDiskCache(String name, String url, Bitmap bitmap) {
        try {
            File cacheFile = new File(mContext.getCacheDir(), String.valueOf(getCacheName(name, url, true)));
            FileOutputStream fos = new FileOutputStream(cacheFile);
            bitmap.compress(CompressFormat.PNG, 100, fos);
            fos.flush();
            fos.close();
            cacheFile.length();
        } catch (Exception e) {
            Log.e(TAG, "Error when saving image to cache. ", e);
        }
    }

    /**
     * Read bitmap associated with a url from disk cache
     */
    public Bitmap getBitmapFromDiskCache(String name, String url) {
        FileInputStream fis = null;
        Bitmap bm = null;
        File cacheFile = null;
        try {

            // Legacy file name
            cacheFile = new File(mContext.getCacheDir(), String.valueOf((url + name).hashCode()));

            if (!cacheFile.exists()) {

                //new file name
                cacheFile = new File(mContext.getCacheDir(), String.valueOf(getCacheName(name, url, true)));

                if (!cacheFile.exists()) {
                    return null;
                }
            }

            fis = new FileInputStream(cacheFile);
            bm = BitmapFactory.decodeStream(fis);
            fis.close();
        } catch (Exception e) {
            Log.e(TAG, "Error when reading image from cache. ", e);
            return null;
        }
        return bm;
    }

    private String getCacheName(String name, String url, boolean hasHat) {
        if (hasHat) {
            return url.hashCode() + "" + name;
        } else {
            return url.hashCode() + "" + name + hasHat;
        }
    }

    public void cancel() {
        mBitmapDownloaderTask.cancel(true);
    }
}