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