• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.ui;
18 
19 import android.graphics.Bitmap;
20 import android.os.Message;
21 
22 import com.android.gallery3d.app.AbstractGalleryActivity;
23 import com.android.gallery3d.app.AlbumDataLoader;
24 import com.android.gallery3d.common.Utils;
25 import com.android.gallery3d.data.BitmapPool;
26 import com.android.gallery3d.data.MediaItem;
27 import com.android.gallery3d.data.MediaObject;
28 import com.android.gallery3d.data.Path;
29 import com.android.gallery3d.data.MediaObject.PanoramaSupportCallback;
30 import com.android.gallery3d.util.Future;
31 import com.android.gallery3d.util.FutureListener;
32 import com.android.gallery3d.util.JobLimiter;
33 
34 public class AlbumSlidingWindow implements AlbumDataLoader.DataListener {
35     @SuppressWarnings("unused")
36     private static final String TAG = "AlbumSlidingWindow";
37 
38     private static final int MSG_UPDATE_ENTRY = 0;
39     private static final int JOB_LIMIT = 2;
40 
41     public static interface Listener {
onSizeChanged(int size)42         public void onSizeChanged(int size);
onContentChanged()43         public void onContentChanged();
44     }
45 
46     public static class AlbumEntry {
47         public MediaItem item;
48         public Path path;
49         public boolean isPanorama;
50         public int rotation;
51         public int mediaType;
52         public boolean isWaitDisplayed;
53         public TiledTexture bitmapTexture;
54         public Texture content;
55         private BitmapLoader contentLoader;
56         private PanoSupportListener mPanoSupportListener;
57     }
58 
59     private final AlbumDataLoader mSource;
60     private final AlbumEntry mData[];
61     private final SynchronizedHandler mHandler;
62     private final JobLimiter mThreadPool;
63     private final TiledTexture.Uploader mTileUploader;
64 
65     private int mSize;
66 
67     private int mContentStart = 0;
68     private int mContentEnd = 0;
69 
70     private int mActiveStart = 0;
71     private int mActiveEnd = 0;
72 
73     private Listener mListener;
74 
75     private int mActiveRequestCount = 0;
76     private boolean mIsActive = false;
77 
78     private class PanoSupportListener implements PanoramaSupportCallback {
79         public final AlbumEntry mEntry;
PanoSupportListener(AlbumEntry entry)80         public PanoSupportListener (AlbumEntry entry) {
81             mEntry = entry;
82         }
83         @Override
panoramaInfoAvailable(MediaObject mediaObject, boolean isPanorama, boolean isPanorama360)84         public void panoramaInfoAvailable(MediaObject mediaObject, boolean isPanorama,
85                 boolean isPanorama360) {
86             if (mEntry != null) mEntry.isPanorama = isPanorama;
87         }
88     }
89 
AlbumSlidingWindow(AbstractGalleryActivity activity, AlbumDataLoader source, int cacheSize)90     public AlbumSlidingWindow(AbstractGalleryActivity activity,
91             AlbumDataLoader source, int cacheSize) {
92         source.setDataListener(this);
93         mSource = source;
94         mData = new AlbumEntry[cacheSize];
95         mSize = source.size();
96 
97         mHandler = new SynchronizedHandler(activity.getGLRoot()) {
98             @Override
99             public void handleMessage(Message message) {
100                 Utils.assertTrue(message.what == MSG_UPDATE_ENTRY);
101                 ((ThumbnailLoader) message.obj).updateEntry();
102             }
103         };
104 
105         mThreadPool = new JobLimiter(activity.getThreadPool(), JOB_LIMIT);
106         mTileUploader = new TiledTexture.Uploader(activity.getGLRoot());
107     }
108 
setListener(Listener listener)109     public void setListener(Listener listener) {
110         mListener = listener;
111     }
112 
get(int slotIndex)113     public AlbumEntry get(int slotIndex) {
114         if (!isActiveSlot(slotIndex)) {
115             Utils.fail("invalid slot: %s outsides (%s, %s)",
116                     slotIndex, mActiveStart, mActiveEnd);
117         }
118         return mData[slotIndex % mData.length];
119     }
120 
isActiveSlot(int slotIndex)121     public boolean isActiveSlot(int slotIndex) {
122         return slotIndex >= mActiveStart && slotIndex < mActiveEnd;
123     }
124 
setContentWindow(int contentStart, int contentEnd)125     private void setContentWindow(int contentStart, int contentEnd) {
126         if (contentStart == mContentStart && contentEnd == mContentEnd) return;
127 
128         if (!mIsActive) {
129             mContentStart = contentStart;
130             mContentEnd = contentEnd;
131             mSource.setActiveWindow(contentStart, contentEnd);
132             return;
133         }
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("%s, %s, %s, %s", start, end, mData.length, mSize);
166         }
167         AlbumEntry data[] = mData;
168 
169         mActiveStart = start;
170         mActiveEnd = end;
171 
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         updateTextureUploadQueue();
177         if (mIsActive) updateAllImageRequests();
178     }
179 
uploadBgTextureInSlot(int index)180     private void uploadBgTextureInSlot(int index) {
181         if (index < mContentEnd && index >= mContentStart) {
182             AlbumEntry entry = mData[index % mData.length];
183             if (entry.bitmapTexture != null) {
184                 mTileUploader.addTexture(entry.bitmapTexture);
185             }
186         }
187     }
188 
updateTextureUploadQueue()189     private void updateTextureUploadQueue() {
190         if (!mIsActive) return;
191         mTileUploader.clear();
192 
193         // add foreground textures
194         for (int i = mActiveStart, n = mActiveEnd; i < n; ++i) {
195             AlbumEntry entry = mData[i % mData.length];
196             if (entry.bitmapTexture != null) {
197                 mTileUploader.addTexture(entry.bitmapTexture);
198             }
199         }
200 
201         // add background textures
202         int range = Math.max(
203                 (mContentEnd - mActiveEnd), (mActiveStart - mContentStart));
204         for (int i = 0; i < range; ++i) {
205             uploadBgTextureInSlot(mActiveEnd + i);
206             uploadBgTextureInSlot(mActiveStart - i - 1);
207         }
208     }
209 
210     // We would like to request non active slots in the following order:
211     // Order:    8 6 4 2                   1 3 5 7
212     //         |---------|---------------|---------|
213     //                   |<-  active  ->|
214     //         |<-------- cached range ----------->|
requestNonactiveImages()215     private void requestNonactiveImages() {
216         int range = Math.max(
217                 (mContentEnd - mActiveEnd), (mActiveStart - mContentStart));
218         for (int i = 0 ;i < range; ++i) {
219             requestSlotImage(mActiveEnd + i);
220             requestSlotImage(mActiveStart - 1 - i);
221         }
222     }
223 
224     // return whether the request is in progress or not
requestSlotImage(int slotIndex)225     private boolean requestSlotImage(int slotIndex) {
226         if (slotIndex < mContentStart || slotIndex >= mContentEnd) return false;
227         AlbumEntry entry = mData[slotIndex % mData.length];
228         if (entry.content != null || entry.item == null) return false;
229 
230         // Set up the panorama callback
231         entry.mPanoSupportListener = new PanoSupportListener(entry);
232         entry.item.getPanoramaSupport(entry.mPanoSupportListener);
233 
234         entry.contentLoader.startLoad();
235         return entry.contentLoader.isRequestInProgress();
236     }
237 
cancelNonactiveImages()238     private void cancelNonactiveImages() {
239         int range = Math.max(
240                 (mContentEnd - mActiveEnd), (mActiveStart - mContentStart));
241         for (int i = 0 ;i < range; ++i) {
242             cancelSlotImage(mActiveEnd + i);
243             cancelSlotImage(mActiveStart - 1 - i);
244         }
245     }
246 
cancelSlotImage(int slotIndex)247     private void cancelSlotImage(int slotIndex) {
248         if (slotIndex < mContentStart || slotIndex >= mContentEnd) return;
249         AlbumEntry item = mData[slotIndex % mData.length];
250         if (item.contentLoader != null) item.contentLoader.cancelLoad();
251     }
252 
freeSlotContent(int slotIndex)253     private void freeSlotContent(int slotIndex) {
254         AlbumEntry data[] = mData;
255         int index = slotIndex % data.length;
256         AlbumEntry entry = data[index];
257         if (entry.contentLoader != null) entry.contentLoader.recycle();
258         if (entry.bitmapTexture != null) entry.bitmapTexture.recycle();
259         data[index] = null;
260     }
261 
prepareSlotContent(int slotIndex)262     private void prepareSlotContent(int slotIndex) {
263         AlbumEntry entry = new AlbumEntry();
264         MediaItem item = mSource.get(slotIndex); // item could be null;
265         entry.item = item;
266         entry.mediaType = (item == null)
267                 ? MediaItem.MEDIA_TYPE_UNKNOWN
268                 : entry.item.getMediaType();
269         entry.path = (item == null) ? null : item.getPath();
270         entry.rotation = (item == null) ? 0 : item.getRotation();
271         entry.contentLoader = new ThumbnailLoader(slotIndex, entry.item);
272         mData[slotIndex % mData.length] = entry;
273     }
274 
updateAllImageRequests()275     private void updateAllImageRequests() {
276         mActiveRequestCount = 0;
277         for (int i = mActiveStart, n = mActiveEnd; i < n; ++i) {
278             if (requestSlotImage(i)) ++mActiveRequestCount;
279         }
280         if (mActiveRequestCount == 0) {
281             requestNonactiveImages();
282         } else {
283             cancelNonactiveImages();
284         }
285     }
286 
287     private class ThumbnailLoader extends BitmapLoader  {
288         private final int mSlotIndex;
289         private final MediaItem mItem;
290 
ThumbnailLoader(int slotIndex, MediaItem item)291         public ThumbnailLoader(int slotIndex, MediaItem item) {
292             mSlotIndex = slotIndex;
293             mItem = item;
294         }
295 
296         @Override
recycleBitmap(Bitmap bitmap)297         protected void recycleBitmap(Bitmap bitmap) {
298             BitmapPool pool = MediaItem.getMicroThumbPool();
299             if (pool != null) pool.recycle(bitmap);
300         }
301 
302         @Override
submitBitmapTask(FutureListener<Bitmap> l)303         protected Future<Bitmap> submitBitmapTask(FutureListener<Bitmap> l) {
304             return mThreadPool.submit(
305                     mItem.requestImage(MediaItem.TYPE_MICROTHUMBNAIL), this);
306         }
307 
308         @Override
onLoadComplete(Bitmap bitmap)309         protected void onLoadComplete(Bitmap bitmap) {
310             mHandler.obtainMessage(MSG_UPDATE_ENTRY, this).sendToTarget();
311         }
312 
updateEntry()313         public void updateEntry() {
314             Bitmap bitmap = getBitmap();
315             if (bitmap == null) return; // error or recycled
316             AlbumEntry entry = mData[mSlotIndex % mData.length];
317             entry.bitmapTexture = new TiledTexture(bitmap);
318             entry.content = entry.bitmapTexture;
319 
320             if (isActiveSlot(mSlotIndex)) {
321                 mTileUploader.addTexture(entry.bitmapTexture);
322                 --mActiveRequestCount;
323                 if (mActiveRequestCount == 0) requestNonactiveImages();
324                 if (mListener != null) mListener.onContentChanged();
325             } else {
326                 mTileUploader.addTexture(entry.bitmapTexture);
327             }
328         }
329     }
330 
331     @Override
onSizeChanged(int size)332     public void onSizeChanged(int size) {
333         if (mSize != size) {
334             mSize = size;
335             if (mListener != null) mListener.onSizeChanged(mSize);
336             if (mContentEnd > mSize) mContentEnd = mSize;
337             if (mActiveEnd > mSize) mActiveEnd = mSize;
338         }
339     }
340 
341     @Override
onContentChanged(int index)342     public void onContentChanged(int index) {
343         if (index >= mContentStart && index < mContentEnd && mIsActive) {
344             freeSlotContent(index);
345             prepareSlotContent(index);
346             updateAllImageRequests();
347             if (mListener != null && isActiveSlot(index)) {
348                 mListener.onContentChanged();
349             }
350         }
351     }
352 
resume()353     public void resume() {
354         mIsActive = true;
355         TiledTexture.prepareResources();
356         for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
357             prepareSlotContent(i);
358         }
359         updateAllImageRequests();
360     }
361 
pause()362     public void pause() {
363         mIsActive = false;
364         mTileUploader.clear();
365         TiledTexture.freeResources();
366         for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
367             freeSlotContent(i);
368         }
369     }
370 }
371