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