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