/* * Copyright (C) 2007 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.android.camera; import com.android.camera.gallery.IImage; import com.android.camera.gallery.IImageList; import android.graphics.Bitmap; import android.os.Handler; import android.util.Log; import java.io.IOException; import java.util.ArrayList; import static com.android.camera.Util.Assert; /** * A dedicated decoding thread used by ImageGallery. */ public class ImageLoader { @SuppressWarnings("unused") private static final String TAG = "ImageLoader"; // Queue of work to do in the worker thread. The work is done in order. private final ArrayList mQueue = new ArrayList(); // the worker thread and a done flag so we know when to exit private boolean mDone; private Thread mDecodeThread; // Thumbnail checking will be done when there is no getBitmap requests // need to be processed. private ThumbnailChecker mThumbnailChecker; /** * Notify interface of how many thumbnails are processed. */ public interface ThumbCheckCallback { public boolean checking(int current, int count); public void done(); } public interface LoadedCallback { public void run(Bitmap result); } public void getBitmap(IImage image, LoadedCallback imageLoadedRunnable, int tag) { if (mDecodeThread == null) { start(); } synchronized (mQueue) { WorkItem w = new WorkItem(image, imageLoadedRunnable, tag); mQueue.add(w); mQueue.notifyAll(); } } public boolean cancel(final IImage image) { synchronized (mQueue) { int index = findItem(image); if (index >= 0) { mQueue.remove(index); return true; } else { return false; } } } // The caller should hold mQueue lock. private int findItem(IImage image) { for (int i = 0; i < mQueue.size(); i++) { if (mQueue.get(i).mImage == image) { return i; } } return -1; } // Clear the queue. Returns an array of tags that were in the queue. public int[] clearQueue() { synchronized (mQueue) { int n = mQueue.size(); int[] tags = new int[n]; for (int i = 0; i < n; i++) { tags[i] = mQueue.get(i).mTag; } mQueue.clear(); return tags; } } private static class WorkItem { IImage mImage; LoadedCallback mOnLoadedRunnable; int mTag; WorkItem(IImage image, LoadedCallback onLoadedRunnable, int tag) { mImage = image; mOnLoadedRunnable = onLoadedRunnable; mTag = tag; } } public ImageLoader(Handler handler) { mThumbnailChecker = new ThumbnailChecker(); start(); } private class WorkerThread implements Runnable { // Pick off items on the queue, one by one, and compute their bitmap. // Place the resulting bitmap in the cache, then call back by executing // the given runnable so things can get updated appropriately. public void run() { while (true) { WorkItem workItem = null; synchronized (mQueue) { if (mDone) { break; } if (!mQueue.isEmpty()) { workItem = mQueue.remove(0); } else { if (!mThumbnailChecker.hasMoreThumbnailsToCheck()) { try { mQueue.wait(); } catch (InterruptedException ex) { // ignore the exception } continue; } } } // This holds if and only if the above // hasMoreThumbnailsToCheck() returns true. (We put the call // here because we want to release the lock on mQueue. if (workItem == null) { mThumbnailChecker.checkNextThumbnail(); continue; } final Bitmap b = workItem.mImage.miniThumbBitmap(); if (workItem.mOnLoadedRunnable != null) { workItem.mOnLoadedRunnable.run(b); } } } } private void start() { if (mDecodeThread != null) { return; } mDone = false; Thread t = new Thread(new WorkerThread()); t.setName("image-loader"); mDecodeThread = t; t.start(); } public void stop() { synchronized (mQueue) { mDone = true; mQueue.notifyAll(); } if (mDecodeThread != null) { try { Thread t = mDecodeThread; BitmapManager.instance().cancelThreadDecoding(t); t.join(); mDecodeThread = null; } catch (InterruptedException ex) { // so now what? } } stopCheckingThumbnails(); } // Passthrough to ThumbnailChecker. public void startCheckingThumbnails(IImageList imageList, ThumbCheckCallback cb) { mThumbnailChecker.startCheckingThumbnails(imageList, cb); // Kick WorkerThread to start working. synchronized (mQueue) { mQueue.notifyAll(); } } public void stopCheckingThumbnails() { mThumbnailChecker.stopCheckingThumbnails(); } } // This is part of ImageLoader which is responsible for checking thumbnails. // // The methods of ThumbnailChecker need to be synchronized because the data // will also be accessed by the WorkerThread. The methods of ThumbnailChecker // is only called by ImageLoader. class ThumbnailChecker { private static final String TAG = "ThumbnailChecker"; private IImageList mImageListToCheck; // The image list we will check. private int mTotalToCheck; // total number of thumbnails to check. private int mNextToCheck; // next thumbnail to check, // -1 if no further checking is needed. private ImageLoader.ThumbCheckCallback mThumbCheckCallback; ThumbnailChecker() { mNextToCheck = -1; } // Both imageList and cb must be non-null. synchronized void startCheckingThumbnails(IImageList imageList, ImageLoader.ThumbCheckCallback cb) { Assert(imageList != null); Assert(cb != null); mImageListToCheck = imageList; mTotalToCheck = imageList.getCount(); mNextToCheck = 0; mThumbCheckCallback = cb; if (!ImageManager.hasStorage()) { Log.v(TAG, "bailing from the image checker -- no storage"); stopCheckingThumbnails(); } } synchronized void stopCheckingThumbnails() { if (mThumbCheckCallback == null) return; // already stopped. mThumbCheckCallback.done(); mImageListToCheck = null; mTotalToCheck = 0; mNextToCheck = -1; mThumbCheckCallback = null; } synchronized boolean hasMoreThumbnailsToCheck() { return mNextToCheck != -1; } synchronized void checkNextThumbnail() { if (mNextToCheck == -1) { return; } if (mNextToCheck >= mTotalToCheck) { stopCheckingThumbnails(); return; } try { mImageListToCheck.checkThumbnail(mNextToCheck); } catch (IOException ex) { Log.e(TAG, "Failed to check thumbnail..." + " was the sd card removed? - " + ex.getMessage()); stopCheckingThumbnails(); } if (!mThumbCheckCallback.checking(mNextToCheck, mTotalToCheck)) { stopCheckingThumbnails(); } mNextToCheck++; } }