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.app;
18
19import android.graphics.Bitmap;
20
21import com.android.gallery3d.app.SlideshowPage.Slide;
22import com.android.gallery3d.data.ContentListener;
23import com.android.gallery3d.data.MediaItem;
24import com.android.gallery3d.data.MediaObject;
25import com.android.gallery3d.data.Path;
26import com.android.gallery3d.util.Future;
27import com.android.gallery3d.util.FutureListener;
28import com.android.gallery3d.util.ThreadPool;
29import com.android.gallery3d.util.ThreadPool.Job;
30import com.android.gallery3d.util.ThreadPool.JobContext;
31
32import java.util.LinkedList;
33import java.util.concurrent.atomic.AtomicBoolean;
34
35public class SlideshowDataAdapter implements SlideshowPage.Model {
36    @SuppressWarnings("unused")
37    private static final String TAG = "SlideshowDataAdapter";
38
39    private static final int IMAGE_QUEUE_CAPACITY = 3;
40
41    public interface SlideshowSource {
42        public void addContentListener(ContentListener listener);
43        public void removeContentListener(ContentListener listener);
44        public long reload();
45        public MediaItem getMediaItem(int index);
46        public int findItemIndex(Path path, int hint);
47    }
48
49    private final SlideshowSource mSource;
50
51    private int mLoadIndex = 0;
52    private int mNextOutput = 0;
53    private boolean mIsActive = false;
54    private boolean mNeedReset;
55    private boolean mDataReady;
56    private Path mInitialPath;
57
58    private final LinkedList<Slide> mImageQueue = new LinkedList<Slide>();
59
60    private Future<Void> mReloadTask;
61    private final ThreadPool mThreadPool;
62
63    private long mDataVersion = MediaObject.INVALID_DATA_VERSION;
64    private final AtomicBoolean mNeedReload = new AtomicBoolean(false);
65    private final SourceListener mSourceListener = new SourceListener();
66
67    // The index is just a hint if initialPath is set
68    public SlideshowDataAdapter(GalleryContext context, SlideshowSource source, int index,
69            Path initialPath) {
70        mSource = source;
71        mInitialPath = initialPath;
72        mLoadIndex = index;
73        mNextOutput = index;
74        mThreadPool = context.getThreadPool();
75    }
76
77    private MediaItem loadItem() {
78        if (mNeedReload.compareAndSet(true, false)) {
79            long v = mSource.reload();
80            if (v != mDataVersion) {
81                mDataVersion = v;
82                mNeedReset = true;
83                return null;
84            }
85        }
86        int index = mLoadIndex;
87        if (mInitialPath != null) {
88            index = mSource.findItemIndex(mInitialPath, index);
89            mInitialPath = null;
90        }
91        return mSource.getMediaItem(index);
92    }
93
94    private class ReloadTask implements Job<Void> {
95        public Void run(JobContext jc) {
96            while (true) {
97                synchronized (SlideshowDataAdapter.this) {
98                    while (mIsActive && (!mDataReady
99                            || mImageQueue.size() >= IMAGE_QUEUE_CAPACITY)) {
100                        try {
101                            SlideshowDataAdapter.this.wait();
102                        } catch (InterruptedException ex) {
103                            // ignored.
104                        }
105                        continue;
106                    }
107                }
108                if (!mIsActive) return null;
109                mNeedReset = false;
110
111                MediaItem item = loadItem();
112
113                if (mNeedReset) {
114                    synchronized (SlideshowDataAdapter.this) {
115                        mImageQueue.clear();
116                        mLoadIndex = mNextOutput;
117                    }
118                    continue;
119                }
120
121                if (item == null) {
122                    synchronized (SlideshowDataAdapter.this) {
123                        if (!mNeedReload.get()) mDataReady = false;
124                        SlideshowDataAdapter.this.notifyAll();
125                    }
126                    continue;
127                }
128
129                Bitmap bitmap = item
130                        .requestImage(MediaItem.TYPE_THUMBNAIL)
131                        .run(jc);
132
133                if (bitmap != null) {
134                    synchronized (SlideshowDataAdapter.this) {
135                        mImageQueue.addLast(
136                                new Slide(item, mLoadIndex, bitmap));
137                        if (mImageQueue.size() == 1) {
138                            SlideshowDataAdapter.this.notifyAll();
139                        }
140                    }
141                }
142                ++mLoadIndex;
143            }
144        }
145    }
146
147    private class SourceListener implements ContentListener {
148        public void onContentDirty() {
149            synchronized (SlideshowDataAdapter.this) {
150                mNeedReload.set(true);
151                mDataReady = true;
152                SlideshowDataAdapter.this.notifyAll();
153            }
154        }
155    }
156
157    private synchronized Slide innerNextBitmap() {
158        while (mIsActive && mDataReady && mImageQueue.isEmpty()) {
159            try {
160                wait();
161            } catch (InterruptedException t) {
162                throw new AssertionError();
163            }
164        }
165        if (mImageQueue.isEmpty()) return null;
166        mNextOutput++;
167        this.notifyAll();
168        return mImageQueue.removeFirst();
169    }
170
171    public Future<Slide> nextSlide(FutureListener<Slide> listener) {
172        return mThreadPool.submit(new Job<Slide>() {
173            public Slide run(JobContext jc) {
174                jc.setMode(ThreadPool.MODE_NONE);
175                return innerNextBitmap();
176            }
177        }, listener);
178    }
179
180    public void pause() {
181        synchronized (this) {
182            mIsActive = false;
183            notifyAll();
184        }
185        mSource.removeContentListener(mSourceListener);
186        mReloadTask.cancel();
187        mReloadTask.waitDone();
188        mReloadTask = null;
189    }
190
191    public synchronized void resume() {
192        mIsActive = true;
193        mSource.addContentListener(mSourceListener);
194        mNeedReload.set(true);
195        mDataReady = true;
196        mReloadTask = mThreadPool.submit(new ReloadTask());
197    }
198}
199