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