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
17package com.android.gallery3d.app;
18
19import android.graphics.Bitmap;
20import android.graphics.BitmapFactory;
21import android.graphics.BitmapRegionDecoder;
22import android.graphics.Rect;
23import android.os.Handler;
24import android.os.Message;
25
26import com.android.gallery3d.common.BitmapUtils;
27import com.android.gallery3d.common.Utils;
28import com.android.gallery3d.data.MediaItem;
29import com.android.gallery3d.data.Path;
30import com.android.gallery3d.ui.BitmapScreenNail;
31import com.android.gallery3d.ui.PhotoView;
32import com.android.gallery3d.ui.ScreenNail;
33import com.android.gallery3d.ui.SynchronizedHandler;
34import com.android.gallery3d.ui.TileImageViewAdapter;
35import com.android.gallery3d.util.Future;
36import com.android.gallery3d.util.FutureListener;
37import com.android.gallery3d.util.ThreadPool;
38
39public 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
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
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
114    public boolean isEmpty() {
115        return false;
116    }
117
118    private void setScreenNail(Bitmap bitmap, int width, int height) {
119        mBitmapScreenNail = new BitmapScreenNail(bitmap);
120        setScreenNail(mBitmapScreenNail, width, height);
121    }
122
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
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
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
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
179    public void moveTo(int index) {
180        throw new UnsupportedOperationException();
181    }
182
183    @Override
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
195    public int getImageRotation(int offset) {
196        return (offset == 0) ? mItem.getFullImageRotation() : 0;
197    }
198
199    @Override
200    public ScreenNail getScreenNail(int offset) {
201        return (offset == 0) ? getScreenNail() : null;
202    }
203
204    @Override
205    public void setNeedFullImage(boolean enabled) {
206        // currently not necessary.
207    }
208
209    @Override
210    public boolean isCamera(int offset) {
211        return false;
212    }
213
214    @Override
215    public boolean isPanorama(int offset) {
216        return false;
217    }
218
219    @Override
220    public boolean isStaticCamera(int offset) {
221        return false;
222    }
223
224    @Override
225    public boolean isVideo(int offset) {
226        return mItem.getMediaType() == MediaItem.MEDIA_TYPE_VIDEO;
227    }
228
229    @Override
230    public boolean isDeletable(int offset) {
231        return (mItem.getSupportedOperations() & MediaItem.SUPPORT_DELETE) != 0;
232    }
233
234    @Override
235    public MediaItem getMediaItem(int offset) {
236        return offset == 0 ? mItem : null;
237    }
238
239    @Override
240    public int getCurrentIndex() {
241        return 0;
242    }
243
244    @Override
245    public void setCurrentPhoto(Path path, int indexHint) {
246        // ignore
247    }
248
249    @Override
250    public void setFocusHintDirection(int direction) {
251        // ignore
252    }
253
254    @Override
255    public void setFocusHintPath(Path path) {
256        // ignore
257    }
258
259    @Override
260    public int getLoadingState(int offset) {
261        return mLoadingState;
262    }
263}
264