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