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