blob: 5f7143479eaa31f7991f899353ecd281fce65da9 [file] [log] [blame]
Owen Lina2fba682011-08-17 22:07:43 +08001/*
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.ui;
18
19import com.android.gallery3d.app.GalleryActivity;
20import com.android.gallery3d.common.BitmapUtils;
21import com.android.gallery3d.common.LruCache;
22import com.android.gallery3d.common.Utils;
23import com.android.gallery3d.data.MediaItem;
24import com.android.gallery3d.data.Path;
25import com.android.gallery3d.util.Future;
26import com.android.gallery3d.util.FutureListener;
Chih-Chung Chang70a73a72011-09-19 11:09:39 +080027import com.android.gallery3d.util.GalleryUtils;
Owen Linace280a2011-08-30 10:38:59 +080028import com.android.gallery3d.util.JobLimiter;
Owen Lina2fba682011-08-17 22:07:43 +080029import com.android.gallery3d.util.ThreadPool.Job;
30import com.android.gallery3d.util.ThreadPool.JobContext;
31
32import android.graphics.Bitmap;
33import android.graphics.Color;
34import android.os.Message;
35
36public class AlbumSlidingWindow implements AlbumView.ModelListener {
37 @SuppressWarnings("unused")
38 private static final String TAG = "AlbumSlidingWindow";
39
40 private static final int MSG_LOAD_BITMAP_DONE = 0;
41 private static final int MSG_UPDATE_SLOT = 1;
Owen Linace280a2011-08-30 10:38:59 +080042 private static final int JOB_LIMIT = 2;
Owen Lina2fba682011-08-17 22:07:43 +080043
44 public static interface Listener {
45 public void onSizeChanged(int size);
46 public void onContentInvalidated();
47 public void onWindowContentChanged(
48 int slot, DisplayItem old, DisplayItem update);
49 }
50
51 private final AlbumView.Model mSource;
52 private int mSize;
53
54 private int mContentStart = 0;
55 private int mContentEnd = 0;
56
57 private int mActiveStart = 0;
58 private int mActiveEnd = 0;
59
60 private Listener mListener;
61 private int mFocusIndex = -1;
62
63 private final AlbumDisplayItem mData[];
64 private final ColorTexture mWaitLoadingTexture;
65 private SelectionDrawer mSelectionDrawer;
66
67 private SynchronizedHandler mHandler;
Owen Linace280a2011-08-30 10:38:59 +080068 private JobLimiter mThreadPool;
Owen Lina2fba682011-08-17 22:07:43 +080069
70 private int mActiveRequestCount = 0;
71 private boolean mIsActive = false;
72
Chih-Chung Chang07069de2011-09-14 20:50:28 +080073 private int mCacheThumbSize; // 0: Don't cache the thumbnails
Owen Lina2fba682011-08-17 22:07:43 +080074 private LruCache<Path, Bitmap> mImageCache = new LruCache<Path, Bitmap>(1000);
75
76 public AlbumSlidingWindow(GalleryActivity activity,
77 AlbumView.Model source, int cacheSize,
Chih-Chung Chang07069de2011-09-14 20:50:28 +080078 int cacheThumbSize) {
Owen Lina2fba682011-08-17 22:07:43 +080079 source.setModelListener(this);
80 mSource = source;
81 mData = new AlbumDisplayItem[cacheSize];
82 mSize = source.size();
Owen Lina2fba682011-08-17 22:07:43 +080083
84 mWaitLoadingTexture = new ColorTexture(Color.TRANSPARENT);
85 mWaitLoadingTexture.setSize(1, 1);
86
87 mHandler = new SynchronizedHandler(activity.getGLRoot()) {
88 @Override
89 public void handleMessage(Message message) {
90 switch (message.what) {
91 case MSG_LOAD_BITMAP_DONE: {
92 ((AlbumDisplayItem) message.obj).onLoadBitmapDone();
93 break;
94 }
95 case MSG_UPDATE_SLOT: {
96 updateSlotContent(message.arg1);
97 break;
98 }
99 }
100 }
101 };
102
Owen Linace280a2011-08-30 10:38:59 +0800103 mThreadPool = new JobLimiter(activity.getThreadPool(), JOB_LIMIT);
Owen Lina2fba682011-08-17 22:07:43 +0800104 }
105
106 public void setSelectionDrawer(SelectionDrawer drawer) {
107 mSelectionDrawer = drawer;
108 }
109
110 public void setListener(Listener listener) {
111 mListener = listener;
112 }
113
114 public void setFocusIndex(int slotIndex) {
115 mFocusIndex = slotIndex;
116 }
117
118 public DisplayItem get(int slotIndex) {
119 Utils.assertTrue(isActiveSlot(slotIndex),
120 "invalid slot: %s outsides (%s, %s)",
121 slotIndex, mActiveStart, mActiveEnd);
122 return mData[slotIndex % mData.length];
123 }
124
125 public int size() {
126 return mSize;
127 }
128
129 public boolean isActiveSlot(int slotIndex) {
130 return slotIndex >= mActiveStart && slotIndex < mActiveEnd;
131 }
132
133 private void setContentWindow(int contentStart, int contentEnd) {
134 if (contentStart == mContentStart && contentEnd == mContentEnd) return;
135
136 if (!mIsActive) {
137 mContentStart = contentStart;
138 mContentEnd = contentEnd;
139 mSource.setActiveWindow(contentStart, contentEnd);
140 return;
141 }
142
143 if (contentStart >= mContentEnd || mContentStart >= contentEnd) {
144 for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
145 freeSlotContent(i);
146 }
147 mSource.setActiveWindow(contentStart, contentEnd);
148 for (int i = contentStart; i < contentEnd; ++i) {
149 prepareSlotContent(i);
150 }
151 } else {
152 for (int i = mContentStart; i < contentStart; ++i) {
153 freeSlotContent(i);
154 }
155 for (int i = contentEnd, n = mContentEnd; i < n; ++i) {
156 freeSlotContent(i);
157 }
158 mSource.setActiveWindow(contentStart, contentEnd);
159 for (int i = contentStart, n = mContentStart; i < n; ++i) {
160 prepareSlotContent(i);
161 }
162 for (int i = mContentEnd; i < contentEnd; ++i) {
163 prepareSlotContent(i);
164 }
165 }
166
167 mContentStart = contentStart;
168 mContentEnd = contentEnd;
169 }
170
171 public void setActiveWindow(int start, int end) {
172 Utils.assertTrue(start <= end
173 && end - start <= mData.length && end <= mSize,
174 "%s, %s, %s, %s", start, end, mData.length, mSize);
175 DisplayItem data[] = mData;
176
177 mActiveStart = start;
178 mActiveEnd = end;
179
180 // If no data is visible, keep the cache content
181 if (start == end) return;
182
183 int contentStart = Utils.clamp((start + end) / 2 - data.length / 2,
184 0, Math.max(0, mSize - data.length));
185 int contentEnd = Math.min(contentStart + data.length, mSize);
186 setContentWindow(contentStart, contentEnd);
187 if (mIsActive) updateAllImageRequests();
188 }
189
190 // We would like to request non active slots in the following order:
191 // Order: 8 6 4 2 1 3 5 7
192 // |---------|---------------|---------|
193 // |<- active ->|
194 // |<-------- cached range ----------->|
195 private void requestNonactiveImages() {
196 int range = Math.max(
197 (mContentEnd - mActiveEnd), (mActiveStart - mContentStart));
198 for (int i = 0 ;i < range; ++i) {
199 requestSlotImage(mActiveEnd + i, false);
200 requestSlotImage(mActiveStart - 1 - i, false);
201 }
202 }
203
204 private void requestSlotImage(int slotIndex, boolean isActive) {
205 if (slotIndex < mContentStart || slotIndex >= mContentEnd) return;
206 AlbumDisplayItem item = mData[slotIndex % mData.length];
207 item.requestImage();
208 }
209
210 private void cancelNonactiveImages() {
211 int range = Math.max(
212 (mContentEnd - mActiveEnd), (mActiveStart - mContentStart));
213 for (int i = 0 ;i < range; ++i) {
214 cancelSlotImage(mActiveEnd + i, false);
215 cancelSlotImage(mActiveStart - 1 - i, false);
216 }
217 }
218
219 private void cancelSlotImage(int slotIndex, boolean isActive) {
220 if (slotIndex < mContentStart || slotIndex >= mContentEnd) return;
221 AlbumDisplayItem item = mData[slotIndex % mData.length];
222 item.cancelImageRequest();
223 }
224
225 private void freeSlotContent(int slotIndex) {
226 AlbumDisplayItem data[] = mData;
227 int index = slotIndex % data.length;
228 AlbumDisplayItem original = data[index];
229 if (original != null) {
230 original.recycle();
231 data[index] = null;
232 }
233 }
234
235 private void prepareSlotContent(final int slotIndex) {
236 mData[slotIndex % mData.length] = new AlbumDisplayItem(
237 slotIndex, mSource.get(slotIndex));
238 }
239
240 private void updateSlotContent(final int slotIndex) {
241 MediaItem item = mSource.get(slotIndex);
242 AlbumDisplayItem data[] = mData;
243 int index = slotIndex % data.length;
244 AlbumDisplayItem original = data[index];
245 AlbumDisplayItem update = new AlbumDisplayItem(slotIndex, item);
246 data[index] = update;
247 boolean isActive = isActiveSlot(slotIndex);
248 if (mListener != null && isActive) {
249 mListener.onWindowContentChanged(slotIndex, original, update);
250 }
251 if (original != null) {
252 if (isActive && original.isRequestInProgress()) {
253 --mActiveRequestCount;
254 }
255 original.recycle();
256 }
257 if (isActive) {
258 if (mActiveRequestCount == 0) cancelNonactiveImages();
259 ++mActiveRequestCount;
260 update.requestImage();
261 } else {
262 if (mActiveRequestCount == 0) update.requestImage();
263 }
264 }
265
266 private void updateAllImageRequests() {
267 mActiveRequestCount = 0;
268 AlbumDisplayItem data[] = mData;
269 for (int i = mActiveStart, n = mActiveEnd; i < n; ++i) {
270 AlbumDisplayItem item = data[i % data.length];
271 item.requestImage();
272 if (item.isRequestInProgress()) ++mActiveRequestCount;
273 }
274 if (mActiveRequestCount == 0) {
275 requestNonactiveImages();
276 } else {
277 cancelNonactiveImages();
278 }
279 }
280
281 private class AlbumDisplayItem extends AbstractDisplayItem
282 implements FutureListener<Bitmap>, Job<Bitmap> {
283 private Future<Bitmap> mFuture;
284 private final int mSlotIndex;
285 private final int mMediaType;
286 private Texture mContent;
Chih-Chung Chang70a73a72011-09-19 11:09:39 +0800287 private boolean mIsPanorama;
Owen Lina2fba682011-08-17 22:07:43 +0800288
289 public AlbumDisplayItem(int slotIndex, MediaItem item) {
290 super(item);
291 mMediaType = (item == null)
292 ? MediaItem.MEDIA_TYPE_UNKNOWN
293 : item.getMediaType();
294 mSlotIndex = slotIndex;
Chih-Chung Chang70a73a72011-09-19 11:09:39 +0800295 mIsPanorama = GalleryUtils.isPanorama(item);
Owen Lina2fba682011-08-17 22:07:43 +0800296 updateContent(mWaitLoadingTexture);
297 }
298
299 @Override
300 protected void onBitmapAvailable(Bitmap bitmap) {
301 boolean isActiveSlot = isActiveSlot(mSlotIndex);
302 if (isActiveSlot) {
303 --mActiveRequestCount;
304 if (mActiveRequestCount == 0) requestNonactiveImages();
305 }
306 if (bitmap != null) {
Chih-Chung Change3312ff2011-09-22 14:55:32 +0800307 BitmapTexture texture = new BitmapTexture(bitmap, true);
Owen Lina2fba682011-08-17 22:07:43 +0800308 texture.setThrottled(true);
309 updateContent(texture);
310 if (mListener != null && isActiveSlot) {
311 mListener.onContentInvalidated();
312 }
313 }
314 }
315
316 private void updateContent(Texture content) {
317 mContent = content;
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800318 }
Owen Lina2fba682011-08-17 22:07:43 +0800319
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800320 @Override
321 public boolean render(GLCanvas canvas, int pass) {
322 // Fit the content into the box
Owen Lina2fba682011-08-17 22:07:43 +0800323 int width = mContent.getWidth();
324 int height = mContent.getHeight();
325
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800326 float scalex = mBoxWidth / (float) width;
327 float scaley = mBoxHeight / (float) height;
Owen Lina2fba682011-08-17 22:07:43 +0800328 float scale = Math.min(scalex, scaley);
329
330 width = (int) Math.floor(width * scale);
331 height = (int) Math.floor(height * scale);
332
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800333 // Now draw it
Owen Lina2fba682011-08-17 22:07:43 +0800334 if (pass == 0) {
335 Path path = null;
336 if (mMediaItem != null) path = mMediaItem.getPath();
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800337 mSelectionDrawer.draw(canvas, mContent, width, height,
Chih-Chung Chang70a73a72011-09-19 11:09:39 +0800338 getRotation(), path, mMediaType, mIsPanorama);
Owen Lina2fba682011-08-17 22:07:43 +0800339 return (mFocusIndex == mSlotIndex);
340 } else if (pass == 1) {
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800341 mSelectionDrawer.drawFocus(canvas, width, height);
Owen Lina2fba682011-08-17 22:07:43 +0800342 }
343 return false;
344 }
345
346 @Override
347 public void startLoadBitmap() {
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800348 if (mCacheThumbSize > 0) {
Owen Lina2fba682011-08-17 22:07:43 +0800349 Path path = mMediaItem.getPath();
350 if (mImageCache.containsKey(path)) {
351 Bitmap bitmap = mImageCache.get(path);
352 updateImage(bitmap, false);
353 return;
354 }
355 mFuture = mThreadPool.submit(this, this);
356 } else {
357 mFuture = mThreadPool.submit(mMediaItem.requestImage(
358 MediaItem.TYPE_MICROTHUMBNAIL), this);
359 }
360 }
361
362 // This gets the bitmap and scale it down.
363 public Bitmap run(JobContext jc) {
364 Job<Bitmap> job = mMediaItem.requestImage(
365 MediaItem.TYPE_MICROTHUMBNAIL);
366 Bitmap bitmap = job.run(jc);
367 if (bitmap != null) {
368 bitmap = BitmapUtils.resizeDownBySideLength(
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800369 bitmap, mCacheThumbSize, true);
Owen Lina2fba682011-08-17 22:07:43 +0800370 }
371 return bitmap;
372 }
373
374 @Override
375 public void cancelLoadBitmap() {
376 if (mFuture != null) {
377 mFuture.cancel();
378 }
379 }
380
381 @Override
382 public void onFutureDone(Future<Bitmap> bitmap) {
383 mHandler.sendMessage(mHandler.obtainMessage(MSG_LOAD_BITMAP_DONE, this));
384 }
385
386 private void onLoadBitmapDone() {
387 Future<Bitmap> future = mFuture;
388 mFuture = null;
389 Bitmap bitmap = future.get();
390 boolean isCancelled = future.isCancelled();
Chih-Chung Chang07069de2011-09-14 20:50:28 +0800391 if (mCacheThumbSize > 0 && (bitmap != null || !isCancelled)) {
Owen Lina2fba682011-08-17 22:07:43 +0800392 Path path = mMediaItem.getPath();
393 mImageCache.put(path, bitmap);
394 }
395 updateImage(bitmap, isCancelled);
396 }
397
398 @Override
399 public String toString() {
400 return String.format("AlbumDisplayItem[%s]", mSlotIndex);
401 }
402 }
403
404 public void onSizeChanged(int size) {
405 if (mSize != size) {
406 mSize = size;
407 if (mListener != null) mListener.onSizeChanged(mSize);
408 }
409 }
410
411 public void onWindowContentChanged(int index) {
412 if (index >= mContentStart && index < mContentEnd && mIsActive) {
413 updateSlotContent(index);
414 }
415 }
416
417 public void resume() {
418 mIsActive = true;
419 for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
420 prepareSlotContent(i);
421 }
422 updateAllImageRequests();
423 }
424
425 public void pause() {
426 mIsActive = false;
427 for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
428 freeSlotContent(i);
429 }
430 mImageCache.clear();
431 }
432}