OSDN Git Service

42388ead7f6b1d86fca5eedf27852c6e1f8cd156
[android-x86/packages-apps-Gallery2.git] / src / com / android / gallery3d / app / AlbumDataAdapter.java
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 com.android.gallery3d.common.Utils;
20 import com.android.gallery3d.data.ContentListener;
21 import com.android.gallery3d.data.DataManager;
22 import com.android.gallery3d.data.MediaItem;
23 import com.android.gallery3d.data.MediaObject;
24 import com.android.gallery3d.data.MediaSet;
25 import com.android.gallery3d.ui.AlbumView;
26 import com.android.gallery3d.ui.SynchronizedHandler;
27
28 import android.os.Handler;
29 import android.os.Message;
30
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.concurrent.Callable;
34 import java.util.concurrent.ExecutionException;
35 import java.util.concurrent.FutureTask;
36
37 public class AlbumDataAdapter implements AlbumView.Model {
38     @SuppressWarnings("unused")
39     private static final String TAG = "AlbumDataAdapter";
40     private static final int DATA_CACHE_SIZE = 1000;
41
42     private static final int MSG_LOAD_START = 1;
43     private static final int MSG_LOAD_FINISH = 2;
44     private static final int MSG_RUN_OBJECT = 3;
45
46     private static final int MIN_LOAD_COUNT = 32;
47     private static final int MAX_LOAD_COUNT = 64;
48
49     private final MediaItem[] mData;
50     private final long[] mItemVersion;
51     private final long[] mSetVersion;
52
53     private int mActiveStart = 0;
54     private int mActiveEnd = 0;
55
56     private int mContentStart = 0;
57     private int mContentEnd = 0;
58
59     private final MediaSet mSource;
60     private long mSourceVersion = MediaObject.INVALID_DATA_VERSION;
61
62     private final Handler mMainHandler;
63     private int mSize = 0;
64
65     private AlbumView.ModelListener mModelListener;
66     private MySourceListener mSourceListener = new MySourceListener();
67     private LoadingListener mLoadingListener;
68
69     private ReloadTask mReloadTask;
70
71     public AlbumDataAdapter(GalleryActivity context, MediaSet mediaSet) {
72         mSource = mediaSet;
73
74         mData = new MediaItem[DATA_CACHE_SIZE];
75         mItemVersion = new long[DATA_CACHE_SIZE];
76         mSetVersion = new long[DATA_CACHE_SIZE];
77         Arrays.fill(mItemVersion, MediaObject.INVALID_DATA_VERSION);
78         Arrays.fill(mSetVersion, MediaObject.INVALID_DATA_VERSION);
79
80         mMainHandler = new SynchronizedHandler(context.getGLRoot()) {
81             @Override
82             public void handleMessage(Message message) {
83                 switch (message.what) {
84                     case MSG_RUN_OBJECT:
85                         ((Runnable) message.obj).run();
86                         return;
87                     case MSG_LOAD_START:
88                         if (mLoadingListener != null) mLoadingListener.onLoadingStarted();
89                         return;
90                     case MSG_LOAD_FINISH:
91                         if (mLoadingListener != null) mLoadingListener.onLoadingFinished();
92                         return;
93                 }
94             }
95         };
96     }
97
98     public void resume() {
99         mSource.addContentListener(mSourceListener);
100         mReloadTask = new ReloadTask();
101         mReloadTask.start();
102     }
103
104     public void pause() {
105         mReloadTask.terminate();
106         mReloadTask = null;
107         mSource.removeContentListener(mSourceListener);
108     }
109
110     public MediaItem get(int index) {
111         if (!isActive(index)) {
112             throw new IllegalArgumentException(String.format(
113                     "%s not in (%s, %s)", index, mActiveStart, mActiveEnd));
114         }
115         return mData[index % mData.length];
116     }
117
118     public int getActiveStart() {
119         return mActiveStart;
120     }
121
122     public int getActiveEnd() {
123         return mActiveEnd;
124     }
125
126     public boolean isActive(int index) {
127         return index >= mActiveStart && index < mActiveEnd;
128     }
129
130     public int size() {
131         return mSize;
132     }
133
134     private void clearSlot(int slotIndex) {
135         mData[slotIndex] = null;
136         mItemVersion[slotIndex] = MediaObject.INVALID_DATA_VERSION;
137         mSetVersion[slotIndex] = MediaObject.INVALID_DATA_VERSION;
138     }
139
140     private void setContentWindow(int contentStart, int contentEnd) {
141         if (contentStart == mContentStart && contentEnd == mContentEnd) return;
142         int end = mContentEnd;
143         int start = mContentStart;
144
145         // We need change the content window before calling reloadData(...)
146         synchronized (this) {
147             mContentStart = contentStart;
148             mContentEnd = contentEnd;
149         }
150         long[] itemVersion = mItemVersion;
151         long[] setVersion = mSetVersion;
152         if (contentStart >= end || start >= contentEnd) {
153             for (int i = start, n = end; i < n; ++i) {
154                 clearSlot(i % DATA_CACHE_SIZE);
155             }
156         } else {
157             for (int i = start; i < contentStart; ++i) {
158                 clearSlot(i % DATA_CACHE_SIZE);
159             }
160             for (int i = contentEnd, n = end; i < n; ++i) {
161                 clearSlot(i % DATA_CACHE_SIZE);
162             }
163         }
164         if (mReloadTask != null) mReloadTask.notifyDirty();
165     }
166
167     public void setActiveWindow(int start, int end) {
168         if (start == mActiveStart && end == mActiveEnd) return;
169
170         Utils.assertTrue(start <= end
171                 && end - start <= mData.length && end <= mSize);
172
173         int length = mData.length;
174         mActiveStart = start;
175         mActiveEnd = end;
176
177         // If no data is visible, keep the cache content
178         if (start == end) return;
179
180         int contentStart = Utils.clamp((start + end) / 2 - length / 2,
181                 0, Math.max(0, mSize - length));
182         int contentEnd = Math.min(contentStart + length, mSize);
183         if (mContentStart > start || mContentEnd < end
184                 || Math.abs(contentStart - mContentStart) > MIN_LOAD_COUNT) {
185             setContentWindow(contentStart, contentEnd);
186         }
187     }
188
189     private class MySourceListener implements ContentListener {
190         public void onContentDirty() {
191             if (mReloadTask != null) mReloadTask.notifyDirty();
192         }
193     }
194
195     public void setModelListener(AlbumView.ModelListener listener) {
196         mModelListener = listener;
197     }
198
199     public void setLoadingListener(LoadingListener listener) {
200         mLoadingListener = listener;
201     }
202
203     private <T> T executeAndWait(Callable<T> callable) {
204         FutureTask<T> task = new FutureTask<T>(callable);
205         mMainHandler.sendMessage(
206                 mMainHandler.obtainMessage(MSG_RUN_OBJECT, task));
207         try {
208             return task.get();
209         } catch (InterruptedException e) {
210             return null;
211         } catch (ExecutionException e) {
212             throw new RuntimeException(e);
213         }
214     }
215
216     private static class UpdateInfo {
217         public long version;
218         public int reloadStart;
219         public int reloadCount;
220
221         public int size;
222         public ArrayList<MediaItem> items;
223     }
224
225     private class GetUpdateInfo implements Callable<UpdateInfo> {
226         private final long mVersion;
227
228         public GetUpdateInfo(long version) {
229             mVersion = version;
230         }
231
232         public UpdateInfo call() throws Exception {
233             UpdateInfo info = new UpdateInfo();
234             long version = mVersion;
235             info.version = mSourceVersion;
236             info.size = mSize;
237             long setVersion[] = mSetVersion;
238             for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
239                 int index = i % DATA_CACHE_SIZE;
240                 if (setVersion[index] != version) {
241                     info.reloadStart = i;
242                     info.reloadCount = Math.min(MAX_LOAD_COUNT, n - i);
243                     return info;
244                 }
245             }
246             return mSourceVersion == mVersion ? null : info;
247         }
248     }
249
250     private class UpdateContent implements Callable<Void> {
251
252         private UpdateInfo mUpdateInfo;
253
254         public UpdateContent(UpdateInfo info) {
255             mUpdateInfo = info;
256         }
257
258         @Override
259         public Void call() throws Exception {
260             UpdateInfo info = mUpdateInfo;
261             mSourceVersion = info.version;
262             if (mSize != info.size) {
263                 mSize = info.size;
264                 if (mModelListener != null) mModelListener.onSizeChanged(mSize);
265                 if (mContentEnd > mSize) mContentEnd = mSize;
266                 if (mActiveEnd > mSize) mActiveEnd = mSize;
267             }
268
269             ArrayList<MediaItem> items = info.items;
270
271             if (items == null) return null;
272             int start = Math.max(info.reloadStart, mContentStart);
273             int end = Math.min(info.reloadStart + items.size(), mContentEnd);
274
275             for (int i = start; i < end; ++i) {
276                 int index = i % DATA_CACHE_SIZE;
277                 mSetVersion[index] = info.version;
278                 MediaItem updateItem = items.get(i - info.reloadStart);
279                 long itemVersion = updateItem.getDataVersion();
280                 if (mItemVersion[index] != itemVersion) {
281                     mItemVersion[index] = itemVersion;
282                     mData[index] = updateItem;
283                     if (mModelListener != null && i >= mActiveStart && i < mActiveEnd) {
284                         mModelListener.onWindowContentChanged(i);
285                     }
286                 }
287             }
288             return null;
289         }
290     }
291
292     /*
293      * The thread model of ReloadTask
294      *      *
295      * [Reload Task]       [Main Thread]
296      *       |                   |
297      * getUpdateInfo() -->       |           (synchronous call)
298      *     (wait) <----    getUpdateInfo()
299      *       |                   |
300      *   Load Data               |
301      *       |                   |
302      * updateContent() -->       |           (synchronous call)
303      *     (wait)          updateContent()
304      *       |                   |
305      *       |                   |
306      */
307     private class ReloadTask extends Thread {
308
309         private volatile boolean mActive = true;
310         private volatile boolean mDirty = true;
311         private boolean mIsLoading = false;
312
313         private void updateLoading(boolean loading) {
314             if (mIsLoading == loading) return;
315             mIsLoading = loading;
316             mMainHandler.sendEmptyMessage(loading ? MSG_LOAD_START : MSG_LOAD_FINISH);
317         }
318
319         @Override
320         public void run() {
321             boolean updateComplete = false;
322             while (mActive) {
323                 synchronized (this) {
324                     if (mActive && !mDirty && updateComplete) {
325                         updateLoading(false);
326                         Utils.waitWithoutInterrupt(this);
327                         continue;
328                     }
329                 }
330                 mDirty = false;
331                 updateLoading(true);
332                 long version;
333                 synchronized (DataManager.LOCK) {
334                     version = mSource.reload();
335                 }
336                 UpdateInfo info = executeAndWait(new GetUpdateInfo(version));
337                 updateComplete = info == null;
338                 if (updateComplete) continue;
339                 synchronized (DataManager.LOCK) {
340                     if (info.version != version) {
341                         info.size = mSource.getMediaItemCount();
342                         info.version = version;
343                     }
344                     if (info.reloadCount > 0) {
345                         info.items = mSource.getMediaItem(info.reloadStart, info.reloadCount);
346                     }
347                 }
348                 executeAndWait(new UpdateContent(info));
349             }
350             updateLoading(false);
351         }
352
353         public synchronized void notifyDirty() {
354             mDirty = true;
355             notifyAll();
356         }
357
358         public synchronized void terminate() {
359             mActive = false;
360             notifyAll();
361         }
362     }
363 }