• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.os.Handler;
20 import android.os.Message;
21 import android.os.Process;
22 import android.os.SystemClock;
23 
24 import com.android.gallery3d.common.Utils;
25 import com.android.gallery3d.data.ContentListener;
26 import com.android.gallery3d.data.DataManager;
27 import com.android.gallery3d.data.MediaItem;
28 import com.android.gallery3d.data.MediaObject;
29 import com.android.gallery3d.data.MediaSet;
30 import com.android.gallery3d.ui.SynchronizedHandler;
31 
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 AlbumSetDataLoader {
38     @SuppressWarnings("unused")
39     private static final String TAG = "AlbumSetDataAdapter";
40 
41     private static final int INDEX_NONE = -1;
42 
43     private static final int MIN_LOAD_COUNT = 4;
44 
45     private static final int MSG_LOAD_START = 1;
46     private static final int MSG_LOAD_FINISH = 2;
47     private static final int MSG_RUN_OBJECT = 3;
48 
49     public static interface DataListener {
onContentChanged(int index)50         public void onContentChanged(int index);
onSizeChanged(int size)51         public void onSizeChanged(int size);
52     }
53 
54     private final MediaSet[] mData;
55     private final MediaItem[] mCoverItem;
56     private final int[] mTotalCount;
57     private final long[] mItemVersion;
58     private final long[] mSetVersion;
59 
60     private int mActiveStart = 0;
61     private int mActiveEnd = 0;
62 
63     private int mContentStart = 0;
64     private int mContentEnd = 0;
65 
66     private final MediaSet mSource;
67     private long mSourceVersion = MediaObject.INVALID_DATA_VERSION;
68     private int mSize;
69 
70     private DataListener mDataListener;
71     private LoadingListener mLoadingListener;
72     private ReloadTask mReloadTask;
73 
74     private final Handler mMainHandler;
75 
76     private final MySourceListener mSourceListener = new MySourceListener();
77 
AlbumSetDataLoader(GalleryActivity activity, MediaSet albumSet, int cacheSize)78     public AlbumSetDataLoader(GalleryActivity activity, MediaSet albumSet, int cacheSize) {
79         mSource = Utils.checkNotNull(albumSet);
80         mCoverItem = new MediaItem[cacheSize];
81         mData = new MediaSet[cacheSize];
82         mTotalCount = new int[cacheSize];
83         mItemVersion = new long[cacheSize];
84         mSetVersion = new long[cacheSize];
85         Arrays.fill(mItemVersion, MediaObject.INVALID_DATA_VERSION);
86         Arrays.fill(mSetVersion, MediaObject.INVALID_DATA_VERSION);
87 
88         mMainHandler = new SynchronizedHandler(activity.getGLRoot()) {
89             @Override
90             public void handleMessage(Message message) {
91                 switch (message.what) {
92                     case MSG_RUN_OBJECT:
93                         ((Runnable) message.obj).run();
94                         return;
95                     case MSG_LOAD_START:
96                         if (mLoadingListener != null) mLoadingListener.onLoadingStarted();
97                         return;
98                     case MSG_LOAD_FINISH:
99                         if (mLoadingListener != null) mLoadingListener.onLoadingFinished();
100                         return;
101                 }
102             }
103         };
104     }
105 
pause()106     public void pause() {
107         mReloadTask.terminate();
108         mReloadTask = null;
109         mSource.removeContentListener(mSourceListener);
110     }
111 
resume()112     public void resume() {
113         mSource.addContentListener(mSourceListener);
114         mReloadTask = new ReloadTask();
115         mReloadTask.start();
116     }
117 
assertIsActive(int index)118     private void assertIsActive(int index) {
119         if (index < mActiveStart && index >= mActiveEnd) {
120             throw new IllegalArgumentException(String.format(
121                     "%s not in (%s, %s)", index, mActiveStart, mActiveEnd));
122         }
123     }
124 
getMediaSet(int index)125     public MediaSet getMediaSet(int index) {
126         assertIsActive(index);
127         return mData[index % mData.length];
128     }
129 
getCoverItem(int index)130     public MediaItem getCoverItem(int index) {
131         assertIsActive(index);
132         return mCoverItem[index % mCoverItem.length];
133     }
134 
getTotalCount(int index)135     public int getTotalCount(int index) {
136         assertIsActive(index);
137         return mTotalCount[index % mTotalCount.length];
138     }
139 
getActiveStart()140     public int getActiveStart() {
141         return mActiveStart;
142     }
143 
isActive(int index)144     public boolean isActive(int index) {
145         return index >= mActiveStart && index < mActiveEnd;
146     }
147 
size()148     public int size() {
149         return mSize;
150     }
151 
clearSlot(int slotIndex)152     private void clearSlot(int slotIndex) {
153         mData[slotIndex] = null;
154         mCoverItem[slotIndex] = null;
155         mTotalCount[slotIndex] = 0;
156         mItemVersion[slotIndex] = MediaObject.INVALID_DATA_VERSION;
157         mSetVersion[slotIndex] = MediaObject.INVALID_DATA_VERSION;
158     }
159 
setContentWindow(int contentStart, int contentEnd)160     private void setContentWindow(int contentStart, int contentEnd) {
161         if (contentStart == mContentStart && contentEnd == mContentEnd) return;
162         int length = mCoverItem.length;
163 
164         int start = this.mContentStart;
165         int end = this.mContentEnd;
166 
167         mContentStart = contentStart;
168         mContentEnd = contentEnd;
169 
170         if (contentStart >= end || start >= contentEnd) {
171             for (int i = start, n = end; i < n; ++i) {
172                 clearSlot(i % length);
173             }
174         } else {
175             for (int i = start; i < contentStart; ++i) {
176                 clearSlot(i % length);
177             }
178             for (int i = contentEnd, n = end; i < n; ++i) {
179                 clearSlot(i % length);
180             }
181         }
182         mReloadTask.notifyDirty();
183     }
184 
setActiveWindow(int start, int end)185     public void setActiveWindow(int start, int end) {
186         if (start == mActiveStart && end == mActiveEnd) return;
187 
188         Utils.assertTrue(start <= end
189                 && end - start <= mCoverItem.length && end <= mSize);
190 
191         mActiveStart = start;
192         mActiveEnd = end;
193 
194         int length = mCoverItem.length;
195         // If no data is visible, keep the cache content
196         if (start == end) return;
197 
198         int contentStart = Utils.clamp((start + end) / 2 - length / 2,
199                 0, Math.max(0, mSize - length));
200         int contentEnd = Math.min(contentStart + length, mSize);
201         if (mContentStart > start || mContentEnd < end
202                 || Math.abs(contentStart - mContentStart) > MIN_LOAD_COUNT) {
203             setContentWindow(contentStart, contentEnd);
204         }
205     }
206 
207     private class MySourceListener implements ContentListener {
onContentDirty()208         public void onContentDirty() {
209             mReloadTask.notifyDirty();
210         }
211     }
212 
setModelListener(DataListener listener)213     public void setModelListener(DataListener listener) {
214         mDataListener = listener;
215     }
216 
setLoadingListener(LoadingListener listener)217     public void setLoadingListener(LoadingListener listener) {
218         mLoadingListener = listener;
219     }
220 
221     private static class UpdateInfo {
222         public long version;
223         public int index;
224 
225         public int size;
226         public MediaSet item;
227         public MediaItem cover;
228         public int totalCount;
229     }
230 
231     private class GetUpdateInfo implements Callable<UpdateInfo> {
232 
233         private final long mVersion;
234 
GetUpdateInfo(long version)235         public GetUpdateInfo(long version) {
236             mVersion = version;
237         }
238 
getInvalidIndex(long version)239         private int getInvalidIndex(long version) {
240             long setVersion[] = mSetVersion;
241             int length = setVersion.length;
242             for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
243                 int index = i % length;
244                 if (setVersion[i % length] != version) return i;
245             }
246             return INDEX_NONE;
247         }
248 
249         @Override
call()250         public UpdateInfo call() throws Exception {
251             int index = getInvalidIndex(mVersion);
252             if (index == INDEX_NONE && mSourceVersion == mVersion) return null;
253             UpdateInfo info = new UpdateInfo();
254             info.version = mSourceVersion;
255             info.index = index;
256             info.size = mSize;
257             return info;
258         }
259     }
260 
261     private class UpdateContent implements Callable<Void> {
262         private final UpdateInfo mUpdateInfo;
263 
UpdateContent(UpdateInfo info)264         public UpdateContent(UpdateInfo info) {
265             mUpdateInfo = info;
266         }
267 
call()268         public Void call() {
269             // Avoid notifying listeners of status change after pause
270             // Otherwise gallery will be in inconsistent state after resume.
271             if (mReloadTask == null) return null;
272             UpdateInfo info = mUpdateInfo;
273             mSourceVersion = info.version;
274             if (mSize != info.size) {
275                 mSize = info.size;
276                 if (mDataListener != null) mDataListener.onSizeChanged(mSize);
277                 if (mContentEnd > mSize) mContentEnd = mSize;
278                 if (mActiveEnd > mSize) mActiveEnd = mSize;
279             }
280             // Note: info.index could be INDEX_NONE, i.e., -1
281             if (info.index >= mContentStart && info.index < mContentEnd) {
282                 int pos = info.index % mCoverItem.length;
283                 mSetVersion[pos] = info.version;
284                 long itemVersion = info.item.getDataVersion();
285                 if (mItemVersion[pos] == itemVersion) return null;
286                 mItemVersion[pos] = itemVersion;
287                 mData[pos] = info.item;
288                 mCoverItem[pos] = info.cover;
289                 mTotalCount[pos] = info.totalCount;
290                 if (mDataListener != null
291                         && info.index >= mActiveStart && info.index < mActiveEnd) {
292                     mDataListener.onContentChanged(info.index);
293                 }
294             }
295             return null;
296         }
297     }
298 
executeAndWait(Callable<T> callable)299     private <T> T executeAndWait(Callable<T> callable) {
300         FutureTask<T> task = new FutureTask<T>(callable);
301         mMainHandler.sendMessage(
302                 mMainHandler.obtainMessage(MSG_RUN_OBJECT, task));
303         try {
304             return task.get();
305         } catch (InterruptedException e) {
306             return null;
307         } catch (ExecutionException e) {
308             throw new RuntimeException(e);
309         }
310     }
311 
312     // TODO: load active range first
313     private class ReloadTask extends Thread {
314         private volatile boolean mActive = true;
315         private volatile boolean mDirty = true;
316         private volatile boolean mIsLoading = false;
317 
updateLoading(boolean loading)318         private void updateLoading(boolean loading) {
319             if (mIsLoading == loading) return;
320             mIsLoading = loading;
321             mMainHandler.sendEmptyMessage(loading ? MSG_LOAD_START : MSG_LOAD_FINISH);
322         }
323 
324         @Override
run()325         public void run() {
326             Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
327 
328             boolean updateComplete = false;
329             while (mActive) {
330                 synchronized (this) {
331                     if (mActive && !mDirty && updateComplete) {
332                         if (!mSource.isLoading()) updateLoading(false);
333                         Utils.waitWithoutInterrupt(this);
334                         continue;
335                     }
336                 }
337                 mDirty = false;
338                 updateLoading(true);
339 
340                 long version;
341                 synchronized (DataManager.LOCK) {
342                     long start = SystemClock.uptimeMillis();
343                     version = mSource.reload();
344                     long duration = SystemClock.uptimeMillis() - start;
345                     if (duration > 20) {
346                         Log.v("DebugLoadingTime", "finish reload - " + duration);
347                     }
348                 }
349                 UpdateInfo info = executeAndWait(new GetUpdateInfo(version));
350                 updateComplete = info == null;
351                 if (updateComplete) continue;
352 
353                 synchronized (DataManager.LOCK) {
354                     if (info.version != version) {
355                         info.version = version;
356                         info.size = mSource.getSubMediaSetCount();
357 
358                         // If the size becomes smaller after reload(), we may
359                         // receive from GetUpdateInfo an index which is too
360                         // big. Because the main thread is not aware of the size
361                         // change until we call UpdateContent.
362                         if (info.index >= info.size) {
363                             info.index = INDEX_NONE;
364                         }
365                     }
366                     if (info.index != INDEX_NONE) {
367                         info.item = mSource.getSubMediaSet(info.index);
368                         if (info.item == null) continue;
369                         info.cover = info.item.getCoverMediaItem();
370                         info.totalCount = info.item.getTotalMediaItemCount();
371                     }
372                 }
373                 executeAndWait(new UpdateContent(info));
374             }
375             updateLoading(false);
376         }
377 
notifyDirty()378         public synchronized void notifyDirty() {
379             mDirty = true;
380             notifyAll();
381         }
382 
terminate()383         public synchronized void terminate() {
384             mActive = false;
385             notifyAll();
386         }
387     }
388 }
389 
390 
391