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