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 { 53 public void imageLoaded(int pos, int offset, RotateBitmap bitmap, 54 boolean isThumb); 55 public boolean wantsThumbnail(int pos, int offset); 56 public boolean wantsFullImage(int pos, int offset); 57 public int fullImageSizeToUse(int pos, int offset); 58 public void completed(); 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 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