/* * @copyright 2012 Philip Warner * @license GNU General Public License * * This file is part of Book Catalogue. * * Book Catalogue 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. * * Book Catalogue 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 Book Catalogue. If not, see <http://www.gnu.org/licenses/>. */ package com.eleybourn.bookcatalogue; import java.io.File; import java.lang.ref.WeakReference; import android.graphics.Bitmap; import android.widget.ImageView; import com.eleybourn.bookcatalogue.booklist.BooklistPreferencesActivity; import com.eleybourn.bookcatalogue.utils.SimpleTaskQueue; import com.eleybourn.bookcatalogue.utils.SimpleTaskQueue.SimpleTask; import com.eleybourn.bookcatalogue.utils.SimpleTaskQueue.SimpleTaskContext; import com.eleybourn.bookcatalogue.utils.Utils; import com.eleybourn.bookcatalogue.utils.ViewTagger; /** * Task to get a thumbnail from the sdcard or cover database. It will resize it as required and * apply the resulting Bitmap to the related view. * * This object also has it's own statically defined SimpleTaskQueue for getting thumbnails in * background. * * @author Philip Warner */ public class GetThumbnailTask implements SimpleTask { // Queue for background thumbnail retrieval; allow 2 threads. More is nice, but with // many books to process it introduces what looks like lag when scrolling: 5 tasks // building now-invisible views is pointless. private static final SimpleTaskQueue mQueue = new SimpleTaskQueue("thumbnails", 1); /** * Create a task to convert, set and store the thumbnail for the passed book. * If cacheWasChecked = false, then the cache will be checked before any work is * done, and if found in the cache it will be used. This option is included to * reduce contention between background and foreground tasks: the forground (UI) * thread checks the chache only if there are no background cache-related tasks * currently running. */ public static void getThumbnail(String hash, ImageView view, int maxWidth, int maxHeight, boolean cacheWasChecked) { GetThumbnailTask t = new GetThumbnailTask( hash, view, maxWidth, maxHeight, cacheWasChecked); mQueue.enqueue(t); } /** * Allow other tasks (or subclasses tasks) to be queued. * * @param t Task to a[put in queue */ public static void enqueue(SimpleTask t) { mQueue.enqueue(t); } /** Reference to the view we are using */ WeakReference<ImageView> mView = null; /** ID of book whose cover we are getting */ private final String mBookHash; /** Resulting bitmap object */ Bitmap mBitmap = null; /** Flag indicating original caller had checked cache */ private final boolean mCacheWasChecked; /** Flag indicating image was found in the cache */ private boolean mWasInCache = false; /** The width of the thumbnail retrieved (based on preferences) */ private int mWidth; /** The height of the thumbnail retrieved (based on preferences) */ private int mHeight; /** Indicated we want the queue manager to call the finished() method. */ private boolean mWantFinished = true; public static boolean hasActiveTasks() { return mQueue.hasActiveTasks(); } /** * Utility routine to remove any record of a prior thumbnail task from a View object. * * Used internally and from Utils.fetchFileIntoImageView to ensure that nothing * overwrites the view. * * @param queue * @param v */ public static void clearOldTaskFromView(final ImageView v) { final GetThumbnailTask oldTask = (GetThumbnailTask)ViewTagger.getTag(v, R.id.TAG_GET_THUMBNAIL_TASK); if (oldTask != null) { ViewTagger.setTag(v, R.id.TAG_GET_THUMBNAIL_TASK, null); mQueue.remove(oldTask); } } /** * Constructor. Clean the view and save the details of what we want. * * @param queue * @param bookId * @param v * @param width * @param height */ public GetThumbnailTask( final String hash, final ImageView v, int maxWidth, int maxHeight, boolean cacheWasChecked ) { clearOldTaskFromView(v); mView = new WeakReference<ImageView>(v); mBookHash = hash; mCacheWasChecked = cacheWasChecked; mWidth = maxWidth; mHeight = maxHeight; // Clear current image v.setImageBitmap(null); // Associate the view with this task ViewTagger.setTag(v, R.id.TAG_GET_THUMBNAIL_TASK, this); } /** * Do the image manipulation. We wait at start to prevent a flood of images from hitting the UI thread. */ @Override public void run(SimpleTaskContext taskContext) { try { /* try { Thread.sleep(10); // Let the UI have a chance to do something if we are racking up images! } catch (InterruptedException e) { } */ // // fetchBookCoverIntoImageView is an expensive operation. Makre wure its still needed. // // Get the view we are targeting and make sure it is valid ImageView v = mView.get(); if (v == null) { mView.clear(); mWantFinished = false; return; } // Make sure the view is still associated with this task. We don't want to overwrite the wrong image // in a recycled view. if (!this.equals(ViewTagger.getTag(v, R.id.TAG_GET_THUMBNAIL_TASK))) { mWantFinished = false; return; } File originalFile = CatalogueDBAdapter.fetchThumbnailByUuid(mBookHash); if (!mCacheWasChecked) { final String cacheId = Utils.getCoverCacheId(mBookHash, mWidth, mHeight); mBitmap = taskContext.getUtils().fetchCachedImageIntoImageView(originalFile, null, cacheId); mWasInCache = (mBitmap != null); } if (mBitmap == null) mBitmap = taskContext.getUtils().fetchBookCoverIntoImageView(null, mWidth, mHeight, true, mBookHash, false, false); //} } finally { } taskContext.setRequiresFinish(mWantFinished); } /** * Handle the results of the task. */ @Override public void onFinish(Exception e) { if (!mWantFinished) return; // Get the view we are targetting and make sure it is valid ImageView v = mView.get(); // Make sure the view is still associated with this task. We dont want to overwrite the wrong image // in a recycled view. final boolean viewIsValid = (v != null && this.equals(ViewTagger.getTag(v, R.id.TAG_GET_THUMBNAIL_TASK))); // Clear the view tag if (viewIsValid) ViewTagger.setTag(v, R.id.TAG_GET_THUMBNAIL_TASK, null); if (mBitmap != null) { if (!mWasInCache && BooklistPreferencesActivity.isThumbnailCacheEnabled()) { // Queue the image to be written to the cache. Do it in a separate queue to avoid delays in displaying image // and to avoid contention -- the cache queue only has one thread. Tell the cache write it can be recycled // if we don't have a valid view. ThumbnailCacheWriterTask.writeToCache(Utils.getCoverCacheId(mBookHash, mWidth, mHeight), mBitmap, !viewIsValid); } if (viewIsValid) { //LayoutParams lp = new LayoutParams(mBitmap.getWidth(), mBitmap.getHeight()); //v.setLayoutParams(lp); v.setImageBitmap(mBitmap); } else { mBitmap.recycle(); mBitmap = null; } } else { v.setImageResource(android.R.drawable.ic_dialog_alert); } mView.clear(); //System.out.println("Set image for ID " + mBookId); } }