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        @Override
96        public Void run(JobContext jc) {
97            while (true) {
98                synchronized (SlideshowDataAdapter.this) {
99                    while (mIsActive && (!mDataReady
100                            || mImageQueue.size() >= IMAGE_QUEUE_CAPACITY)) {
101                        try {
102                            SlideshowDataAdapter.this.wait();
103                        } catch (InterruptedException ex) {
104                            // ignored.
105                        }
106                        continue;
107                    }
108                }
109                if (!mIsActive) return null;
110                mNeedReset = false;
111
112                MediaItem item = loadItem();
113
114                if (mNeedReset) {
115                    synchronized (SlideshowDataAdapter.this) {
116                        mImageQueue.clear();
117                        mLoadIndex = mNextOutput;
118                    }
119                    continue;
120                }
121
122                if (item == null) {
123                    synchronized (SlideshowDataAdapter.this) {
124                        if (!mNeedReload.get()) mDataReady = false;
125                        SlideshowDataAdapter.this.notifyAll();
126                    }
127                    continue;
128                }
129
130                Bitmap bitmap = item
131                        .requestImage(MediaItem.TYPE_THUMBNAIL)
132                        .run(jc);
133
134                if (bitmap != null) {
135                    synchronized (SlideshowDataAdapter.this) {
136                        mImageQueue.addLast(
137                                new Slide(item, mLoadIndex, bitmap));
138                        if (mImageQueue.size() == 1) {
139                            SlideshowDataAdapter.this.notifyAll();
140                        }
141                    }
142                }
143                ++mLoadIndex;
144            }
145        }
146    }
147
148    private class SourceListener implements ContentListener {
149        @Override
150        public void onContentDirty() {
151            synchronized (SlideshowDataAdapter.this) {
152                mNeedReload.set(true);
153                mDataReady = true;
154                SlideshowDataAdapter.this.notifyAll();
155            }
156        }
157    }
158
159    private synchronized Slide innerNextBitmap() {
160        while (mIsActive && mDataReady && mImageQueue.isEmpty()) {
161            try {
162                wait();
163            } catch (InterruptedException t) {
164                throw new AssertionError();
165            }
166        }
167        if (mImageQueue.isEmpty()) return null;
168        mNextOutput++;
169        this.notifyAll();
170        return mImageQueue.removeFirst();
171    }
172
173    @Override
174    public Future<Slide> nextSlide(FutureListener<Slide> listener) {
175        return mThreadPool.submit(new Job<Slide>() {
176            @Override
177            public Slide run(JobContext jc) {
178                jc.setMode(ThreadPool.MODE_NONE);
179                return innerNextBitmap();
180            }
181        }, listener);
182    }
183
184    @Override
185    public void pause() {
186        synchronized (this) {
187            mIsActive = false;
188            notifyAll();
189        }
190        mSource.removeContentListener(mSourceListener);
191        mReloadTask.cancel();
192        mReloadTask.waitDone();
193        mReloadTask = null;
194    }
195
196    @Override
197    public synchronized void resume() {
198        mIsActive = true;
199        mSource.addContentListener(mSourceListener);
200        mNeedReload.set(true);
201        mDataReady = true;
202        mReloadTask = mThreadPool.submit(new ReloadTask());
203    }
204}
205