Home | History | Annotate | Download | only in camera
      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