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