/** * Copyright (C) 2011 Whisper Systems * * 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.securecomcode.text.mms; import android.content.Context; import android.graphics.Color; import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.TransitionDrawable; import android.net.Uri; import android.os.Handler; import android.util.Log; import android.widget.ImageView; import com.securecomcode.text.R; import com.securecomcode.text.database.MmsDatabase; import com.securecomcode.text.util.BitmapDecodingException; import com.securecomcode.text.util.BitmapUtil; import com.securecomcode.text.util.LRUCache; import org.whispersystems.textsecure.crypto.MasterSecret; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.lang.ref.SoftReference; import java.lang.ref.WeakReference; import java.util.Collections; import java.util.Map; import ws.com.google.android.mms.ContentType; import ws.com.google.android.mms.pdu.PduPart; public class ImageSlide extends Slide { private static final int MAX_CACHE_SIZE = 10; private static final Map<Uri, SoftReference<Drawable>> thumbnailCache = Collections.synchronizedMap(new LRUCache<Uri, SoftReference<Drawable>>(MAX_CACHE_SIZE)); public ImageSlide(Context context, MasterSecret masterSecret, PduPart part) { super(context, masterSecret, part); } public ImageSlide(Context context, Uri uri) throws IOException, BitmapDecodingException { super(context, constructPartFromUri(context, uri)); } @Override public Drawable getThumbnail(int maxWidth, int maxHeight) { Drawable thumbnail = getCachedThumbnail(); if (thumbnail != null) { return thumbnail; } if (part.isPendingPush()) { return context.getResources().getDrawable(R.drawable.stat_sys_download); } try { InputStream measureStream = getPartDataInputStream(); InputStream dataStream = getPartDataInputStream(); thumbnail = new BitmapDrawable(context.getResources(), BitmapUtil.createScaledBitmap(measureStream, dataStream, maxWidth, maxHeight)); thumbnailCache.put(part.getDataUri(), new SoftReference<Drawable>(thumbnail)); return thumbnail; } catch (FileNotFoundException e) { Log.w("ImageSlide", e); return context.getResources().getDrawable(R.drawable.ic_missing_thumbnail_picture); } catch (BitmapDecodingException e) { Log.w("ImageSlide", e); return context.getResources().getDrawable(R.drawable.ic_missing_thumbnail_picture); } } @Override public void setThumbnailOn(ImageView imageView) { Drawable thumbnail = getCachedThumbnail(); if (thumbnail != null) { Log.w("ImageSlide", "Setting cached thumbnail..."); setThumbnailOn(imageView, thumbnail, true); return; } final ColorDrawable temporaryDrawable = new ColorDrawable(Color.TRANSPARENT); final WeakReference<ImageView> weakImageView = new WeakReference<ImageView>(imageView); final Handler handler = new Handler(); final int maxWidth = imageView.getWidth(); final int maxHeight = imageView.getHeight(); imageView.setImageDrawable(temporaryDrawable); if (maxWidth == 0 || maxHeight == 0) return; MmsDatabase.slideResolver.execute(new Runnable() { @Override public void run() { final Drawable bitmap = getThumbnail(maxWidth, maxHeight); final ImageView destination = weakImageView.get(); if (destination != null && destination.getDrawable() == temporaryDrawable) { handler.post(new Runnable() { @Override public void run() { setThumbnailOn(destination, bitmap, false); } }); } } }); } private void setThumbnailOn(ImageView imageView, Drawable thumbnail, boolean fromMemory) { if (fromMemory) { imageView.setImageDrawable(thumbnail); } else if (thumbnail instanceof AnimationDrawable) { imageView.setImageDrawable(thumbnail); ((AnimationDrawable)imageView.getDrawable()).start(); } else { TransitionDrawable fadingResult = new TransitionDrawable(new Drawable[]{new ColorDrawable(Color.TRANSPARENT), thumbnail}); imageView.setImageDrawable(fadingResult); fadingResult.startTransition(300); } } private Drawable getCachedThumbnail() { synchronized (thumbnailCache) { SoftReference<Drawable> bitmapReference = thumbnailCache.get(part.getDataUri()); Log.w("ImageSlide", "Got soft reference: " + bitmapReference); if (bitmapReference != null) { Drawable bitmap = bitmapReference.get(); Log.w("ImageSlide", "Got cached bitmap: " + bitmap); if (bitmap != null) return bitmap; else thumbnailCache.remove(part.getDataUri()); } } return null; } @Override public boolean hasImage() { return true; } private static PduPart constructPartFromUri(Context context, Uri uri) throws IOException, BitmapDecodingException { PduPart part = new PduPart(); byte[] data = BitmapUtil.createScaledBytes(context, uri, 640, 480, (300 * 1024) - 5000); part.setData(data); part.setDataUri(uri); part.setContentType(ContentType.IMAGE_JPEG.getBytes()); part.setContentId((System.currentTimeMillis()+"").getBytes()); part.setName(("Image" + System.currentTimeMillis()).getBytes()); return part; } }