1 /*
2  * Copyright (C) 2010 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.gallery3d.app;
18 
19 import android.graphics.Bitmap;
20 import android.graphics.BitmapFactory;
21 import android.graphics.BitmapRegionDecoder;
22 import android.graphics.Rect;
23 import android.os.Handler;
24 import android.os.Message;
25 
26 import com.android.gallery3d.common.BitmapUtils;
27 import com.android.gallery3d.common.Utils;
28 import com.android.gallery3d.data.MediaItem;
29 import com.android.gallery3d.data.Path;
30 import com.android.gallery3d.ui.BitmapScreenNail;
31 import com.android.gallery3d.ui.PhotoView;
32 import com.android.gallery3d.ui.ScreenNail;
33 import com.android.gallery3d.ui.SynchronizedHandler;
34 import com.android.gallery3d.ui.TileImageViewAdapter;
35 import com.android.gallery3d.util.Future;
36 import com.android.gallery3d.util.FutureListener;
37 import com.android.gallery3d.util.ThreadPool;
38 
39 public class SinglePhotoDataAdapter extends TileImageViewAdapter
40         implements PhotoPage.Model {
41 
42     private static final String TAG = "SinglePhotoDataAdapter";
43     private static final int SIZE_BACKUP = 1024;
44     private static final int MSG_UPDATE_IMAGE = 1;
45 
46     private MediaItem mItem;
47     private boolean mHasFullImage;
48     private Future<?> mTask;
49     private Handler mHandler;
50 
51     private PhotoView mPhotoView;
52     private ThreadPool mThreadPool;
53     private int mLoadingState = LOADING_INIT;
54     private BitmapScreenNail mBitmapScreenNail;
55 
SinglePhotoDataAdapter( AbstractGalleryActivity activity, PhotoView view, MediaItem item)56     public SinglePhotoDataAdapter(
57             AbstractGalleryActivity activity, PhotoView view, MediaItem item) {
58         mItem = Utils.checkNotNull(item);
59         mHasFullImage = (item.getSupportedOperations() &
60                 MediaItem.SUPPORT_FULL_IMAGE) != 0;
61         mPhotoView = Utils.checkNotNull(view);
62         mHandler = new SynchronizedHandler(activity.getGLRoot()) {
63             @Override
64             @SuppressWarnings("unchecked")
65             public void handleMessage(Message message) {
66                 Utils.assertTrue(message.what == MSG_UPDATE_IMAGE);
67                 if (mHasFullImage) {
68                     onDecodeLargeComplete((ImageBundle) message.obj);
69                 } else {
70                     onDecodeThumbComplete((Future<Bitmap>) message.obj);
71                 }
72             }
73         };
74         mThreadPool = activity.getThreadPool();
75     }
76 
77     private static class ImageBundle {
78         public final BitmapRegionDecoder decoder;
79         public final Bitmap backupImage;
80 
ImageBundle(BitmapRegionDecoder decoder, Bitmap backupImage)81         public ImageBundle(BitmapRegionDecoder decoder, Bitmap backupImage) {
82             this.decoder = decoder;
83             this.backupImage = backupImage;
84         }
85     }
86 
87     private FutureListener<BitmapRegionDecoder> mLargeListener =
88             new FutureListener<BitmapRegionDecoder>() {
89         @Override
90         public void onFutureDone(Future<BitmapRegionDecoder> future) {
91             BitmapRegionDecoder decoder = future.get();
92             if (decoder == null) return;
93             int width = decoder.getWidth();
94             int height = decoder.getHeight();
95             BitmapFactory.Options options = new BitmapFactory.Options();
96             options.inSampleSize = BitmapUtils.computeSampleSize(
97                     (float) SIZE_BACKUP / Math.max(width, height));
98             Bitmap bitmap = decoder.decodeRegion(new Rect(0, 0, width, height), options);
99             mHandler.sendMessage(mHandler.obtainMessage(
100                     MSG_UPDATE_IMAGE, new ImageBundle(decoder, bitmap)));
101         }
102     };
103 
104     private FutureListener<Bitmap> mThumbListener =
105             new FutureListener<Bitmap>() {
106         @Override
107         public void onFutureDone(Future<Bitmap> future) {
108             mHandler.sendMessage(
109                     mHandler.obtainMessage(MSG_UPDATE_IMAGE, future));
110         }
111     };
112 
113     @Override
isEmpty()114     public boolean isEmpty() {
115         return false;
116     }
117 
setScreenNail(Bitmap bitmap, int width, int height)118     private void setScreenNail(Bitmap bitmap, int width, int height) {
119         mBitmapScreenNail = new BitmapScreenNail(bitmap);
120         setScreenNail(mBitmapScreenNail, width, height);
121     }
122 
onDecodeLargeComplete(ImageBundle bundle)123     private void onDecodeLargeComplete(ImageBundle bundle) {
124         try {
125             setScreenNail(bundle.backupImage,
126                     bundle.decoder.getWidth(), bundle.decoder.getHeight());
127             setRegionDecoder(bundle.decoder);
128             mPhotoView.notifyImageChange(0);
129         } catch (Throwable t) {
130             Log.w(TAG, "fail to decode large", t);
131         }
132     }
133 
onDecodeThumbComplete(Future<Bitmap> future)134     private void onDecodeThumbComplete(Future<Bitmap> future) {
135         try {
136             Bitmap backup = future.get();
137             if (backup == null) {
138                 mLoadingState = LOADING_FAIL;
139                 return;
140             } else {
141                 mLoadingState = LOADING_COMPLETE;
142             }
143             setScreenNail(backup, backup.getWidth(), backup.getHeight());
144             mPhotoView.notifyImageChange(0);
145         } catch (Throwable t) {
146             Log.w(TAG, "fail to decode thumb", t);
147         }
148     }
149 
150     @Override
resume()151     public void resume() {
152         if (mTask == null) {
153             if (mHasFullImage) {
154                 mTask = mThreadPool.submit(
155                         mItem.requestLargeImage(), mLargeListener);
156             } else {
157                 mTask = mThreadPool.submit(
158                         mItem.requestImage(MediaItem.TYPE_THUMBNAIL),
159                         mThumbListener);
160             }
161         }
162     }
163 
164     @Override
pause()165     public void pause() {
166         Future<?> task = mTask;
167         task.cancel();
168         task.waitDone();
169         if (task.get() == null) {
170             mTask = null;
171         }
172         if (mBitmapScreenNail != null) {
173             mBitmapScreenNail.recycle();
174             mBitmapScreenNail = null;
175         }
176     }
177 
178     @Override
moveTo(int index)179     public void moveTo(int index) {
180         throw new UnsupportedOperationException();
181     }
182 
183     @Override
getImageSize(int offset, PhotoView.Size size)184     public void getImageSize(int offset, PhotoView.Size size) {
185         if (offset == 0) {
186             size.width = mItem.getWidth();
187             size.height = mItem.getHeight();
188         } else {
189             size.width = 0;
190             size.height = 0;
191         }
192     }
193 
194     @Override
getImageRotation(int offset)195     public int getImageRotation(int offset) {
196         return (offset == 0) ? mItem.getFullImageRotation() : 0;
197     }
198 
199     @Override
getScreenNail(int offset)200     public ScreenNail getScreenNail(int offset) {
201         return (offset == 0) ? getScreenNail() : null;
202     }
203 
204     @Override
setNeedFullImage(boolean enabled)205     public void setNeedFullImage(boolean enabled) {
206         // currently not necessary.
207     }
208 
209     @Override
isCamera(int offset)210     public boolean isCamera(int offset) {
211         return false;
212     }
213 
214     @Override
isPanorama(int offset)215     public boolean isPanorama(int offset) {
216         return false;
217     }
218 
219     @Override
isStaticCamera(int offset)220     public boolean isStaticCamera(int offset) {
221         return false;
222     }
223 
224     @Override
isVideo(int offset)225     public boolean isVideo(int offset) {
226         return mItem.getMediaType() == MediaItem.MEDIA_TYPE_VIDEO;
227     }
228 
229     @Override
isDeletable(int offset)230     public boolean isDeletable(int offset) {
231         return (mItem.getSupportedOperations() & MediaItem.SUPPORT_DELETE) != 0;
232     }
233 
234     @Override
getMediaItem(int offset)235     public MediaItem getMediaItem(int offset) {
236         return offset == 0 ? mItem : null;
237     }
238 
239     @Override
getCurrentIndex()240     public int getCurrentIndex() {
241         return 0;
242     }
243 
244     @Override
setCurrentPhoto(Path path, int indexHint)245     public void setCurrentPhoto(Path path, int indexHint) {
246         // ignore
247     }
248 
249     @Override
setFocusHintDirection(int direction)250     public void setFocusHintDirection(int direction) {
251         // ignore
252     }
253 
254     @Override
setFocusHintPath(Path path)255     public void setFocusHintPath(Path path) {
256         // ignore
257     }
258 
259     @Override
getLoadingState(int offset)260     public int getLoadingState(int offset) {
261         return mLoadingState;
262     }
263 }
264