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.app;
18 
19 import android.graphics.Bitmap;
20 
21 import com.android.gallery3d.app.SlideshowPage.Slide;
22 import com.android.gallery3d.data.ContentListener;
23 import com.android.gallery3d.data.MediaItem;
24 import com.android.gallery3d.data.MediaObject;
25 import com.android.gallery3d.data.Path;
26 import com.android.gallery3d.util.Future;
27 import com.android.gallery3d.util.FutureListener;
28 import com.android.gallery3d.util.ThreadPool;
29 import com.android.gallery3d.util.ThreadPool.Job;
30 import com.android.gallery3d.util.ThreadPool.JobContext;
31 
32 import java.util.LinkedList;
33 import java.util.concurrent.atomic.AtomicBoolean;
34 
35 public 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 {
addContentListener(ContentListener listener)42         public void addContentListener(ContentListener listener);
removeContentListener(ContentListener listener)43         public void removeContentListener(ContentListener listener);
reload()44         public long reload();
getMediaItem(int index)45         public MediaItem getMediaItem(int index);
findItemIndex(Path path, int hint)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
SlideshowDataAdapter(GalleryContext context, SlideshowSource source, int index, Path initialPath)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 
loadItem()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
run(JobContext jc)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
onContentDirty()150         public void onContentDirty() {
151             synchronized (SlideshowDataAdapter.this) {
152                 mNeedReload.set(true);
153                 mDataReady = true;
154                 SlideshowDataAdapter.this.notifyAll();
155             }
156         }
157     }
158 
innerNextBitmap()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
nextSlide(FutureListener<Slide> listener)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