blob: 72228da0598231ca63e5421648c11c7a20c333c8 [file] [log] [blame]
Chih-Chung Chang4250e212009-07-24 10:58:40 +08001/*
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
17package com.android.camera;
18
19import com.android.camera.gallery.IImage;
20import com.android.camera.gallery.IImageList;
21import com.android.camera.gallery.VideoObject;
22
Ray Chenbde544f2009-09-30 14:33:15 -070023import android.content.ContentResolver;
Chih-Chung Chang4250e212009-07-24 10:58:40 +080024import android.graphics.Bitmap;
25import android.os.Handler;
26import android.os.Message;
Chih-Chung Chang3b456572009-07-30 19:14:04 +080027import android.os.Process;
Ray Chenbde544f2009-09-30 14:33:15 -070028import android.provider.MediaStore;
Chih-Chung Chang4250e212009-07-24 10:58:40 +080029
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
52interface ImageGetterCallback {
Ray Chen012d0f32009-07-20 16:33:41 +080053 public void imageLoaded(int pos, int offset, RotateBitmap bitmap,
Chih-Chung Chang4250e212009-07-24 10:58:40 +080054 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
62class 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
Ray Chenbde544f2009-09-30 14:33:15 -070098 private ContentResolver mCr;
99
Chih-Chung Chang4250e212009-07-24 10:58:40 +0800100 private class ImageGetterRunnable implements Runnable {
101
102 private Runnable callback(final int position, final int offset,
Ray Chen012d0f32009-07-20 16:33:41 +0800103 final boolean isThumb,
104 final RotateBitmap bitmap,
Chih-Chung Chang4250e212009-07-24 10:58:40 +0800105 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() {
Chih-Chung Chang3b456572009-07-30 19:14:04 +0800130 // Lower the priority of this thread to avoid competing with
131 // the UI thread.
132 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
133
Chih-Chung Chang4250e212009-07-24 10:58:40 +0800134 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
Ray Chen012d0f32009-07-20 16:33:41 +0800173 Bitmap b = image.thumbBitmap(IImage.NO_ROTATE);
Chih-Chung Chang4250e212009-07-24 10:58:40 +0800174 if (b == null) continue;
175 if (mCancel) {
176 b.recycle();
177 return;
178 }
179
180 Runnable cb = callback(mCurrentPosition, offset,
Ray Chen012d0f32009-07-20 16:33:41 +0800181 true,
182 new RotateBitmap(b, image.getDegreesRotated()),
183 mCurrentSerial);
Chih-Chung Chang4250e212009-07-24 10:58:40 +0800184 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);
Chih-Chung Changce033a52009-07-27 13:03:29 +0800204 Bitmap b = image.fullSizeBitmap(sizeToUse, 3 * 1024 * 1024,
Ray Chen012d0f32009-07-20 16:33:41 +0800205 IImage.NO_ROTATE, IImage.USE_NATIVE);
206
Chih-Chung Chang4250e212009-07-24 10:58:40 +0800207 if (b == null) continue;
208 if (mCancel) {
209 b.recycle();
210 return;
211 }
212
Ray Chen012d0f32009-07-20 16:33:41 +0800213 RotateBitmap rb = new RotateBitmap(b,
214 image.getDegreesRotated());
215
Chih-Chung Chang4250e212009-07-24 10:58:40 +0800216 Runnable cb = callback(mCurrentPosition, offset,
Ray Chen012d0f32009-07-20 16:33:41 +0800217 false, rb, mCurrentSerial);
Chih-Chung Chang4250e212009-07-24 10:58:40 +0800218 mHandler.postGetterCallback(cb);
219 }
220 }
221
222 mHandler.postGetterCallback(completedCallback(mCurrentSerial));
223 }
224 }
225
Ray Chenbde544f2009-09-30 14:33:15 -0700226 public ImageGetter(ContentResolver cr) {
227 mCr = cr;
Chih-Chung Chang4250e212009-07-24 10:58:40 +0800228 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;
Ray Chen28f35952009-10-05 14:34:24 -0700237 BitmapManager.instance().cancelThreadDecoding(mGetterThread, mCr);
Chih-Chung Chang4250e212009-07-24 10:58:40 +0800238 }
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
286class 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}