Home | History | Annotate | Download | only in app
      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 }
    364