• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*T
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.ui;
18 
19 import android.graphics.Bitmap;
20 import android.os.Message;
21 
22 import com.android.gallery3d.R;
23 import com.android.gallery3d.app.AlbumSetDataLoader;
24 import com.android.gallery3d.app.GalleryActivity;
25 import com.android.gallery3d.common.Utils;
26 import com.android.gallery3d.data.DataSourceType;
27 import com.android.gallery3d.data.MediaItem;
28 import com.android.gallery3d.data.MediaObject;
29 import com.android.gallery3d.data.MediaSet;
30 import com.android.gallery3d.data.Path;
31 import com.android.gallery3d.util.Future;
32 import com.android.gallery3d.util.FutureListener;
33 import com.android.gallery3d.util.GalleryUtils;
34 import com.android.gallery3d.util.ThreadPool;
35 
36 public class AlbumSetSlidingWindow implements AlbumSetDataLoader.DataListener {
37     private static final String TAG = "AlbumSetSlidingWindow";
38     private static final int MSG_UPDATE_ALBUM_ENTRY = 1;
39 
40     public static interface Listener {
onSizeChanged(int size)41         public void onSizeChanged(int size);
onContentChanged()42         public void onContentChanged();
43     }
44 
45     private final AlbumSetDataLoader mSource;
46     private int mSize;
47 
48     private int mContentStart = 0;
49     private int mContentEnd = 0;
50 
51     private int mActiveStart = 0;
52     private int mActiveEnd = 0;
53 
54     private Listener mListener;
55 
56     private final AlbumSetEntry mData[];
57     private final SynchronizedHandler mHandler;
58     private final ThreadPool mThreadPool;
59     private final AlbumLabelMaker mLabelMaker;
60     private final String mLoadingText;
61     private final TextureUploader mTextureUploader;
62 
63     private int mActiveRequestCount = 0;
64     private boolean mIsActive = false;
65     private BitmapTexture mLoadingLabel;
66 
67     private int mSlotWidth;
68 
69     public static class AlbumSetEntry {
70         public MediaSet album;
71         public MediaItem coverItem;
72         public Texture content;
73         public BitmapTexture labelTexture;
74         public BitmapTexture bitmapTexture;
75         public Path setPath;
76         public String title;
77         public int totalCount;
78         public int sourceType;
79         public int cacheFlag;
80         public int cacheStatus;
81         public int rotation;
82         public int mediaType;
83         public boolean isPanorama;
84         public boolean isWaitLoadingDisplayed;
85         public long setDataVersion;
86         public long coverDataVersion;
87         private BitmapLoader labelLoader;
88         private BitmapLoader coverLoader;
89     }
90 
AlbumSetSlidingWindow(GalleryActivity activity, AlbumSetDataLoader source, AlbumSetSlotRenderer.LabelSpec labelSpec, int cacheSize)91     public AlbumSetSlidingWindow(GalleryActivity activity,
92             AlbumSetDataLoader source, AlbumSetSlotRenderer.LabelSpec labelSpec, int cacheSize) {
93         source.setModelListener(this);
94         mSource = source;
95         mData = new AlbumSetEntry[cacheSize];
96         mSize = source.size();
97         mThreadPool = activity.getThreadPool();
98 
99         mLabelMaker = new AlbumLabelMaker(activity.getAndroidContext(), labelSpec);
100         mLoadingText = activity.getAndroidContext().getString(R.string.loading);
101         mTextureUploader = new TextureUploader(activity.getGLRoot());
102 
103         mHandler = new SynchronizedHandler(activity.getGLRoot()) {
104             @Override
105             public void handleMessage(Message message) {
106                 Utils.assertTrue(message.what == MSG_UPDATE_ALBUM_ENTRY);
107                 ((EntryUpdater) message.obj).updateEntry();
108             }
109         };
110     }
111 
setListener(Listener listener)112     public void setListener(Listener listener) {
113         mListener = listener;
114     }
115 
get(int slotIndex)116     public AlbumSetEntry get(int slotIndex) {
117         if (!isActiveSlot(slotIndex)) {
118             Utils.fail("invalid slot: %s outsides (%s, %s)",
119                     slotIndex, mActiveStart, mActiveEnd);
120         }
121         return mData[slotIndex % mData.length];
122     }
123 
size()124     public int size() {
125         return mSize;
126     }
127 
isActiveSlot(int slotIndex)128     public boolean isActiveSlot(int slotIndex) {
129         return slotIndex >= mActiveStart && slotIndex < mActiveEnd;
130     }
131 
setContentWindow(int contentStart, int contentEnd)132     private void setContentWindow(int contentStart, int contentEnd) {
133         if (contentStart == mContentStart && contentEnd == mContentEnd) return;
134 
135         if (contentStart >= mContentEnd || mContentStart >= contentEnd) {
136             for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
137                 freeSlotContent(i);
138             }
139             mSource.setActiveWindow(contentStart, contentEnd);
140             for (int i = contentStart; i < contentEnd; ++i) {
141                 prepareSlotContent(i);
142             }
143         } else {
144             for (int i = mContentStart; i < contentStart; ++i) {
145                 freeSlotContent(i);
146             }
147             for (int i = contentEnd, n = mContentEnd; i < n; ++i) {
148                 freeSlotContent(i);
149             }
150             mSource.setActiveWindow(contentStart, contentEnd);
151             for (int i = contentStart, n = mContentStart; i < n; ++i) {
152                 prepareSlotContent(i);
153             }
154             for (int i = mContentEnd; i < contentEnd; ++i) {
155                 prepareSlotContent(i);
156             }
157         }
158 
159         mContentStart = contentStart;
160         mContentEnd = contentEnd;
161     }
162 
setActiveWindow(int start, int end)163     public void setActiveWindow(int start, int end) {
164         if (!(start <= end && end - start <= mData.length && end <= mSize)) {
165             Utils.fail("start = %s, end = %s, length = %s, size = %s",
166                     start, end, mData.length, mSize);
167         }
168 
169         AlbumSetEntry data[] = mData;
170         mActiveStart = start;
171         mActiveEnd = end;
172         int contentStart = Utils.clamp((start + end) / 2 - data.length / 2,
173                 0, Math.max(0, mSize - data.length));
174         int contentEnd = Math.min(contentStart + data.length, mSize);
175         setContentWindow(contentStart, contentEnd);
176 
177         if (mIsActive) {
178             updateTextureUploadQueue();
179             updateAllImageRequests();
180         }
181     }
182 
183     // We would like to request non active slots in the following order:
184     // Order:    8 6 4 2                   1 3 5 7
185     //         |---------|---------------|---------|
186     //                   |<-  active  ->|
187     //         |<-------- cached range ----------->|
requestNonactiveImages()188     private void requestNonactiveImages() {
189         int range = Math.max(
190                 mContentEnd - mActiveEnd, mActiveStart - mContentStart);
191         for (int i = 0 ;i < range; ++i) {
192             requestImagesInSlot(mActiveEnd + i);
193             requestImagesInSlot(mActiveStart - 1 - i);
194         }
195     }
196 
cancelNonactiveImages()197     private void cancelNonactiveImages() {
198         int range = Math.max(
199                 mContentEnd - mActiveEnd, mActiveStart - mContentStart);
200         for (int i = 0 ;i < range; ++i) {
201             cancelImagesInSlot(mActiveEnd + i);
202             cancelImagesInSlot(mActiveStart - 1 - i);
203         }
204     }
205 
requestImagesInSlot(int slotIndex)206     private void requestImagesInSlot(int slotIndex) {
207         if (slotIndex < mContentStart || slotIndex >= mContentEnd) return;
208         AlbumSetEntry entry = mData[slotIndex % mData.length];
209         if (entry.coverLoader != null) entry.coverLoader.startLoad();
210         if (entry.labelLoader != null) entry.labelLoader.startLoad();
211     }
212 
cancelImagesInSlot(int slotIndex)213     private void cancelImagesInSlot(int slotIndex) {
214         if (slotIndex < mContentStart || slotIndex >= mContentEnd) return;
215         AlbumSetEntry entry = mData[slotIndex % mData.length];
216         if (entry.coverLoader != null) entry.coverLoader.cancelLoad();
217         if (entry.labelLoader != null) entry.labelLoader.cancelLoad();
218     }
219 
getDataVersion(MediaObject object)220     private static long getDataVersion(MediaObject object) {
221         return object == null
222                 ? MediaSet.INVALID_DATA_VERSION
223                 : object.getDataVersion();
224     }
225 
freeSlotContent(int slotIndex)226     private void freeSlotContent(int slotIndex) {
227         AlbumSetEntry entry = mData[slotIndex % mData.length];
228         if (entry.coverLoader != null) entry.coverLoader.recycle();
229         if (entry.labelLoader != null) entry.labelLoader.recycle();
230         if (entry.labelTexture != null) entry.labelTexture.recycle();
231         if (entry.bitmapTexture != null) entry.bitmapTexture.recycle();
232         mData[slotIndex % mData.length] = null;
233     }
234 
isLabelChanged( AlbumSetEntry entry, String title, int totalCount, int sourceType)235     private boolean isLabelChanged(
236             AlbumSetEntry entry, String title, int totalCount, int sourceType) {
237         return !Utils.equals(entry.title, title)
238                 || entry.totalCount != totalCount
239                 || entry.sourceType != sourceType;
240     }
241 
updateAlbumSetEntry(AlbumSetEntry entry, int slotIndex)242     private void updateAlbumSetEntry(AlbumSetEntry entry, int slotIndex) {
243         MediaSet album = mSource.getMediaSet(slotIndex);
244         MediaItem cover = mSource.getCoverItem(slotIndex);
245         int totalCount = mSource.getTotalCount(slotIndex);
246 
247         entry.album = album;
248         entry.setDataVersion = getDataVersion(album);
249         entry.cacheFlag = identifyCacheFlag(album);
250         entry.cacheStatus = identifyCacheStatus(album);
251         entry.setPath = (album == null) ? null : album.getPath();
252 
253         String title = (album == null) ? "" : Utils.ensureNotNull(album.getName());
254         int sourceType = DataSourceType.identifySourceType(album);
255         if (isLabelChanged(entry, title, totalCount, sourceType)) {
256             entry.title = title;
257             entry.totalCount = totalCount;
258             entry.sourceType = sourceType;
259             if (entry.labelLoader != null) {
260                 entry.labelLoader.recycle();
261                 entry.labelLoader = null;
262                 entry.labelTexture = null;
263             }
264             if (album != null) {
265                 entry.labelLoader = new AlbumLabelLoader(
266                         slotIndex, title, totalCount, sourceType);
267             }
268         }
269 
270         entry.coverItem = cover;
271         if (getDataVersion(cover) != entry.coverDataVersion) {
272             entry.coverDataVersion = getDataVersion(cover);
273             entry.isPanorama = GalleryUtils.isPanorama(cover);
274             entry.rotation = (cover == null) ? 0 : cover.getRotation();
275             entry.mediaType = (cover == null) ? 0 : cover.getMediaType();
276             if (entry.coverLoader != null) {
277                 entry.coverLoader.recycle();
278                 entry.coverLoader = null;
279                 entry.bitmapTexture = null;
280                 entry.content = null;
281             }
282             if (cover != null) {
283                 entry.coverLoader = new AlbumCoverLoader(slotIndex, cover);
284             }
285         }
286     }
287 
prepareSlotContent(int slotIndex)288     private void prepareSlotContent(int slotIndex) {
289         AlbumSetEntry entry = new AlbumSetEntry();
290         updateAlbumSetEntry(entry, slotIndex);
291         mData[slotIndex % mData.length] = entry;
292     }
293 
startLoadBitmap(BitmapLoader loader)294     private static boolean startLoadBitmap(BitmapLoader loader) {
295         if (loader == null) return false;
296         loader.startLoad();
297         return loader.isRequestInProgress();
298     }
299 
uploadBackgroundTextureInSlot(int index)300     private void uploadBackgroundTextureInSlot(int index) {
301         if (index < mContentStart || index >= mContentEnd) return;
302         AlbumSetEntry entry = mData[index % mData.length];
303         if (entry.bitmapTexture != null) {
304             mTextureUploader.addBgTexture(entry.bitmapTexture);
305         }
306         if (entry.labelTexture != null) {
307             mTextureUploader.addBgTexture(entry.labelTexture);
308         }
309     }
310 
updateTextureUploadQueue()311     private void updateTextureUploadQueue() {
312         if (!mIsActive) return;
313         mTextureUploader.clear();
314 
315         // Upload foreground texture
316         for (int i = mActiveStart, n = mActiveEnd; i < n; ++i) {
317             AlbumSetEntry entry = mData[i % mData.length];
318             if (entry.bitmapTexture != null) {
319                 mTextureUploader.addFgTexture(entry.bitmapTexture);
320             }
321             if (entry.labelTexture != null) {
322                 mTextureUploader.addFgTexture(entry.labelTexture);
323             }
324         }
325 
326         // add background textures
327         int range = Math.max(
328                 (mContentEnd - mActiveEnd), (mActiveStart - mContentStart));
329         for (int i = 0; i < range; ++i) {
330             uploadBackgroundTextureInSlot(mActiveEnd + i);
331             uploadBackgroundTextureInSlot(mActiveStart - i - 1);
332         }
333     }
334 
updateAllImageRequests()335     private void updateAllImageRequests() {
336         mActiveRequestCount = 0;
337         for (int i = mActiveStart, n = mActiveEnd; i < n; ++i) {
338             AlbumSetEntry entry = mData[i % mData.length];
339             if (startLoadBitmap(entry.coverLoader)) ++mActiveRequestCount;
340             if (startLoadBitmap(entry.labelLoader)) ++mActiveRequestCount;
341         }
342         if (mActiveRequestCount == 0) {
343             requestNonactiveImages();
344         } else {
345             cancelNonactiveImages();
346         }
347     }
348 
349     @Override
onSizeChanged(int size)350     public void onSizeChanged(int size) {
351         if (mIsActive && mSize != size) {
352             mSize = size;
353             if (mListener != null) mListener.onSizeChanged(mSize);
354             if (mContentEnd > mSize) mContentEnd = mSize;
355             if (mActiveEnd > mSize) mActiveEnd = mSize;
356         }
357     }
358 
359     @Override
onContentChanged(int index)360     public void onContentChanged(int index) {
361         if (!mIsActive) {
362             // paused, ignore slot changed event
363             return;
364         }
365 
366         // If the updated content is not cached, ignore it
367         if (index < mContentStart || index >= mContentEnd) {
368             Log.w(TAG, String.format(
369                     "invalid update: %s is outside (%s, %s)",
370                     index, mContentStart, mContentEnd) );
371             return;
372         }
373 
374         AlbumSetEntry entry = mData[index % mData.length];
375         updateAlbumSetEntry(entry, index);
376         updateAllImageRequests();
377         updateTextureUploadQueue();
378         if (mListener != null && isActiveSlot(index)) {
379             mListener.onContentChanged();
380         }
381     }
382 
getLoadingTexture()383     public BitmapTexture getLoadingTexture() {
384         if (mLoadingLabel == null) {
385             Bitmap bitmap = mLabelMaker.requestLabel(
386                     mLoadingText, "", DataSourceType.TYPE_NOT_CATEGORIZED)
387                     .run(ThreadPool.JOB_CONTEXT_STUB);
388             mLoadingLabel = new BitmapTexture(bitmap);
389             mLoadingLabel.setOpaque(false);
390         }
391         return mLoadingLabel;
392     }
393 
pause()394     public void pause() {
395         mIsActive = false;
396         mTextureUploader.clear();
397         for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
398             freeSlotContent(i);
399         }
400         mLabelMaker.clearRecycledLabels();
401     }
402 
resume()403     public void resume() {
404         mIsActive = true;
405         for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
406             prepareSlotContent(i);
407         }
408         updateAllImageRequests();
409     }
410 
411     private static interface EntryUpdater {
updateEntry()412         public void updateEntry();
413     }
414 
415     private class AlbumCoverLoader extends BitmapLoader implements EntryUpdater {
416         private MediaItem mMediaItem;
417         private final int mSlotIndex;
418 
AlbumCoverLoader(int slotIndex, MediaItem item)419         public AlbumCoverLoader(int slotIndex, MediaItem item) {
420             mSlotIndex = slotIndex;
421             mMediaItem = item;
422         }
423 
424         @Override
recycleBitmap(Bitmap bitmap)425         protected void recycleBitmap(Bitmap bitmap) {
426             MediaItem.getMicroThumbPool().recycle(bitmap);
427         }
428 
429         @Override
submitBitmapTask(FutureListener<Bitmap> l)430         protected Future<Bitmap> submitBitmapTask(FutureListener<Bitmap> l) {
431             return mThreadPool.submit(mMediaItem.requestImage(
432                     MediaItem.TYPE_MICROTHUMBNAIL), l);
433         }
434 
435         @Override
onLoadComplete(Bitmap bitmap)436         protected void onLoadComplete(Bitmap bitmap) {
437             mHandler.obtainMessage(MSG_UPDATE_ALBUM_ENTRY, this).sendToTarget();
438         }
439 
440         @Override
updateEntry()441         public void updateEntry() {
442             Bitmap bitmap = getBitmap();
443             if (bitmap == null) return; // error or recycled
444 
445             AlbumSetEntry entry = mData[mSlotIndex % mData.length];
446             BitmapTexture texture = new BitmapTexture(bitmap);
447             entry.bitmapTexture = texture;
448             entry.content = texture;
449 
450             if (isActiveSlot(mSlotIndex)) {
451                 mTextureUploader.addFgTexture(texture);
452                 --mActiveRequestCount;
453                 if (mActiveRequestCount == 0) requestNonactiveImages();
454                 if (mListener != null) mListener.onContentChanged();
455             } else {
456                 mTextureUploader.addBgTexture(texture);
457             }
458         }
459     }
460 
identifyCacheFlag(MediaSet set)461     private static int identifyCacheFlag(MediaSet set) {
462         if (set == null || (set.getSupportedOperations()
463                 & MediaSet.SUPPORT_CACHE) == 0) {
464             return MediaSet.CACHE_FLAG_NO;
465         }
466 
467         return set.getCacheFlag();
468     }
469 
identifyCacheStatus(MediaSet set)470     private static int identifyCacheStatus(MediaSet set) {
471         if (set == null || (set.getSupportedOperations()
472                 & MediaSet.SUPPORT_CACHE) == 0) {
473             return MediaSet.CACHE_STATUS_NOT_CACHED;
474         }
475 
476         return set.getCacheStatus();
477     }
478 
479     private class AlbumLabelLoader extends BitmapLoader implements EntryUpdater {
480         private final int mSlotIndex;
481         private final String mTitle;
482         private final int mTotalCount;
483         private final int mSourceType;
484 
AlbumLabelLoader( int slotIndex, String title, int totalCount, int sourceType)485         public AlbumLabelLoader(
486                 int slotIndex, String title, int totalCount, int sourceType) {
487             mSlotIndex = slotIndex;
488             mTitle = title;
489             mTotalCount = totalCount;
490             mSourceType = sourceType;
491         }
492 
493         @Override
submitBitmapTask(FutureListener<Bitmap> l)494         protected Future<Bitmap> submitBitmapTask(FutureListener<Bitmap> l) {
495             return mThreadPool.submit(mLabelMaker.requestLabel(
496                     mTitle, String.valueOf(mTotalCount), mSourceType), l);
497         }
498 
499         @Override
recycleBitmap(Bitmap bitmap)500         protected void recycleBitmap(Bitmap bitmap) {
501             mLabelMaker.recycleLabel(bitmap);
502         }
503 
504         @Override
onLoadComplete(Bitmap bitmap)505         protected void onLoadComplete(Bitmap bitmap) {
506             mHandler.obtainMessage(MSG_UPDATE_ALBUM_ENTRY, this).sendToTarget();
507         }
508 
509         @Override
updateEntry()510         public void updateEntry() {
511             Bitmap bitmap = getBitmap();
512             if (bitmap == null) return; // Error or recycled
513 
514             AlbumSetEntry entry = mData[mSlotIndex % mData.length];
515             BitmapTexture texture = new BitmapTexture(bitmap);
516             texture.setOpaque(false);
517             entry.labelTexture = texture;
518 
519             if (isActiveSlot(mSlotIndex)) {
520                 mTextureUploader.addFgTexture(texture);
521                 --mActiveRequestCount;
522                 if (mActiveRequestCount == 0) requestNonactiveImages();
523                 if (mListener != null) mListener.onContentChanged();
524             } else {
525                 mTextureUploader.addBgTexture(texture);
526             }
527         }
528     }
529 
onSlotSizeChanged(int width, int height)530     public void onSlotSizeChanged(int width, int height) {
531         if (mSlotWidth == width) return;
532 
533         mSlotWidth = width;
534         mLoadingLabel = null;
535         mLabelMaker.setLabelWidth(mSlotWidth);
536 
537         if (!mIsActive) return;
538 
539         for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
540             AlbumSetEntry entry = mData[i % mData.length];
541             if (entry.labelLoader != null) {
542                 entry.labelLoader.recycle();
543                 entry.labelLoader = null;
544                 entry.labelTexture = null;
545             }
546             if (entry.album != null) {
547                 entry.labelLoader = new AlbumLabelLoader(i,
548                         entry.title, entry.totalCount, entry.sourceType);
549             }
550         }
551         updateAllImageRequests();
552         updateTextureUploadQueue();
553     }
554 }
555