com.frostwire.android.gui.views.ImageLoader.java Source code

Java tutorial

Introduction

Here is the source code for com.frostwire.android.gui.views.ImageLoader.java

Source

/*
 * Created by Angel Leon (@gubatron), Alden Torres (aldenml)
 * Copyright (c) 2011, 2012, FrostWire(TM). All rights reserved.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.frostwire.android.gui.views;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;

import org.apache.commons.io.IOUtils;

import android.content.ContentResolver;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.provider.MediaStore.Images;
import android.provider.MediaStore.Video;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;

import com.frostwire.android.R;
import com.frostwire.android.core.Constants;
import com.frostwire.android.core.FileDescriptor;
import com.frostwire.android.core.providers.UniversalStore.Applications;
import com.frostwire.android.gui.util.DiskLruRawDataCache;
import com.frostwire.android.gui.util.FileUtils;
import com.frostwire.android.gui.util.MusicUtils;
import com.frostwire.android.gui.util.SystemUtils;
import com.squareup.picasso.Downloader;
import com.squareup.picasso.LruCache;
import com.squareup.picasso.Picasso;
import com.squareup.picasso.Picasso.Builder;
import com.squareup.picasso.RequestCreator;
import com.squareup.picasso.Transformation;
import com.squareup.picasso.UrlConnectionDownloader;

/**
 * @author gubatron
 * @author aldenml
 * 
 */
public final class ImageLoader {

    private static final int MEMORY_CACHE_SIZE = 1024 * 1024 * 2; // 2MB
    private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB

    public static final int OVERLAY_FLAG_PLAY = 1;
    public static final int DOWNSCALE_HUGE_BITMAPS = OVERLAY_FLAG_PLAY << 1;

    private final DiskLruRawDataCache diskCache;

    private final Context context;

    private static ImageLoader defaultInstance;

    private final Picasso picasso;

    public static ImageLoader getDefault() {
        return defaultInstance;
    }

    public static void createDefaultInstance(Context context) {
        if (defaultInstance == null) {
            defaultInstance = new ImageLoader(context);
        }
    }

    public ImageLoader(Context context) {
        this.context = context;
        diskCache = diskCacheOpen();
        picasso = new Builder(context).downloader(new ThumbnailLoader())
                .memoryCache(new LruCache(MEMORY_CACHE_SIZE)).build();
        picasso.setDebugging(false);
    }

    private DiskLruRawDataCache diskCacheOpen() {
        DiskLruRawDataCache cache = null;
        File imgCacheDir = SystemUtils.getImageCacheDirectory();
        cache = (FileUtils.isValidDirectory(imgCacheDir)) ? new DiskLruRawDataCache(imgCacheDir, DISK_CACHE_SIZE)
                : null;
        return cache;
    }

    public void displayImage(FileDescriptor image, ImageView imageView, Drawable defaultDrawable) {
        displayImage(image, imageView, defaultDrawable, 0);
    }

    public void displayImage(FileDescriptor fd, ImageView imageView, Drawable defaultDrawable, int overlayFlags) {
        StringBuilder path = getResourceIdentifier(fd);
        displayImage(path.toString(), imageView, defaultDrawable, overlayFlags);
    }

    /**
     * @param fd
     * @return Depending on the file type returns either video:<id> or image:<id>
     */
    private StringBuilder getResourceIdentifier(FileDescriptor fd) {
        StringBuilder path = new StringBuilder();

        switch (fd.fileType) {
        case Constants.FILE_TYPE_PICTURES:
            path.append("image:");
            break;
        case Constants.FILE_TYPE_VIDEOS:
            path.append("video:");
            break;
        case Constants.FILE_TYPE_AUDIO:
            path.append("audio:");
            break;
        case Constants.FILE_TYPE_APPLICATIONS:
            path.append("application:");
            break;
        default:
            path.append("image:");
            break;
        }
        path.append(fd.id);
        return path;
    }

    public void displayImage(String imageSrc, ImageView imageView, Drawable defaultDrawable, int overlayFlags) {
        imageView.setScaleType(ScaleType.FIT_CENTER);

        RequestCreator requestBuilder = picasso.load(imageSrc).placeholder(defaultDrawable);

        if ((overlayFlags & OVERLAY_FLAG_PLAY) == OVERLAY_FLAG_PLAY) {
            requestBuilder.transform(
                    new OverlayBitmapTransformation(imageView, imageSrc, R.drawable.play_icon_transparent, 40, 40));
        } else if ((overlayFlags & DOWNSCALE_HUGE_BITMAPS) == DOWNSCALE_HUGE_BITMAPS) {
            // hardcoded to 1/2 for now
            requestBuilder.transform(new DownscaleTransformation(imageSrc, 0.5f));
        }

        requestBuilder.into(imageView);
    }

    private boolean isKeyRemote(String key) {
        return key.startsWith("http://");
    }

    private Bitmap getBitmap(Context context, byte fileType, long id) {
        Bitmap bmp = null;

        try {
            ContentResolver cr = context.getContentResolver();

            if (fileType == Constants.FILE_TYPE_PICTURES) {
                bmp = Images.Thumbnails.getThumbnail(cr, id, Images.Thumbnails.MICRO_KIND, null);
            } else if (fileType == Constants.FILE_TYPE_VIDEOS) {
                bmp = Video.Thumbnails.getThumbnail(cr, id, Video.Thumbnails.MICRO_KIND, null);
            } else if (fileType == Constants.FILE_TYPE_AUDIO) {
                bmp = MusicUtils.getArtwork(context, id, -1, 2);
            } else if (fileType == Constants.FILE_TYPE_APPLICATIONS) {
                InputStream is = cr.openInputStream(
                        Uri.withAppendedPath(Applications.Media.CONTENT_URI_ITEM, String.valueOf(id)));
                bmp = BitmapFactory.decodeStream(is);
                is.close();
            }
        } catch (Throwable e) {
            bmp = null;
            // ignore
        }

        return bmp;
    }

    private class RawDataResponse extends Downloader.Response {

        private final byte[] data;

        public RawDataResponse(byte[] data, boolean loadedFromCache) {
            super(new ByteArrayInputStream(data), loadedFromCache);
            this.data = data;
        }

        public byte[] getData() {
            return data;
        }
    }

    /**
     * We'll use the Raw data response given by this loader to store bytes
     * on a PRIVATE disk cache that nobody else will come and erase or share with us,
     * like the default HttpResponseCache that Picasso uses (which doesn't work for
     * older androids, and which I believe sucks balls)
     * @author gubatron
     *
     */
    private class RawDataUrlConnectionLoader extends UrlConnectionDownloader {

        public RawDataUrlConnectionLoader(Context context) {
            super(context);
        }

        @Override
        public RawDataResponse load(Uri uri, boolean localCacheOnly) throws IOException {
            HttpURLConnection connection = openConnection(uri);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            IOUtils.copy(connection.getInputStream(), baos);
            connection.disconnect();
            return new RawDataResponse(baos.toByteArray(), localCacheOnly);
        }
    }

    private class ThumbnailLoader implements Downloader {

        private final RawDataUrlConnectionLoader fallback;

        public ThumbnailLoader() {
            fallback = new RawDataUrlConnectionLoader(context);
        }

        /**
         * @param itemIdentifier video:<videoId>, or image:<imageId>, audio:<audioId>, where the Id is an Integer.
         */
        @Override
        public Response load(Uri uri, boolean localCacheOnly) throws IOException {
            String itemIdentifier = uri.toString();
            Response response = null;
            try {
                byte fileType = getFileType(itemIdentifier);

                if (fileType != -1) {
                    response = fromFileType(itemIdentifier, localCacheOnly, fileType);
                } else if (isKeyRemote(itemIdentifier)) {
                    response = fromRemote(itemIdentifier, localCacheOnly);
                } else {
                    response = fallback.load(uri, localCacheOnly);
                }
            } catch (Throwable t) {
                throw new IOException("load caught a non-IOException", t);
            }
            return response;
        }

        private Response fromRemote(String itemIdentifier, boolean localCacheOnly) throws IOException {
            RawDataResponse response = null;
            if (itemIdentifier != null) {
                if (diskCache != null) {
                    if (!diskCache.containsKey(itemIdentifier)) {
                        response = fallback.load(Uri.parse(itemIdentifier), localCacheOnly);
                        diskCache.put(itemIdentifier, response.getData());
                    } else {
                        byte[] data = diskCache.getBytes(itemIdentifier);
                        response = new RawDataResponse(data, localCacheOnly);
                    }
                } else {
                    response = fallback.load(Uri.parse(itemIdentifier), false);
                }
            }
            return response;
        }

        private Response fromFileType(String itemIdentifier, boolean localCacheOnly, byte fileType)
                throws IOException {
            Response response;
            long id = getFileId(itemIdentifier);
            Bitmap bitmap = null;

            try {
                bitmap = getBitmap(context, fileType, id);
                response = new Response(bitmap, localCacheOnly);
            } catch (NullPointerException npe) {
                throw new IOException("ThumbnailLoader - bitmap not found.");
            } catch (Throwable e) {
                throw new IOException("ThumbnailLoader - bitmap might be too big.");
            }

            return response;
        }

        private byte getFileType(String itemIdentifier) {
            byte fileType = -1;

            if (itemIdentifier.startsWith("image:")) {
                fileType = Constants.FILE_TYPE_PICTURES;
            } else if (itemIdentifier.startsWith("video:")) {
                fileType = Constants.FILE_TYPE_VIDEOS;
            } else if (itemIdentifier.startsWith("audio:")) {
                fileType = Constants.FILE_TYPE_AUDIO;
            } else if (itemIdentifier.startsWith("application:")) {
                fileType = Constants.FILE_TYPE_APPLICATIONS;
            }
            return fileType;
        }

        private long getFileId(String itemIdentifier) {
            return Long.valueOf(itemIdentifier.substring(itemIdentifier.indexOf(':') + 1));
        }
    }

    private class OverlayBitmapTransformation implements Transformation {
        private final ImageView imageView;
        private final String keyPrefix;
        private final int overlayIconId;
        private final int overlayWidthPercentage;
        private final int overlayHeightPercentage;

        public OverlayBitmapTransformation(ImageView imageView, String keyPrefix, int overlayIconId,
                int overlayWidthPercentage, int overlayHeightPercentage) {
            this.imageView = imageView;
            this.keyPrefix = keyPrefix;
            this.overlayIconId = overlayIconId;
            this.overlayWidthPercentage = overlayWidthPercentage;
            this.overlayHeightPercentage = overlayHeightPercentage;
        }

        @Override
        public Bitmap transform(Bitmap source) {
            Bitmap bmp = overlayIcon(source, overlayIconId, overlayWidthPercentage, overlayHeightPercentage);
            source.recycle();
            return bmp;
        }

        @Override
        public String key() {
            return keyPrefix + ":" + overlayIconId + ":" + imageView.getWidth() + "," + imageView.getHeight() + ":"
                    + overlayWidthPercentage + "," + overlayHeightPercentage;
        }

        private Bitmap overlayIcon(Bitmap backgroundBmp, int iconResId, int iconWidthPercentage,
                int iconHeightPercentage) {
            Bitmap result = backgroundBmp;
            if (imageView.getWidth() > 0 && imageView.getHeight() > 0) {

                Bitmap canvasBitmap = Bitmap.createBitmap(imageView.getWidth(), imageView.getHeight(),
                        backgroundBmp.getConfig());
                Canvas canvas = resizeBackgroundToFitImageView(canvasBitmap, backgroundBmp);
                paintScaledIcon(canvas, iconResId, iconWidthPercentage, iconHeightPercentage);

                backgroundBmp.recycle();
                result = canvasBitmap;
            }
            return result;
        }

        private void paintScaledIcon(Canvas canvas, int iconResId, int iconWidthPercentage,
                int iconHeightPercentage) {
            Bitmap icon = BitmapFactory.decodeResource(context.getResources(), iconResId);
            Rect iconSrcRect = new Rect(0, 0, icon.getWidth(), icon.getHeight());
            int iconResizedWidth = (int) ((imageView.getWidth() * iconWidthPercentage) / 100f);
            int iconResizedHeight = (int) ((imageView.getHeight() * iconHeightPercentage) / 100f);
            int left = (imageView.getWidth() - iconResizedWidth) >> 1;
            int top = (imageView.getHeight() - iconResizedHeight) >> 1;
            Rect iconDestRect = new Rect(left, top, left + iconResizedWidth, top + iconResizedHeight);
            canvas.drawBitmap(icon, iconSrcRect, iconDestRect, null);
            icon.recycle();
        }

        private Canvas resizeBackgroundToFitImageView(Bitmap canvasBitmap, Bitmap backgroundBmp) {
            Rect backgroundDestRect = new Rect(0, 0, imageView.getWidth(), imageView.getHeight());
            Canvas canvas = new Canvas(canvasBitmap);
            canvas.drawBitmap(backgroundBmp, null, backgroundDestRect, null);
            return canvas;
        }
    }

    private class DownscaleTransformation implements Transformation {

        private final float factor;
        private final String key;

        public DownscaleTransformation(String key, float factor) {
            this.factor = factor;
            this.key = key + ":" + factor;
        }

        @Override
        public Bitmap transform(Bitmap source) {
            int width = (int) (source.getWidth() * factor);
            int height = (int) (source.getHeight() * factor);
            Bitmap bmp = Bitmap.createScaledBitmap(source, width, height, false);
            source.recycle();
            return bmp;
        }

        @Override
        public String key() {
            return key;
        }
    }
}