AlbumSlidingWindow.java revision b21b8e58a604f6c701245d84b141b5b87663192b
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 17package com.android.gallery3d.ui; 18 19import android.graphics.Bitmap; 20import android.os.Message; 21 22import com.android.gallery3d.app.AbstractGalleryActivity; 23import com.android.gallery3d.app.AlbumDataLoader; 24import com.android.gallery3d.common.Utils; 25import com.android.gallery3d.data.BitmapPool; 26import com.android.gallery3d.data.MediaItem; 27import com.android.gallery3d.data.Path; 28import com.android.gallery3d.util.Future; 29import com.android.gallery3d.util.FutureListener; 30import com.android.gallery3d.util.GalleryUtils; 31import com.android.gallery3d.util.JobLimiter; 32 33public class AlbumSlidingWindow implements AlbumDataLoader.DataListener { 34 @SuppressWarnings("unused") 35 private static final String TAG = "AlbumSlidingWindow"; 36 37 private static final int MSG_UPDATE_ENTRY = 0; 38 private static final int JOB_LIMIT = 2; 39 40 public static interface Listener { 41 public void onSizeChanged(int size); 42 public void onContentChanged(); 43 } 44 45 public static class AlbumEntry { 46 public MediaItem item; 47 public Path path; 48 public boolean isPanorama; 49 public int rotation; 50 public int mediaType; 51 public boolean isWaitDisplayed; 52 public BitmapTexture bitmapTexture; 53 public Texture content; 54 private BitmapLoader contentLoader; 55 } 56 57 private final AlbumDataLoader mSource; 58 private final AlbumEntry mData[]; 59 private final SynchronizedHandler mHandler; 60 private final JobLimiter mThreadPool; 61 private final TextureUploader mTextureUploader; 62 63 private int mSize; 64 65 private int mContentStart = 0; 66 private int mContentEnd = 0; 67 68 private int mActiveStart = 0; 69 private int mActiveEnd = 0; 70 71 private Listener mListener; 72 73 private int mActiveRequestCount = 0; 74 private boolean mIsActive = false; 75 76 public AlbumSlidingWindow(AbstractGalleryActivity activity, 77 AlbumDataLoader source, int cacheSize) { 78 source.setDataListener(this); 79 mSource = source; 80 mData = new AlbumEntry[cacheSize]; 81 mSize = source.size(); 82 83 mHandler = new SynchronizedHandler(activity.getGLRoot()) { 84 @Override 85 public void handleMessage(Message message) { 86 Utils.assertTrue(message.what == MSG_UPDATE_ENTRY); 87 ((ThumbnailLoader) message.obj).updateEntry(); 88 } 89 }; 90 91 mThreadPool = new JobLimiter(activity.getThreadPool(), JOB_LIMIT); 92 mTextureUploader = new TextureUploader(activity.getGLRoot()); 93 } 94 95 public void setListener(Listener listener) { 96 mListener = listener; 97 } 98 99 public AlbumEntry get(int slotIndex) { 100 if (!isActiveSlot(slotIndex)) { 101 Utils.fail("invalid slot: %s outsides (%s, %s)", 102 slotIndex, mActiveStart, mActiveEnd); 103 } 104 return mData[slotIndex % mData.length]; 105 } 106 107 public boolean isActiveSlot(int slotIndex) { 108 return slotIndex >= mActiveStart && slotIndex < mActiveEnd; 109 } 110 111 private void setContentWindow(int contentStart, int contentEnd) { 112 if (contentStart == mContentStart && contentEnd == mContentEnd) return; 113 114 if (!mIsActive) { 115 mContentStart = contentStart; 116 mContentEnd = contentEnd; 117 mSource.setActiveWindow(contentStart, contentEnd); 118 return; 119 } 120 121 if (contentStart >= mContentEnd || mContentStart >= contentEnd) { 122 for (int i = mContentStart, n = mContentEnd; i < n; ++i) { 123 freeSlotContent(i); 124 } 125 mSource.setActiveWindow(contentStart, contentEnd); 126 for (int i = contentStart; i < contentEnd; ++i) { 127 prepareSlotContent(i); 128 } 129 } else { 130 for (int i = mContentStart; i < contentStart; ++i) { 131 freeSlotContent(i); 132 } 133 for (int i = contentEnd, n = mContentEnd; i < n; ++i) { 134 freeSlotContent(i); 135 } 136 mSource.setActiveWindow(contentStart, contentEnd); 137 for (int i = contentStart, n = mContentStart; i < n; ++i) { 138 prepareSlotContent(i); 139 } 140 for (int i = mContentEnd; i < contentEnd; ++i) { 141 prepareSlotContent(i); 142 } 143 } 144 145 mContentStart = contentStart; 146 mContentEnd = contentEnd; 147 } 148 149 public void setActiveWindow(int start, int end) { 150 if (!(start <= end && end - start <= mData.length && end <= mSize)) { 151 Utils.fail("%s, %s, %s, %s", start, end, mData.length, mSize); 152 } 153 AlbumEntry data[] = mData; 154 155 mActiveStart = start; 156 mActiveEnd = end; 157 158 int contentStart = Utils.clamp((start + end) / 2 - data.length / 2, 159 0, Math.max(0, mSize - data.length)); 160 int contentEnd = Math.min(contentStart + data.length, mSize); 161 setContentWindow(contentStart, contentEnd); 162 updateTextureUploadQueue(); 163 if (mIsActive) updateAllImageRequests(); 164 } 165 166 private void uploadBgTextureInSlot(int index) { 167 if (index < mContentEnd && index >= mContentStart) { 168 AlbumEntry entry = mData[index % mData.length]; 169 if (entry.bitmapTexture != null) { 170 mTextureUploader.addBgTexture(entry.bitmapTexture); 171 } 172 } 173 } 174 175 private void updateTextureUploadQueue() { 176 if (!mIsActive) return; 177 mTextureUploader.clear(); 178 179 // add foreground textures 180 for (int i = mActiveStart, n = mActiveEnd; i < n; ++i) { 181 AlbumEntry entry = mData[i % mData.length]; 182 if (entry.bitmapTexture != null) { 183 mTextureUploader.addFgTexture(entry.bitmapTexture); 184 } 185 } 186 187 // add background textures 188 int range = Math.max( 189 (mContentEnd - mActiveEnd), (mActiveStart - mContentStart)); 190 for (int i = 0; i < range; ++i) { 191 uploadBgTextureInSlot(mActiveEnd + i); 192 uploadBgTextureInSlot(mActiveStart - i - 1); 193 } 194 } 195 196 // We would like to request non active slots in the following order: 197 // Order: 8 6 4 2 1 3 5 7 198 // |---------|---------------|---------| 199 // |<- active ->| 200 // |<-------- cached range ----------->| 201 private void requestNonactiveImages() { 202 int range = Math.max( 203 (mContentEnd - mActiveEnd), (mActiveStart - mContentStart)); 204 for (int i = 0 ;i < range; ++i) { 205 requestSlotImage(mActiveEnd + i); 206 requestSlotImage(mActiveStart - 1 - i); 207 } 208 } 209 210 // return whether the request is in progress or not 211 private boolean requestSlotImage(int slotIndex) { 212 if (slotIndex < mContentStart || slotIndex >= mContentEnd) return false; 213 AlbumEntry entry = mData[slotIndex % mData.length]; 214 if (entry.content != null || entry.item == null) return false; 215 216 entry.contentLoader.startLoad(); 217 return entry.contentLoader.isRequestInProgress(); 218 } 219 220 private void cancelNonactiveImages() { 221 int range = Math.max( 222 (mContentEnd - mActiveEnd), (mActiveStart - mContentStart)); 223 for (int i = 0 ;i < range; ++i) { 224 cancelSlotImage(mActiveEnd + i); 225 cancelSlotImage(mActiveStart - 1 - i); 226 } 227 } 228 229 private void cancelSlotImage(int slotIndex) { 230 if (slotIndex < mContentStart || slotIndex >= mContentEnd) return; 231 AlbumEntry item = mData[slotIndex % mData.length]; 232 if (item.contentLoader != null) item.contentLoader.cancelLoad(); 233 } 234 235 private void freeSlotContent(int slotIndex) { 236 AlbumEntry data[] = mData; 237 int index = slotIndex % data.length; 238 AlbumEntry entry = data[index]; 239 if (entry.contentLoader != null) entry.contentLoader.recycle(); 240 if (entry.bitmapTexture != null) entry.bitmapTexture.recycle(); 241 data[index] = null; 242 } 243 244 private void prepareSlotContent(int slotIndex) { 245 AlbumEntry entry = new AlbumEntry(); 246 MediaItem item = mSource.get(slotIndex); // item could be null; 247 entry.item = item; 248 entry.isPanorama = GalleryUtils.isPanorama(entry.item); 249 entry.mediaType = (item == null) 250 ? MediaItem.MEDIA_TYPE_UNKNOWN 251 : entry.item.getMediaType(); 252 entry.path = (item == null) ? null : item.getPath(); 253 entry.rotation = (item == null) ? 0 : item.getRotation(); 254 entry.contentLoader = new ThumbnailLoader(slotIndex, entry.item); 255 mData[slotIndex % mData.length] = entry; 256 } 257 258 private void updateAllImageRequests() { 259 mActiveRequestCount = 0; 260 for (int i = mActiveStart, n = mActiveEnd; i < n; ++i) { 261 if (requestSlotImage(i)) ++mActiveRequestCount; 262 } 263 if (mActiveRequestCount == 0) { 264 requestNonactiveImages(); 265 } else { 266 cancelNonactiveImages(); 267 } 268 } 269 270 private class ThumbnailLoader extends BitmapLoader { 271 private final int mSlotIndex; 272 private final MediaItem mItem; 273 274 public ThumbnailLoader(int slotIndex, MediaItem item) { 275 mSlotIndex = slotIndex; 276 mItem = item; 277 } 278 279 @Override 280 protected void recycleBitmap(Bitmap bitmap) { 281 BitmapPool pool = MediaItem.getMicroThumbPool(); 282 if (pool != null) pool.recycle(bitmap); 283 } 284 285 @Override 286 protected Future<Bitmap> submitBitmapTask(FutureListener<Bitmap> l) { 287 return mThreadPool.submit( 288 mItem.requestImage(MediaItem.TYPE_MICROTHUMBNAIL), this); 289 } 290 291 @Override 292 protected void onLoadComplete(Bitmap bitmap) { 293 mHandler.obtainMessage(MSG_UPDATE_ENTRY, this).sendToTarget(); 294 } 295 296 public void updateEntry() { 297 Bitmap bitmap = getBitmap(); 298 if (bitmap == null) return; // error or recycled 299 300 AlbumEntry entry = mData[mSlotIndex % mData.length]; 301 entry.bitmapTexture = new BitmapTexture(bitmap); 302 entry.content = entry.bitmapTexture; 303 304 if (isActiveSlot(mSlotIndex)) { 305 mTextureUploader.addFgTexture(entry.bitmapTexture); 306 --mActiveRequestCount; 307 if (mActiveRequestCount == 0) requestNonactiveImages(); 308 if (mListener != null) mListener.onContentChanged(); 309 } else { 310 mTextureUploader.addBgTexture(entry.bitmapTexture); 311 } 312 } 313 } 314 315 @Override 316 public void onSizeChanged(int size) { 317 if (mSize != size) { 318 mSize = size; 319 if (mListener != null) mListener.onSizeChanged(mSize); 320 if (mContentEnd > mSize) mContentEnd = mSize; 321 if (mActiveEnd > mSize) mActiveEnd = mSize; 322 } 323 } 324 325 @Override 326 public void onContentChanged(int index) { 327 if (index >= mContentStart && index < mContentEnd && mIsActive) { 328 freeSlotContent(index); 329 prepareSlotContent(index); 330 updateAllImageRequests(); 331 if (mListener != null && isActiveSlot(index)) { 332 mListener.onContentChanged(); 333 } 334 } 335 } 336 337 public void resume() { 338 mIsActive = true; 339 for (int i = mContentStart, n = mContentEnd; i < n; ++i) { 340 prepareSlotContent(i); 341 } 342 updateAllImageRequests(); 343 } 344 345 public void pause() { 346 mIsActive = false; 347 mTextureUploader.clear(); 348 for (int i = mContentStart, n = mContentEnd; i < n; ++i) { 349 freeSlotContent(i); 350 } 351 } 352} 353