1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.camera; 18 19 import com.android.camera.gallery.IImage; 20 import com.android.camera.gallery.IImageList; 21 import com.android.camera.gallery.VideoObject; 22 23 import android.content.ContentResolver; 24 import android.graphics.Bitmap; 25 import android.os.Handler; 26 import android.os.Message; 27 import android.os.Process; 28 import android.provider.MediaStore; 29 30 /* 31 * Here's the loading strategy. For any given image, load the thumbnail 32 * into memory and post a callback to display the resulting bitmap. 33 * 34 * Then proceed to load the full image bitmap. Three things can 35 * happen at this point: 36 * 37 * 1. the image fails to load because the UI thread decided 38 * to move on to a different image. This "cancellation" happens 39 * by virtue of the UI thread closing the stream containing the 40 * image being decoded. BitmapFactory.decodeStream returns null 41 * in this case. 42 * 43 * 2. the image loaded successfully. At that point we post 44 * a callback to the UI thread to actually show the bitmap. 45 * 46 * 3. when the post runs it checks to see if the image that was 47 * loaded is still the one we want. The UI may have moved on 48 * to some other image and if so we just drop the newly loaded 49 * bitmap on the floor. 50 */ 51 52 interface ImageGetterCallback { imageLoaded(int pos, int offset, RotateBitmap bitmap, boolean isThumb)53 public void imageLoaded(int pos, int offset, RotateBitmap bitmap, 54 boolean isThumb); wantsThumbnail(int pos, int offset)55 public boolean wantsThumbnail(int pos, int offset); wantsFullImage(int pos, int offset)56 public boolean wantsFullImage(int pos, int offset); fullImageSizeToUse(int pos, int offset)57 public int fullImageSizeToUse(int pos, int offset); completed()58 public void completed(); loadOrder()59 public int [] loadOrder(); 60 } 61 62 class ImageGetter { 63 64 @SuppressWarnings("unused") 65 private static final String TAG = "ImageGetter"; 66 67 // The thread which does the work. 68 private Thread mGetterThread; 69 70 // The current request serial number. 71 // This is increased by one each time a new job is assigned. 72 // It is only written in the main thread. 73 private int mCurrentSerial; 74 75 // The base position that's being retrieved. The actual images retrieved 76 // are this base plus each of the offets. -1 means there is no current 77 // request needs to be finished. 78 private int mCurrentPosition = -1; 79 80 // The callback to invoke for each image. 81 private ImageGetterCallback mCB; 82 83 // The image list for the images. 84 private IImageList mImageList; 85 86 // The handler to do callback. 87 private GetterHandler mHandler; 88 89 // True if we want to cancel the current loading. 90 private volatile boolean mCancel = true; 91 92 // True if the getter thread is idle waiting. 93 private boolean mIdle = false; 94 95 // True when the getter thread should exit. 96 private boolean mDone = false; 97 98 private ContentResolver mCr; 99 100 private class ImageGetterRunnable implements Runnable { 101 callback(final int position, final int offset, final boolean isThumb, final RotateBitmap bitmap, final int requestSerial)102 private Runnable callback(final int position, final int offset, 103 final boolean isThumb, 104 final RotateBitmap bitmap, 105 final int requestSerial) { 106 return new Runnable() { 107 public void run() { 108 // check for inflight callbacks that aren't applicable 109 // any longer before delivering them 110 if (requestSerial == mCurrentSerial) { 111 mCB.imageLoaded(position, offset, bitmap, isThumb); 112 } else if (bitmap != null) { 113 bitmap.recycle(); 114 } 115 } 116 }; 117 } 118 119 private Runnable completedCallback(final int requestSerial) { 120 return new Runnable() { 121 public void run() { 122 if (requestSerial == mCurrentSerial) { 123 mCB.completed(); 124 } 125 } 126 }; 127 } 128 129 public void run() { 130 // Lower the priority of this thread to avoid competing with 131 // the UI thread. 132 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 133 134 while (true) { 135 synchronized (ImageGetter.this) { 136 while (mCancel || mDone || mCurrentPosition == -1) { 137 if (mDone) return; 138 mIdle = true; 139 ImageGetter.this.notify(); 140 try { 141 ImageGetter.this.wait(); 142 } catch (InterruptedException ex) { 143 // ignore 144 } 145 mIdle = false; 146 } 147 } 148 149 executeRequest(); 150 151 synchronized (ImageGetter.this) { 152 mCurrentPosition = -1; 153 } 154 } 155 } 156 private void executeRequest() { 157 int imageCount = mImageList.getCount(); 158 159 int [] order = mCB.loadOrder(); 160 for (int i = 0; i < order.length; i++) { 161 if (mCancel) return; 162 int offset = order[i]; 163 int imageNumber = mCurrentPosition + offset; 164 if (imageNumber >= 0 && imageNumber < imageCount) { 165 if (!mCB.wantsThumbnail(mCurrentPosition, offset)) { 166 continue; 167 } 168 169 IImage image = mImageList.getImageAt(imageNumber); 170 if (image == null) continue; 171 if (mCancel) return; 172 173 Bitmap b = image.thumbBitmap(IImage.NO_ROTATE); 174 if (b == null) continue; 175 if (mCancel) { 176 b.recycle(); 177 return; 178 } 179 180 Runnable cb = callback(mCurrentPosition, offset, 181 true, 182 new RotateBitmap(b, image.getDegreesRotated()), 183 mCurrentSerial); 184 mHandler.postGetterCallback(cb); 185 } 186 } 187 188 for (int i = 0; i < order.length; i++) { 189 if (mCancel) return; 190 int offset = order[i]; 191 int imageNumber = mCurrentPosition + offset; 192 if (imageNumber >= 0 && imageNumber < imageCount) { 193 if (!mCB.wantsFullImage(mCurrentPosition, offset)) { 194 continue; 195 } 196 197 IImage image = mImageList.getImageAt(imageNumber); 198 if (image == null) continue; 199 if (image instanceof VideoObject) continue; 200 if (mCancel) return; 201 202 int sizeToUse = mCB.fullImageSizeToUse( 203 mCurrentPosition, offset); 204 Bitmap b = image.fullSizeBitmap(sizeToUse, 3 * 1024 * 1024, 205 IImage.NO_ROTATE, IImage.USE_NATIVE); 206 207 if (b == null) continue; 208 if (mCancel) { 209 b.recycle(); 210 return; 211 } 212 213 RotateBitmap rb = new RotateBitmap(b, 214 image.getDegreesRotated()); 215 216 Runnable cb = callback(mCurrentPosition, offset, 217 false, rb, mCurrentSerial); 218 mHandler.postGetterCallback(cb); 219 } 220 } 221 222 mHandler.postGetterCallback(completedCallback(mCurrentSerial)); 223 } 224 } 225 226 public ImageGetter(ContentResolver cr) { 227 mCr = cr; 228 mGetterThread = new Thread(new ImageGetterRunnable()); 229 mGetterThread.setName("ImageGettter"); 230 mGetterThread.start(); 231 } 232 233 // Cancels current loading (without waiting). 234 public synchronized void cancelCurrent() { 235 Util.Assert(mGetterThread != null); 236 mCancel = true; 237 BitmapManager.instance().cancelThreadDecoding(mGetterThread, mCr); 238 } 239 240 // Cancels current loading (with waiting). 241 private synchronized void cancelCurrentAndWait() { 242 cancelCurrent(); 243 while (mIdle != true) { 244 try { 245 wait(); 246 } catch (InterruptedException ex) { 247 // ignore. 248 } 249 } 250 } 251 252 // Stops this image getter. 253 public void stop() { 254 synchronized (this) { 255 cancelCurrentAndWait(); 256 mDone = true; 257 notify(); 258 } 259 try { 260 mGetterThread.join(); 261 } catch (InterruptedException ex) { 262 // Ignore the exception 263 } 264 mGetterThread = null; 265 } 266 267 public synchronized void setPosition(int position, ImageGetterCallback cb, 268 IImageList imageList, GetterHandler handler) { 269 // Cancel the previous request. 270 cancelCurrentAndWait(); 271 272 // Set new data. 273 mCurrentPosition = position; 274 mCB = cb; 275 mImageList = imageList; 276 mHandler = handler; 277 mCurrentSerial += 1; 278 279 // Kick-start the current request. 280 mCancel = false; 281 BitmapManager.instance().allowThreadDecoding(mGetterThread); 282 notify(); 283 } 284 } 285 286 class GetterHandler extends Handler { 287 private static final int IMAGE_GETTER_CALLBACK = 1; 288 289 @Override 290 public void handleMessage(Message message) { 291 switch(message.what) { 292 case IMAGE_GETTER_CALLBACK: 293 ((Runnable) message.obj).run(); 294 break; 295 } 296 } 297 298 public void postGetterCallback(Runnable callback) { 299 postDelayedGetterCallback(callback, 0); 300 } 301 302 public void postDelayedGetterCallback(Runnable callback, long delay) { 303 if (callback == null) { 304 throw new NullPointerException(); 305 } 306 Message message = Message.obtain(); 307 message.what = IMAGE_GETTER_CALLBACK; 308 message.obj = callback; 309 sendMessageDelayed(message, delay); 310 } 311 312 public void removeAllGetterCallbacks() { 313 removeMessages(IMAGE_GETTER_CALLBACK); 314 } 315 } 316