AlbumSetSlidingWindow.java revision 8ef6c55bdad9a3e835ce56bdc98681434b4ac5b3
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 } 351 } 352 353 @Override 354 public void onContentChanged(int index) { 355 if (!mIsActive) { 356 // paused, ignore slot changed event 357 return; 358 } 359 360 // If the updated content is not cached, ignore it 361 if (index < mContentStart || index >= mContentEnd) { 362 Log.w(TAG, String.format( 363 "invalid update: %s is outside (%s, %s)", 364 index, mContentStart, mContentEnd) ); 365 return; 366 } 367 368 AlbumSetEntry entry = mData[index % mData.length]; 369 updateAlbumSetEntry(entry, index); 370 updateAllImageRequests(); 371 updateTextureUploadQueue(); 372 if (mListener != null && isActiveSlot(index)) { 373 mListener.onContentChanged(); 374 } 375 } 376 377 public BitmapTexture getLoadingTexture() { 378 if (mLoadingLabel == null) { 379 Bitmap bitmap = mLabelMaker.requestLabel( 380 mLoadingText, "", DataSourceType.TYPE_NOT_CATEGORIZED) 381 .run(ThreadPool.JOB_CONTEXT_STUB); 382 mLoadingLabel = new BitmapTexture(bitmap); 383 mLoadingLabel.setOpaque(false); 384 } 385 return mLoadingLabel; 386 } 387 388 public void pause() { 389 mIsActive = false; 390 for (int i = mContentStart, n = mContentEnd; i < n; ++i) { 391 freeSlotContent(i); 392 } 393 mLabelMaker.clearRecycledLabels(); 394 } 395 396 public void resume() { 397 mIsActive = true; 398 for (int i = mContentStart, n = mContentEnd; i < n; ++i) { 399 prepareSlotContent(i); 400 } 401 updateAllImageRequests(); 402 } 403 404 private static interface EntryUpdater { 405 public void updateEntry(); 406 } 407 408 private class AlbumCoverLoader extends BitmapLoader implements EntryUpdater { 409 private MediaItem mMediaItem; 410 private final int mSlotIndex; 411 412 public AlbumCoverLoader(int slotIndex, MediaItem item) { 413 mSlotIndex = slotIndex; 414 mMediaItem = item; 415 } 416 417 @Override 418 protected void recycleBitmap(Bitmap bitmap) { 419 MediaItem.getMicroThumbPool().recycle(bitmap); 420 } 421 422 @Override 423 protected Future<Bitmap> submitBitmapTask(FutureListener<Bitmap> l) { 424 return mThreadPool.submit(mMediaItem.requestImage( 425 MediaItem.TYPE_MICROTHUMBNAIL), l); 426 } 427 428 @Override 429 protected void onLoadComplete(Bitmap bitmap) { 430 mHandler.obtainMessage(MSG_UPDATE_ALBUM_ENTRY, this).sendToTarget(); 431 } 432 433 @Override 434 public void updateEntry() { 435 Bitmap bitmap = getBitmap(); 436 if (bitmap == null) return; // error or recycled 437 438 AlbumSetEntry entry = mData[mSlotIndex % mData.length]; 439 BitmapTexture texture = new BitmapTexture(bitmap); 440 entry.content = texture; 441 442 if (isActiveSlot(mSlotIndex)) { 443 mTextureUploader.addFgTexture(texture); 444 --mActiveRequestCount; 445 if (mActiveRequestCount == 0) requestNonactiveImages(); 446 if (mListener != null) mListener.onContentChanged(); 447 } else { 448 mTextureUploader.addBgTexture(texture); 449 } 450 } 451 } 452 453 private static int identifyCacheFlag(MediaSet set) { 454 if (set == null || (set.getSupportedOperations() 455 & MediaSet.SUPPORT_CACHE) == 0) { 456 return MediaSet.CACHE_FLAG_NO; 457 } 458 459 return set.getCacheFlag(); 460 } 461 462 private static int identifyCacheStatus(MediaSet set) { 463 if (set == null || (set.getSupportedOperations() 464 & MediaSet.SUPPORT_CACHE) == 0) { 465 return MediaSet.CACHE_STATUS_NOT_CACHED; 466 } 467 468 return set.getCacheStatus(); 469 } 470 471 private class AlbumLabelLoader extends BitmapLoader implements EntryUpdater { 472 private final int mSlotIndex; 473 private final String mTitle; 474 private final int mTotalCount; 475 private final int mSourceType; 476 477 public AlbumLabelLoader( 478 int slotIndex, String title, int totalCount, int sourceType) { 479 mSlotIndex = slotIndex; 480 mTitle = title; 481 mTotalCount = totalCount; 482 mSourceType = sourceType; 483 } 484 485 @Override 486 protected Future<Bitmap> submitBitmapTask(FutureListener<Bitmap> l) { 487 return mThreadPool.submit(mLabelMaker.requestLabel( 488 mTitle, String.valueOf(mTotalCount), mSourceType), l); 489 } 490 491 @Override 492 protected void recycleBitmap(Bitmap bitmap) { 493 mLabelMaker.reycleLabel(bitmap); 494 } 495 496 @Override 497 protected void onLoadComplete(Bitmap bitmap) { 498 mHandler.obtainMessage(MSG_UPDATE_ALBUM_ENTRY, this).sendToTarget(); 499 } 500 501 @Override 502 public void updateEntry() { 503 Bitmap bitmap = getBitmap(); 504 if (bitmap == null) return; // Error or recycled 505 506 AlbumSetEntry entry = mData[mSlotIndex % mData.length]; 507 BitmapTexture texture = new BitmapTexture(bitmap); 508 texture.setOpaque(false); 509 entry.label = texture; 510 511 if (isActiveSlot(mSlotIndex)) { 512 mTextureUploader.addFgTexture(texture); 513 --mActiveRequestCount; 514 if (mActiveRequestCount == 0) requestNonactiveImages(); 515 if (mListener != null) mListener.onContentChanged(); 516 } else { 517 mTextureUploader.addBgTexture(texture); 518 } 519 } 520 } 521 522 public void onSlotSizeChanged(int width, int height) { 523 if (mSlotWidth == width) return; 524 525 mSlotWidth = width; 526 mLoadingLabel = null; 527 mLabelMaker.setLabelWidth(mSlotWidth); 528 529 if (!mIsActive) return; 530 531 for (int i = mContentStart, n = mContentEnd; i < n; ++i) { 532 AlbumSetEntry entry = mData[i % mData.length]; 533 if (entry.labelLoader != null) { 534 entry.labelLoader.recycle(); 535 entry.labelLoader = null; 536 entry.label = null; 537 } 538 if (entry.album != null) { 539 entry.labelLoader = new AlbumLabelLoader(i, 540 entry.title, entry.totalCount, entry.sourceType); 541 } 542 } 543 updateAllImageRequests(); 544 updateTextureUploadQueue(); 545 } 546} 547