diff options
Diffstat (limited to 'src/com/android/camera/ImageGetter.java')
-rw-r--r-- | src/com/android/camera/ImageGetter.java | 299 |
1 files changed, 299 insertions, 0 deletions
diff --git a/src/com/android/camera/ImageGetter.java b/src/com/android/camera/ImageGetter.java new file mode 100644 index 0000000..60095da --- /dev/null +++ b/src/com/android/camera/ImageGetter.java @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2009 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 com.android.camera.gallery.VideoObject; + +import android.graphics.Bitmap; +import android.os.Handler; +import android.os.Message; +import android.util.Log; + +/* + * Here's the loading strategy. For any given image, load the thumbnail + * into memory and post a callback to display the resulting bitmap. + * + * Then proceed to load the full image bitmap. Three things can + * happen at this point: + * + * 1. the image fails to load because the UI thread decided + * to move on to a different image. This "cancellation" happens + * by virtue of the UI thread closing the stream containing the + * image being decoded. BitmapFactory.decodeStream returns null + * in this case. + * + * 2. the image loaded successfully. At that point we post + * a callback to the UI thread to actually show the bitmap. + * + * 3. when the post runs it checks to see if the image that was + * loaded is still the one we want. The UI may have moved on + * to some other image and if so we just drop the newly loaded + * bitmap on the floor. + */ + +interface ImageGetterCallback { + public void imageLoaded(int pos, int offset, Bitmap bitmap, + boolean isThumb); + public boolean wantsThumbnail(int pos, int offset); + public boolean wantsFullImage(int pos, int offset); + public int fullImageSizeToUse(int pos, int offset); + public void completed(); + public int [] loadOrder(); +} + +class ImageGetter { + + @SuppressWarnings("unused") + private static final String TAG = "ImageGetter"; + + // The thread which does the work. + private Thread mGetterThread; + + // The current request serial number. + // This is increased by one each time a new job is assigned. + // It is only written in the main thread. + private int mCurrentSerial; + + // The base position that's being retrieved. The actual images retrieved + // are this base plus each of the offets. -1 means there is no current + // request needs to be finished. + private int mCurrentPosition = -1; + + // The callback to invoke for each image. + private ImageGetterCallback mCB; + + // The image list for the images. + private IImageList mImageList; + + // The handler to do callback. + private GetterHandler mHandler; + + // True if we want to cancel the current loading. + private volatile boolean mCancel = true; + + // True if the getter thread is idle waiting. + private boolean mIdle = false; + + // True when the getter thread should exit. + private boolean mDone = false; + + private class ImageGetterRunnable implements Runnable { + + private Runnable callback(final int position, final int offset, + final boolean isThumb, final Bitmap bitmap, + final int requestSerial) { + return new Runnable() { + public void run() { + // check for inflight callbacks that aren't applicable + // any longer before delivering them + if (requestSerial == mCurrentSerial) { + mCB.imageLoaded(position, offset, bitmap, isThumb); + } else if (bitmap != null) { + bitmap.recycle(); + } + } + }; + } + + private Runnable completedCallback(final int requestSerial) { + return new Runnable() { + public void run() { + if (requestSerial == mCurrentSerial) { + mCB.completed(); + } + } + }; + } + + public void run() { + while (true) { + synchronized (ImageGetter.this) { + while (mCancel || mDone || mCurrentPosition == -1) { + if (mDone) return; + mIdle = true; + ImageGetter.this.notify(); + try { + ImageGetter.this.wait(); + } catch (InterruptedException ex) { + // ignore + } + mIdle = false; + } + } + + executeRequest(); + + synchronized (ImageGetter.this) { + mCurrentPosition = -1; + } + } + } + private void executeRequest() { + int imageCount = mImageList.getCount(); + + int [] order = mCB.loadOrder(); + for (int i = 0; i < order.length; i++) { + if (mCancel) return; + int offset = order[i]; + int imageNumber = mCurrentPosition + offset; + if (imageNumber >= 0 && imageNumber < imageCount) { + if (!mCB.wantsThumbnail(mCurrentPosition, offset)) { + continue; + } + + IImage image = mImageList.getImageAt(imageNumber); + if (image == null) continue; + if (mCancel) return; + + Bitmap b = image.thumbBitmap(); + if (b == null) continue; + if (mCancel) { + b.recycle(); + return; + } + + Runnable cb = callback(mCurrentPosition, offset, + true, b, mCurrentSerial); + mHandler.postGetterCallback(cb); + } + } + + for (int i = 0; i < order.length; i++) { + if (mCancel) return; + int offset = order[i]; + int imageNumber = mCurrentPosition + offset; + if (imageNumber >= 0 && imageNumber < imageCount) { + if (!mCB.wantsFullImage(mCurrentPosition, offset)) { + continue; + } + + IImage image = mImageList.getImageAt(imageNumber); + if (image == null) continue; + if (image instanceof VideoObject) continue; + if (mCancel) return; + + int sizeToUse = mCB.fullImageSizeToUse( + mCurrentPosition, offset); + Bitmap b = image.fullSizeBitmap(sizeToUse, + IImage.ROTATE_AS_NEEDED, IImage.USE_NATIVE); + if (b == null) continue; + if (mCancel) { + b.recycle(); + return; + } + + Runnable cb = callback(mCurrentPosition, offset, + false, b, mCurrentSerial); + mHandler.postGetterCallback(cb); + } + } + + mHandler.postGetterCallback(completedCallback(mCurrentSerial)); + } + } + + public ImageGetter() { + mGetterThread = new Thread(new ImageGetterRunnable()); + mGetterThread.setName("ImageGettter"); + mGetterThread.start(); + } + + // Cancels current loading (without waiting). + public synchronized void cancelCurrent() { + Util.Assert(mGetterThread != null); + mCancel = true; + BitmapManager.instance().cancelThreadDecoding(mGetterThread); + } + + // Cancels current loading (with waiting). + private synchronized void cancelCurrentAndWait() { + cancelCurrent(); + while (mIdle != true) { + try { + wait(); + } catch (InterruptedException ex) { + // ignore. + } + } + } + + // Stops this image getter. + public void stop() { + synchronized (this) { + cancelCurrentAndWait(); + mDone = true; + notify(); + } + try { + mGetterThread.join(); + } catch (InterruptedException ex) { + // Ignore the exception + } + mGetterThread = null; + } + + public synchronized void setPosition(int position, ImageGetterCallback cb, + IImageList imageList, GetterHandler handler) { + // Cancel the previous request. + cancelCurrentAndWait(); + + // Set new data. + mCurrentPosition = position; + mCB = cb; + mImageList = imageList; + mHandler = handler; + mCurrentSerial += 1; + + // Kick-start the current request. + mCancel = false; + BitmapManager.instance().allowThreadDecoding(mGetterThread); + notify(); + } +} + +class GetterHandler extends Handler { + private static final int IMAGE_GETTER_CALLBACK = 1; + + @Override + public void handleMessage(Message message) { + switch(message.what) { + case IMAGE_GETTER_CALLBACK: + ((Runnable) message.obj).run(); + break; + } + } + + public void postGetterCallback(Runnable callback) { + postDelayedGetterCallback(callback, 0); + } + + public void postDelayedGetterCallback(Runnable callback, long delay) { + if (callback == null) { + throw new NullPointerException(); + } + Message message = Message.obtain(); + message.what = IMAGE_GETTER_CALLBACK; + message.obj = callback; + sendMessageDelayed(message, delay); + } + + public void removeAllGetterCallbacks() { + removeMessages(IMAGE_GETTER_CALLBACK); + } +} |