Home | History | Annotate | Download | only in ui
      1 /*T
      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.ui;
     18 
     19 import android.graphics.Bitmap;
     20 import android.os.Message;
     21 
     22 import com.android.gallery3d.R;
     23 import com.android.gallery3d.app.AbstractGalleryActivity;
     24 import com.android.gallery3d.app.AlbumSetDataLoader;
     25 import com.android.gallery3d.common.Utils;
     26 import com.android.gallery3d.data.DataSourceType;
     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.data.Path;
     31 import com.android.gallery3d.glrenderer.BitmapTexture;
     32 import com.android.gallery3d.glrenderer.Texture;
     33 import com.android.gallery3d.glrenderer.TextureUploader;
     34 import com.android.gallery3d.glrenderer.TiledTexture;
     35 import com.android.gallery3d.util.Future;
     36 import com.android.gallery3d.util.FutureListener;
     37 import com.android.gallery3d.util.ThreadPool;
     38 
     39 public class AlbumSetSlidingWindow implements AlbumSetDataLoader.DataListener {
     40     private static final String TAG = "AlbumSetSlidingWindow";
     41     private static final int MSG_UPDATE_ALBUM_ENTRY = 1;
     42 
     43     public static interface Listener {
     44         public void onSizeChanged(int size);
     45         public void onContentChanged();
     46     }
     47 
     48     private final AlbumSetDataLoader mSource;
     49     private int mSize;
     50 
     51     private int mContentStart = 0;
     52     private int mContentEnd = 0;
     53 
     54     private int mActiveStart = 0;
     55     private int mActiveEnd = 0;
     56 
     57     private Listener mListener;
     58 
     59     private final AlbumSetEntry mData[];
     60     private final SynchronizedHandler mHandler;
     61     private final ThreadPool mThreadPool;
     62     private final AlbumLabelMaker mLabelMaker;
     63     private final String mLoadingText;
     64 
     65     private final TiledTexture.Uploader mContentUploader;
     66     private final TextureUploader mLabelUploader;
     67 
     68     private int mActiveRequestCount = 0;
     69     private boolean mIsActive = false;
     70     private BitmapTexture mLoadingLabel;
     71 
     72     private int mSlotWidth;
     73 
     74     public static class AlbumSetEntry {
     75         public MediaSet album;
     76         public MediaItem coverItem;
     77         public Texture content;
     78         public BitmapTexture labelTexture;
     79         public TiledTexture bitmapTexture;
     80         public Path setPath;
     81         public String title;
     82         public int totalCount;
     83         public int sourceType;
     84         public int cacheFlag;
     85         public int cacheStatus;
     86         public int rotation;
     87         public boolean isWaitLoadingDisplayed;
     88         public long setDataVersion;
     89         public long coverDataVersion;
     90         private BitmapLoader labelLoader;
     91         private BitmapLoader coverLoader;
     92     }
     93 
     94     public AlbumSetSlidingWindow(AbstractGalleryActivity activity,
     95             AlbumSetDataLoader source, AlbumSetSlotRenderer.LabelSpec labelSpec, int cacheSize) {
     96         source.setModelListener(this);
     97         mSource = source;
     98         mData = new AlbumSetEntry[cacheSize];
     99         mSize = source.size();
    100         mThreadPool = activity.getThreadPool();
    101 
    102         mLabelMaker = new AlbumLabelMaker(activity.getAndroidContext(), labelSpec);
    103         mLoadingText = activity.getAndroidContext().getString(R.string.loading);
    104         mContentUploader = new TiledTexture.Uploader(activity.getGLRoot());
    105         mLabelUploader = new TextureUploader(activity.getGLRoot());
    106 
    107         mHandler = new SynchronizedHandler(activity.getGLRoot()) {
    108             @Override
    109             public void handleMessage(Message message) {
    110                 Utils.assertTrue(message.what == MSG_UPDATE_ALBUM_ENTRY);
    111                 ((EntryUpdater) message.obj).updateEntry();
    112             }
    113         };
    114     }
    115 
    116     public void setListener(Listener listener) {
    117         mListener = listener;
    118     }
    119 
    120     public AlbumSetEntry get(int slotIndex) {
    121         if (!isActiveSlot(slotIndex)) {
    122             Utils.fail("invalid slot: %s outsides (%s, %s)",
    123                     slotIndex, mActiveStart, mActiveEnd);
    124         }
    125         return mData[slotIndex % mData.length];
    126     }
    127 
    128     public int size() {
    129         return mSize;
    130     }
    131 
    132     public boolean isActiveSlot(int slotIndex) {
    133         return slotIndex >= mActiveStart && slotIndex < mActiveEnd;
    134     }
    135 
    136     private void setContentWindow(int contentStart, int contentEnd) {
    137         if (contentStart == mContentStart && contentEnd == mContentEnd) return;
    138 
    139         if (contentStart >= mContentEnd || mContentStart >= contentEnd) {
    140             for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
    141                 freeSlotContent(i);
    142             }
    143             mSource.setActiveWindow(contentStart, contentEnd);
    144             for (int i = contentStart; i < contentEnd; ++i) {
    145                 prepareSlotContent(i);
    146             }
    147         } else {
    148             for (int i = mContentStart; i < contentStart; ++i) {
    149                 freeSlotContent(i);
    150             }
    151             for (int i = contentEnd, n = mContentEnd; i < n; ++i) {
    152                 freeSlotContent(i);
    153             }
    154             mSource.setActiveWindow(contentStart, contentEnd);
    155             for (int i = contentStart, n = mContentStart; i < n; ++i) {
    156                 prepareSlotContent(i);
    157             }
    158             for (int i = mContentEnd; i < contentEnd; ++i) {
    159                 prepareSlotContent(i);
    160             }
    161         }
    162 
    163         mContentStart = contentStart;
    164         mContentEnd = contentEnd;
    165     }
    166 
    167     public void setActiveWindow(int start, int end) {
    168         if (!(start <= end && end - start <= mData.length && end <= mSize)) {
    169             Utils.fail("start = %s, end = %s, length = %s, size = %s",
    170                     start, end, mData.length, mSize);
    171         }
    172 
    173         AlbumSetEntry data[] = mData;
    174         mActiveStart = start;
    175         mActiveEnd = end;
    176         int contentStart = Utils.clamp((start + end) / 2 - data.length / 2,
    177                 0, Math.max(0, mSize - data.length));
    178         int contentEnd = Math.min(contentStart + data.length, mSize);
    179         setContentWindow(contentStart, contentEnd);
    180 
    181         if (mIsActive) {
    182             updateTextureUploadQueue();
    183             updateAllImageRequests();
    184         }
    185     }
    186 
    187     // We would like to request non active slots in the following order:
    188     // Order:    8 6 4 2                   1 3 5 7
    189     //         |---------|---------------|---------|
    190     //                   |<-  active  ->|
    191     //         |<-------- cached range ----------->|
    192     private void requestNonactiveImages() {
    193         int range = Math.max(
    194                 mContentEnd - mActiveEnd, mActiveStart - mContentStart);
    195         for (int i = 0 ;i < range; ++i) {
    196             requestImagesInSlot(mActiveEnd + i);
    197             requestImagesInSlot(mActiveStart - 1 - i);
    198         }
    199     }
    200 
    201     private void cancelNonactiveImages() {
    202         int range = Math.max(
    203                 mContentEnd - mActiveEnd, mActiveStart - mContentStart);
    204         for (int i = 0 ;i < range; ++i) {
    205             cancelImagesInSlot(mActiveEnd + i);
    206             cancelImagesInSlot(mActiveStart - 1 - i);
    207         }
    208     }
    209 
    210     private void requestImagesInSlot(int slotIndex) {
    211         if (slotIndex < mContentStart || slotIndex >= mContentEnd) return;
    212         AlbumSetEntry entry = mData[slotIndex % mData.length];
    213         if (entry.coverLoader != null) entry.coverLoader.startLoad();
    214         if (entry.labelLoader != null) entry.labelLoader.startLoad();
    215     }
    216 
    217     private void cancelImagesInSlot(int slotIndex) {
    218         if (slotIndex < mContentStart || slotIndex >= mContentEnd) return;
    219         AlbumSetEntry entry = mData[slotIndex % mData.length];
    220         if (entry.coverLoader != null) entry.coverLoader.cancelLoad();
    221         if (entry.labelLoader != null) entry.labelLoader.cancelLoad();
    222     }
    223 
    224     private static long getDataVersion(MediaObject object) {
    225         return object == null
    226                 ? MediaSet.INVALID_DATA_VERSION
    227                 : object.getDataVersion();
    228     }
    229 
    230     private void freeSlotContent(int slotIndex) {
    231         AlbumSetEntry entry = mData[slotIndex % mData.length];
    232         if (entry.coverLoader != null) entry.coverLoader.recycle();
    233         if (entry.labelLoader != null) entry.labelLoader.recycle();
    234         if (entry.labelTexture != null) entry.labelTexture.recycle();
    235         if (entry.bitmapTexture != null) entry.bitmapTexture.recycle();
    236         mData[slotIndex % mData.length] = null;
    237     }
    238 
    239     private boolean isLabelChanged(
    240             AlbumSetEntry entry, String title, int totalCount, int sourceType) {
    241         return !Utils.equals(entry.title, title)
    242                 || entry.totalCount != totalCount
    243                 || entry.sourceType != sourceType;
    244     }
    245 
    246     private void updateAlbumSetEntry(AlbumSetEntry entry, int slotIndex) {
    247         MediaSet album = mSource.getMediaSet(slotIndex);
    248         MediaItem cover = mSource.getCoverItem(slotIndex);
    249         int totalCount = mSource.getTotalCount(slotIndex);
    250 
    251         entry.album = album;
    252         entry.setDataVersion = getDataVersion(album);
    253         entry.cacheFlag = identifyCacheFlag(album);
    254         entry.cacheStatus = identifyCacheStatus(album);
    255         entry.setPath = (album == null) ? null : album.getPath();
    256 
    257         String title = (album == null) ? "" : Utils.ensureNotNull(album.getName());
    258         int sourceType = DataSourceType.identifySourceType(album);
    259         if (isLabelChanged(entry, title, totalCount, sourceType)) {
    260             entry.title = title;
    261             entry.totalCount = totalCount;
    262             entry.sourceType = sourceType;
    263             if (entry.labelLoader != null) {
    264                 entry.labelLoader.recycle();
    265                 entry.labelLoader = null;
    266                 entry.labelTexture = null;
    267             }
    268             if (album != null) {
    269                 entry.labelLoader = new AlbumLabelLoader(
    270                         slotIndex, title, totalCount, sourceType);
    271             }
    272         }
    273 
    274         entry.coverItem = cover;
    275         if (getDataVersion(cover) != entry.coverDataVersion) {
    276             entry.coverDataVersion = getDataVersion(cover);
    277             entry.rotation = (cover == null) ? 0 : cover.getRotation();
    278             if (entry.coverLoader != null) {
    279                 entry.coverLoader.recycle();
    280                 entry.coverLoader = null;
    281                 entry.bitmapTexture = null;
    282                 entry.content = null;
    283             }
    284             if (cover != null) {
    285                 entry.coverLoader = new AlbumCoverLoader(slotIndex, cover);
    286             }
    287         }
    288     }
    289 
    290     private void prepareSlotContent(int slotIndex) {
    291         AlbumSetEntry entry = new AlbumSetEntry();
    292         updateAlbumSetEntry(entry, slotIndex);
    293         mData[slotIndex % mData.length] = entry;
    294     }
    295 
    296     private static boolean startLoadBitmap(BitmapLoader loader) {
    297         if (loader == null) return false;
    298         loader.startLoad();
    299         return loader.isRequestInProgress();
    300     }
    301 
    302     private void uploadBackgroundTextureInSlot(int index) {
    303         if (index < mContentStart || index >= mContentEnd) return;
    304         AlbumSetEntry entry = mData[index % mData.length];
    305         if (entry.bitmapTexture != null) {
    306             mContentUploader.addTexture(entry.bitmapTexture);
    307         }
    308         if (entry.labelTexture != null) {
    309             mLabelUploader.addBgTexture(entry.labelTexture);
    310         }
    311     }
    312 
    313     private void updateTextureUploadQueue() {
    314         if (!mIsActive) return;
    315         mContentUploader.clear();
    316         mLabelUploader.clear();
    317 
    318         // Upload foreground texture
    319         for (int i = mActiveStart, n = mActiveEnd; i < n; ++i) {
    320             AlbumSetEntry entry = mData[i % mData.length];
    321             if (entry.bitmapTexture != null) {
    322                 mContentUploader.addTexture(entry.bitmapTexture);
    323             }
    324             if (entry.labelTexture != null) {
    325                 mLabelUploader.addFgTexture(entry.labelTexture);
    326             }
    327         }
    328 
    329         // add background textures
    330         int range = Math.max(
    331                 (mContentEnd - mActiveEnd), (mActiveStart - mContentStart));
    332         for (int i = 0; i < range; ++i) {
    333             uploadBackgroundTextureInSlot(mActiveEnd + i);
    334             uploadBackgroundTextureInSlot(mActiveStart - i - 1);
    335         }
    336     }
    337 
    338     private void updateAllImageRequests() {
    339         mActiveRequestCount = 0;
    340         for (int i = mActiveStart, n = mActiveEnd; i < n; ++i) {
    341             AlbumSetEntry entry = mData[i % mData.length];
    342             if (startLoadBitmap(entry.coverLoader)) ++mActiveRequestCount;
    343             if (startLoadBitmap(entry.labelLoader)) ++mActiveRequestCount;
    344         }
    345         if (mActiveRequestCount == 0) {
    346             requestNonactiveImages();
    347         } else {
    348             cancelNonactiveImages();
    349         }
    350     }
    351 
    352     @Override
    353     public void onSizeChanged(int size) {
    354         if (mIsActive && mSize != size) {
    355             mSize = size;
    356             if (mListener != null) mListener.onSizeChanged(mSize);
    357             if (mContentEnd > mSize) mContentEnd = mSize;
    358             if (mActiveEnd > mSize) mActiveEnd = mSize;
    359         }
    360     }
    361 
    362     @Override
    363     public void onContentChanged(int index) {
    364         if (!mIsActive) {
    365             // paused, ignore slot changed event
    366             return;
    367         }
    368 
    369         // If the updated content is not cached, ignore it
    370         if (index < mContentStart || index >= mContentEnd) {
    371             Log.w(TAG, String.format(
    372                     "invalid update: %s is outside (%s, %s)",
    373                     index, mContentStart, mContentEnd) );
    374             return;
    375         }
    376 
    377         AlbumSetEntry entry = mData[index % mData.length];
    378         updateAlbumSetEntry(entry, index);
    379         updateAllImageRequests();
    380         updateTextureUploadQueue();
    381         if (mListener != null && isActiveSlot(index)) {
    382             mListener.onContentChanged();
    383         }
    384     }
    385 
    386     public BitmapTexture getLoadingTexture() {
    387         if (mLoadingLabel == null) {
    388             Bitmap bitmap = mLabelMaker.requestLabel(
    389                     mLoadingText, "", DataSourceType.TYPE_NOT_CATEGORIZED)
    390                     .run(ThreadPool.JOB_CONTEXT_STUB);
    391             mLoadingLabel = new BitmapTexture(bitmap);
    392             mLoadingLabel.setOpaque(false);
    393         }
    394         return mLoadingLabel;
    395     }
    396 
    397     public void pause() {
    398         mIsActive = false;
    399         mLabelUploader.clear();
    400         mContentUploader.clear();
    401         TiledTexture.freeResources();
    402         for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
    403             freeSlotContent(i);
    404         }
    405     }
    406 
    407     public void resume() {
    408         mIsActive = true;
    409         TiledTexture.prepareResources();
    410         for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
    411             prepareSlotContent(i);
    412         }
    413         updateAllImageRequests();
    414     }
    415 
    416     private static interface EntryUpdater {
    417         public void updateEntry();
    418     }
    419 
    420     private class AlbumCoverLoader extends BitmapLoader implements EntryUpdater {
    421         private MediaItem mMediaItem;
    422         private final int mSlotIndex;
    423 
    424         public AlbumCoverLoader(int slotIndex, MediaItem item) {
    425             mSlotIndex = slotIndex;
    426             mMediaItem = item;
    427         }
    428 
    429         @Override
    430         protected Future<Bitmap> submitBitmapTask(FutureListener<Bitmap> l) {
    431             return mThreadPool.submit(mMediaItem.requestImage(
    432                     MediaItem.TYPE_MICROTHUMBNAIL), l);
    433         }
    434 
    435         @Override
    436         protected void onLoadComplete(Bitmap bitmap) {
    437             mHandler.obtainMessage(MSG_UPDATE_ALBUM_ENTRY, this).sendToTarget();
    438         }
    439 
    440         @Override
    441         public void updateEntry() {
    442             Bitmap bitmap = getBitmap();
    443             if (bitmap == null) return; // error or recycled
    444 
    445             AlbumSetEntry entry = mData[mSlotIndex % mData.length];
    446             TiledTexture texture = new TiledTexture(bitmap);
    447             entry.bitmapTexture = texture;
    448             entry.content = texture;
    449 
    450             if (isActiveSlot(mSlotIndex)) {
    451                 mContentUploader.addTexture(texture);
    452                 --mActiveRequestCount;
    453                 if (mActiveRequestCount == 0) requestNonactiveImages();
    454                 if (mListener != null) mListener.onContentChanged();
    455             } else {
    456                 mContentUploader.addTexture(texture);
    457             }
    458         }
    459     }
    460 
    461     private static int identifyCacheFlag(MediaSet set) {
    462         if (set == null || (set.getSupportedOperations()
    463                 & MediaSet.SUPPORT_CACHE) == 0) {
    464             return MediaSet.CACHE_FLAG_NO;
    465         }
    466 
    467         return set.getCacheFlag();
    468     }
    469 
    470     private static int identifyCacheStatus(MediaSet set) {
    471         if (set == null || (set.getSupportedOperations()
    472                 & MediaSet.SUPPORT_CACHE) == 0) {
    473             return MediaSet.CACHE_STATUS_NOT_CACHED;
    474         }
    475 
    476         return set.getCacheStatus();
    477     }
    478 
    479     private class AlbumLabelLoader extends BitmapLoader implements EntryUpdater {
    480         private final int mSlotIndex;
    481         private final String mTitle;
    482         private final int mTotalCount;
    483         private final int mSourceType;
    484 
    485         public AlbumLabelLoader(
    486                 int slotIndex, String title, int totalCount, int sourceType) {
    487             mSlotIndex = slotIndex;
    488             mTitle = title;
    489             mTotalCount = totalCount;
    490             mSourceType = sourceType;
    491         }
    492 
    493         @Override
    494         protected Future<Bitmap> submitBitmapTask(FutureListener<Bitmap> l) {
    495             return mThreadPool.submit(mLabelMaker.requestLabel(
    496                     mTitle, String.valueOf(mTotalCount), mSourceType), l);
    497         }
    498 
    499         @Override
    500         protected void onLoadComplete(Bitmap bitmap) {
    501             mHandler.obtainMessage(MSG_UPDATE_ALBUM_ENTRY, this).sendToTarget();
    502         }
    503 
    504         @Override
    505         public void updateEntry() {
    506             Bitmap bitmap = getBitmap();
    507             if (bitmap == null) return; // Error or recycled
    508 
    509             AlbumSetEntry entry = mData[mSlotIndex % mData.length];
    510             BitmapTexture texture = new BitmapTexture(bitmap);
    511             texture.setOpaque(false);
    512             entry.labelTexture = texture;
    513 
    514             if (isActiveSlot(mSlotIndex)) {
    515                 mLabelUploader.addFgTexture(texture);
    516                 --mActiveRequestCount;
    517                 if (mActiveRequestCount == 0) requestNonactiveImages();
    518                 if (mListener != null) mListener.onContentChanged();
    519             } else {
    520                 mLabelUploader.addBgTexture(texture);
    521             }
    522         }
    523     }
    524 
    525     public void onSlotSizeChanged(int width, int height) {
    526         if (mSlotWidth == width) return;
    527 
    528         mSlotWidth = width;
    529         mLoadingLabel = null;
    530         mLabelMaker.setLabelWidth(mSlotWidth);
    531 
    532         if (!mIsActive) return;
    533 
    534         for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
    535             AlbumSetEntry entry = mData[i % mData.length];
    536             if (entry.labelLoader != null) {
    537                 entry.labelLoader.recycle();
    538                 entry.labelLoader = null;
    539                 entry.labelTexture = null;
    540             }
    541             if (entry.album != null) {
    542                 entry.labelLoader = new AlbumLabelLoader(i,
    543                         entry.title, entry.totalCount, entry.sourceType);
    544             }
    545         }
    546         updateAllImageRequests();
    547         updateTextureUploadQueue();
    548     }
    549 }
    550