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