AlbumSetSlidingWindow.java revision 6bb8b2eb8695c042798f0eb798032cd30d642a65
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.graphics.Color; 21import android.os.Message; 22import android.util.FloatMath; 23 24import com.android.gallery3d.R; 25import com.android.gallery3d.app.GalleryActivity; 26import com.android.gallery3d.common.Utils; 27import com.android.gallery3d.data.MediaItem; 28import com.android.gallery3d.data.MediaSet; 29import com.android.gallery3d.data.Path; 30import com.android.gallery3d.ui.AlbumSetView.AlbumSetItem; 31import com.android.gallery3d.util.Future; 32import com.android.gallery3d.util.FutureListener; 33import com.android.gallery3d.util.GalleryUtils; 34import com.android.gallery3d.util.MediaSetUtils; 35import com.android.gallery3d.util.ThreadPool; 36 37public class AlbumSetSlidingWindow implements AlbumSetView.ModelListener { 38 private static final String TAG = "GallerySlidingWindow"; 39 private static final int MSG_LOAD_BITMAP_DONE = 0; 40 private static final int PLACEHOLDER_COLOR = 0xFF222222; 41 42 public static interface Listener { 43 public void onSizeChanged(int size); 44 public void onContentInvalidated(); 45 public void onWindowContentChanged( 46 int slot, AlbumSetItem old, AlbumSetItem update); 47 } 48 49 private final AlbumSetView.Model mSource; 50 private int mSize; 51 private final AlbumSetView.LabelSpec mLabelSpec; 52 53 private int mContentStart = 0; 54 private int mContentEnd = 0; 55 56 private int mActiveStart = 0; 57 private int mActiveEnd = 0; 58 59 private Listener mListener; 60 61 private final MyAlbumSetItem mData[]; 62 private SelectionDrawer mSelectionDrawer; 63 private final ColorTexture mWaitLoadingTexture; 64 65 private final SynchronizedHandler mHandler; 66 private final ThreadPool mThreadPool; 67 68 private int mActiveRequestCount = 0; 69 private final String mLoadingLabel; 70 private boolean mIsActive = false; 71 72 private static class MyAlbumSetItem extends AlbumSetItem { 73 public Path setPath; 74 public int sourceType; 75 public int cacheFlag; 76 public int cacheStatus; 77 } 78 79 public AlbumSetSlidingWindow(GalleryActivity activity, 80 AlbumSetView.LabelSpec labelSpec, SelectionDrawer drawer, 81 AlbumSetView.Model source, int cacheSize) { 82 source.setModelListener(this); 83 mLabelSpec = labelSpec; 84 mLoadingLabel = activity.getAndroidContext().getString(R.string.loading); 85 mSource = source; 86 mSelectionDrawer = drawer; 87 mData = new MyAlbumSetItem[cacheSize]; 88 mSize = source.size(); 89 90 mWaitLoadingTexture = new ColorTexture(PLACEHOLDER_COLOR); 91 mWaitLoadingTexture.setSize(1, 1); 92 93 mHandler = new SynchronizedHandler(activity.getGLRoot()) { 94 @Override 95 public void handleMessage(Message message) { 96 Utils.assertTrue(message.what == MSG_LOAD_BITMAP_DONE); 97 ((GalleryDisplayItem) message.obj).onLoadBitmapDone(); 98 } 99 }; 100 101 mThreadPool = activity.getThreadPool(); 102 } 103 104 public void setSelectionDrawer(SelectionDrawer drawer) { 105 mSelectionDrawer = drawer; 106 } 107 108 public void setListener(Listener listener) { 109 mListener = listener; 110 } 111 112 public AlbumSetItem get(int slotIndex) { 113 Utils.assertTrue(isActiveSlot(slotIndex), 114 "invalid slot: %s outsides (%s, %s)", 115 slotIndex, mActiveStart, mActiveEnd); 116 return mData[slotIndex % mData.length]; 117 } 118 119 public int size() { 120 return mSize; 121 } 122 123 public boolean isActiveSlot(int slotIndex) { 124 return slotIndex >= mActiveStart && slotIndex < mActiveEnd; 125 } 126 127 private void setContentWindow(int contentStart, int contentEnd) { 128 if (contentStart == mContentStart && contentEnd == mContentEnd) return; 129 130 if (contentStart >= mContentEnd || mContentStart >= contentEnd) { 131 for (int i = mContentStart, n = mContentEnd; i < n; ++i) { 132 freeSlotContent(i); 133 } 134 mSource.setActiveWindow(contentStart, contentEnd); 135 for (int i = contentStart; i < contentEnd; ++i) { 136 prepareSlotContent(i); 137 } 138 } else { 139 for (int i = mContentStart; i < contentStart; ++i) { 140 freeSlotContent(i); 141 } 142 for (int i = contentEnd, n = mContentEnd; i < n; ++i) { 143 freeSlotContent(i); 144 } 145 mSource.setActiveWindow(contentStart, contentEnd); 146 for (int i = contentStart, n = mContentStart; i < n; ++i) { 147 prepareSlotContent(i); 148 } 149 for (int i = mContentEnd; i < contentEnd; ++i) { 150 prepareSlotContent(i); 151 } 152 } 153 154 mContentStart = contentStart; 155 mContentEnd = contentEnd; 156 } 157 158 public void setActiveWindow(int start, int end) { 159 Utils.assertTrue( 160 start <= end && end - start <= mData.length && end <= mSize, 161 "start = %s, end = %s, length = %s, size = %s", 162 start, end, mData.length, mSize); 163 164 AlbumSetItem data[] = mData; 165 166 mActiveStart = start; 167 mActiveEnd = end; 168 169 int contentStart = Utils.clamp((start + end) / 2 - data.length / 2, 170 0, Math.max(0, mSize - data.length)); 171 int contentEnd = Math.min(contentStart + data.length, mSize); 172 setContentWindow(contentStart, contentEnd); 173 if (mIsActive) updateAllImageRequests(); 174 } 175 176 // We would like to request non active slots in the following order: 177 // Order: 8 6 4 2 1 3 5 7 178 // |---------|---------------|---------| 179 // |<- active ->| 180 // |<-------- cached range ----------->| 181 private void requestNonactiveImages() { 182 int range = Math.max( 183 mContentEnd - mActiveEnd, mActiveStart - mContentStart); 184 for (int i = 0 ;i < range; ++i) { 185 requestImagesInSlot(mActiveEnd + i); 186 requestImagesInSlot(mActiveStart - 1 - i); 187 } 188 } 189 190 private void cancelNonactiveImages() { 191 int range = Math.max( 192 mContentEnd - mActiveEnd, mActiveStart - mContentStart); 193 for (int i = 0 ;i < range; ++i) { 194 cancelImagesInSlot(mActiveEnd + i); 195 cancelImagesInSlot(mActiveStart - 1 - i); 196 } 197 } 198 199 private void requestImagesInSlot(int slotIndex) { 200 if (slotIndex < mContentStart || slotIndex >= mContentEnd) return; 201 AlbumSetItem items = mData[slotIndex % mData.length]; 202 for (DisplayItem item : items.covers) { 203 ((GalleryDisplayItem) item).requestImage(); 204 } 205 } 206 207 private void cancelImagesInSlot(int slotIndex) { 208 if (slotIndex < mContentStart || slotIndex >= mContentEnd) return; 209 AlbumSetItem items = mData[slotIndex % mData.length]; 210 for (DisplayItem item : items.covers) { 211 ((GalleryDisplayItem) item).cancelImageRequest(); 212 } 213 } 214 215 private void freeSlotContent(int slotIndex) { 216 AlbumSetItem data[] = mData; 217 int index = slotIndex % data.length; 218 AlbumSetItem original = data[index]; 219 if (original != null) { 220 data[index] = null; 221 for (DisplayItem item : original.covers) { 222 ((GalleryDisplayItem) item).recycle(); 223 } 224 } 225 } 226 227 private long getMediaSetDataVersion(MediaSet set) { 228 return set == null 229 ? MediaSet.INVALID_DATA_VERSION 230 : set.getDataVersion(); 231 } 232 233 private void prepareSlotContent(int slotIndex) { 234 MediaSet set = mSource.getMediaSet(slotIndex); 235 236 MyAlbumSetItem item = new MyAlbumSetItem(); 237 MediaItem[] coverItems = mSource.getCoverItems(slotIndex); 238 item.covers = new GalleryDisplayItem[coverItems.length]; 239 item.sourceType = identifySourceType(set); 240 item.cacheFlag = identifyCacheFlag(set); 241 item.cacheStatus = identifyCacheStatus(set); 242 item.setPath = set == null ? null : set.getPath(); 243 244 for (int i = 0; i < coverItems.length; ++i) { 245 item.covers[i] = new GalleryDisplayItem(slotIndex, i, coverItems[i]); 246 } 247 item.labelItem = new LabelDisplayItem(slotIndex); 248 item.setDataVersion = getMediaSetDataVersion(set); 249 mData[slotIndex % mData.length] = item; 250 } 251 252 private boolean isCoverItemsChanged(int slotIndex) { 253 AlbumSetItem original = mData[slotIndex % mData.length]; 254 if (original == null) return true; 255 MediaItem[] coverItems = mSource.getCoverItems(slotIndex); 256 257 if (original.covers.length != coverItems.length) return true; 258 for (int i = 0, n = coverItems.length; i < n; ++i) { 259 GalleryDisplayItem g = (GalleryDisplayItem) original.covers[i]; 260 if (g.mDataVersion != coverItems[i].getDataVersion()) return true; 261 } 262 return false; 263 } 264 265 private void updateSlotContent(final int slotIndex) { 266 267 MyAlbumSetItem data[] = mData; 268 int pos = slotIndex % data.length; 269 MyAlbumSetItem original = data[pos]; 270 271 if (!isCoverItemsChanged(slotIndex)) { 272 MediaSet set = mSource.getMediaSet(slotIndex); 273 original.sourceType = identifySourceType(set); 274 original.cacheFlag = identifyCacheFlag(set); 275 original.cacheStatus = identifyCacheStatus(set); 276 original.setPath = set == null ? null : set.getPath(); 277 ((LabelDisplayItem) original.labelItem).updateContent(); 278 if (mListener != null) mListener.onContentInvalidated(); 279 return; 280 } 281 282 prepareSlotContent(slotIndex); 283 AlbumSetItem update = data[pos]; 284 285 if (mListener != null && isActiveSlot(slotIndex)) { 286 mListener.onWindowContentChanged(slotIndex, original, update); 287 } 288 if (original != null) { 289 for (DisplayItem item : original.covers) { 290 ((GalleryDisplayItem) item).recycle(); 291 } 292 } 293 } 294 295 private void notifySlotChanged(int slotIndex) { 296 // If the updated content is not cached, ignore it 297 if (slotIndex < mContentStart || slotIndex >= mContentEnd) { 298 Log.w(TAG, String.format( 299 "invalid update: %s is outside (%s, %s)", 300 slotIndex, mContentStart, mContentEnd) ); 301 return; 302 } 303 updateSlotContent(slotIndex); 304 boolean isActiveSlot = isActiveSlot(slotIndex); 305 if (mActiveRequestCount == 0 || isActiveSlot) { 306 for (DisplayItem item : mData[slotIndex % mData.length].covers) { 307 GalleryDisplayItem galleryItem = (GalleryDisplayItem) item; 308 galleryItem.requestImage(); 309 if (isActiveSlot && galleryItem.isRequestInProgress()) { 310 ++mActiveRequestCount; 311 } 312 } 313 } 314 } 315 316 private void updateAllImageRequests() { 317 mActiveRequestCount = 0; 318 for (int i = mActiveStart, n = mActiveEnd; i < n; ++i) { 319 for (DisplayItem item : mData[i % mData.length].covers) { 320 GalleryDisplayItem coverItem = (GalleryDisplayItem) item; 321 coverItem.requestImage(); 322 if (coverItem.isRequestInProgress()) ++mActiveRequestCount; 323 } 324 } 325 if (mActiveRequestCount == 0) { 326 requestNonactiveImages(); 327 } else { 328 cancelNonactiveImages(); 329 } 330 } 331 332 private class GalleryDisplayItem extends AbstractDisplayItem 333 implements FutureListener<Bitmap> { 334 private Future<Bitmap> mFuture; 335 private final int mSlotIndex; 336 private final int mCoverIndex; 337 private final int mMediaType; 338 private Texture mContent; 339 private final long mDataVersion; 340 private final boolean mIsPanorama; 341 private boolean mWaitLoadingDisplayed; 342 343 public GalleryDisplayItem(int slotIndex, int coverIndex, MediaItem item) { 344 super(item); 345 mSlotIndex = slotIndex; 346 mCoverIndex = coverIndex; 347 mMediaType = item.getMediaType(); 348 mDataVersion = item.getDataVersion(); 349 mIsPanorama = GalleryUtils.isPanorama(item); 350 updateContent(mWaitLoadingTexture); 351 } 352 353 @Override 354 protected void onBitmapAvailable(Bitmap bitmap) { 355 if (isActiveSlot(mSlotIndex)) { 356 --mActiveRequestCount; 357 if (mActiveRequestCount == 0) requestNonactiveImages(); 358 } 359 if (bitmap != null) { 360 BitmapTexture texture = new BitmapTexture(bitmap, true); 361 texture.setThrottled(true); 362 if (mWaitLoadingDisplayed) { 363 updateContent(new FadeInTexture(PLACEHOLDER_COLOR, texture)); 364 } else { 365 updateContent(texture); 366 } 367 if (mListener != null) mListener.onContentInvalidated(); 368 } 369 } 370 371 private void updateContent(Texture content) { 372 mContent = content; 373 } 374 375 @Override 376 public int render(GLCanvas canvas, int pass) { 377 // Fit the content into the box 378 int width = mContent.getWidth(); 379 int height = mContent.getHeight(); 380 381 float scalex = mBoxWidth / (float) width; 382 float scaley = mBoxHeight / (float) height; 383 float scale = Math.min(scalex, scaley); 384 385 width = (int) FloatMath.floor(width * scale); 386 height = (int) FloatMath.floor(height * scale); 387 388 // Now draw it 389 int sourceType = SelectionDrawer.DATASOURCE_TYPE_NOT_CATEGORIZED; 390 int cacheFlag = MediaSet.CACHE_FLAG_NO; 391 int cacheStatus = MediaSet.CACHE_STATUS_NOT_CACHED; 392 MyAlbumSetItem set = mData[mSlotIndex % mData.length]; 393 Path path = set.setPath; 394 if (mCoverIndex == 0) { 395 sourceType = set.sourceType; 396 cacheFlag = set.cacheFlag; 397 cacheStatus = set.cacheStatus; 398 } 399 400 mSelectionDrawer.draw(canvas, mContent, width, height, 401 getRotation(), path, sourceType, mMediaType, 402 mIsPanorama, mLabelSpec.labelBackgroundHeight, 403 cacheFlag == MediaSet.CACHE_FLAG_FULL, 404 (cacheFlag == MediaSet.CACHE_FLAG_FULL) 405 && (cacheStatus != MediaSet.CACHE_STATUS_CACHED_FULL)); 406 407 if (mContent == mWaitLoadingTexture) { 408 mWaitLoadingDisplayed = true; 409 } 410 411 if ((mContent instanceof FadeInTexture) && 412 ((FadeInTexture) mContent).isAnimating()) { 413 return RENDER_MORE_FRAME; 414 } else { 415 return 0; 416 } 417 } 418 419 @Override 420 public void startLoadBitmap() { 421 mFuture = mThreadPool.submit(mMediaItem.requestImage( 422 MediaItem.TYPE_MICROTHUMBNAIL), this); 423 } 424 425 @Override 426 public void cancelLoadBitmap() { 427 mFuture.cancel(); 428 } 429 430 @Override 431 public void onFutureDone(Future<Bitmap> future) { 432 mHandler.sendMessage(mHandler.obtainMessage(MSG_LOAD_BITMAP_DONE, this)); 433 } 434 435 private void onLoadBitmapDone() { 436 Future<Bitmap> future = mFuture; 437 mFuture = null; 438 updateImage(future.get(), future.isCancelled()); 439 } 440 441 @Override 442 public String toString() { 443 return String.format("GalleryDisplayItem(%s, %s)", mSlotIndex, mCoverIndex); 444 } 445 } 446 447 private static int identifySourceType(MediaSet set) { 448 if (set == null) { 449 return SelectionDrawer.DATASOURCE_TYPE_NOT_CATEGORIZED; 450 } 451 452 Path path = set.getPath(); 453 if (MediaSetUtils.isCameraSource(path)) { 454 return SelectionDrawer.DATASOURCE_TYPE_CAMERA; 455 } 456 457 int type = SelectionDrawer.DATASOURCE_TYPE_NOT_CATEGORIZED; 458 String prefix = path.getPrefix(); 459 460 if (prefix.equals("picasa")) { 461 type = SelectionDrawer.DATASOURCE_TYPE_PICASA; 462 } else if (prefix.equals("local") || prefix.equals("merge")) { 463 type = SelectionDrawer.DATASOURCE_TYPE_LOCAL; 464 } else if (prefix.equals("mtp")) { 465 type = SelectionDrawer.DATASOURCE_TYPE_MTP; 466 } 467 468 return type; 469 } 470 471 private static int identifyCacheFlag(MediaSet set) { 472 if (set == null || (set.getSupportedOperations() 473 & MediaSet.SUPPORT_CACHE) == 0) { 474 return MediaSet.CACHE_FLAG_NO; 475 } 476 477 return set.getCacheFlag(); 478 } 479 480 private static int identifyCacheStatus(MediaSet set) { 481 if (set == null || (set.getSupportedOperations() 482 & MediaSet.SUPPORT_CACHE) == 0) { 483 return MediaSet.CACHE_STATUS_NOT_CACHED; 484 } 485 486 return set.getCacheStatus(); 487 } 488 489 private class LabelDisplayItem extends DisplayItem { 490 private static final int FONT_COLOR_TITLE = Color.WHITE; 491 private static final int FONT_COLOR_COUNT = 0x80FFFFFF; // 50% white 492 493 private StringTexture mTextureTitle; 494 private StringTexture mTextureCount; 495 private String mTitle; 496 private String mCount; 497 private int mLastWidth; 498 private final int mSlotIndex; 499 private boolean mHasIcon; 500 501 public LabelDisplayItem(int slotIndex) { 502 mSlotIndex = slotIndex; 503 } 504 505 public boolean updateContent() { 506 String title = mLoadingLabel; 507 String count = ""; 508 MediaSet set = mSource.getMediaSet(mSlotIndex); 509 if (set != null) { 510 title = Utils.ensureNotNull(set.getName()); 511 count = "" + set.getTotalMediaItemCount(); 512 } 513 if (Utils.equals(title, mTitle) 514 && Utils.equals(count, mCount) 515 && Utils.equals(mBoxWidth, mLastWidth)) { 516 return false; 517 } 518 mTitle = title; 519 mCount = count; 520 mLastWidth = mBoxWidth; 521 mHasIcon = (identifySourceType(set) != 522 SelectionDrawer.DATASOURCE_TYPE_NOT_CATEGORIZED); 523 524 AlbumSetView.LabelSpec s = mLabelSpec; 525 mTextureTitle = StringTexture.newInstance( 526 title, s.titleFontSize, FONT_COLOR_TITLE, 527 mBoxWidth - s.leftMargin, false); 528 mTextureCount = StringTexture.newInstance( 529 count, s.countFontSize, FONT_COLOR_COUNT, 530 mBoxWidth - s.leftMargin, true); 531 532 return true; 533 } 534 535 @Override 536 public int render(GLCanvas canvas, int pass) { 537 if (mBoxWidth != mLastWidth) { 538 updateContent(); 539 } 540 541 AlbumSetView.LabelSpec s = mLabelSpec; 542 int x = -mBoxWidth / 2; 543 int y = (mBoxHeight + 1) / 2 - s.labelBackgroundHeight; 544 y += s.titleOffset; 545 mTextureTitle.draw(canvas, x + s.leftMargin, y); 546 y += s.titleFontSize + s.countOffset; 547 x += mHasIcon ? s.iconSize : s.leftMargin; 548 mTextureCount.draw(canvas, x, y); 549 return 0; 550 } 551 552 @Override 553 public long getIdentity() { 554 return System.identityHashCode(this); 555 } 556 } 557 558 public void onSizeChanged(int size) { 559 if (mIsActive && mSize != size) { 560 mSize = size; 561 if (mListener != null) mListener.onSizeChanged(mSize); 562 } 563 } 564 565 public void onWindowContentChanged(int index) { 566 if (!mIsActive) { 567 // paused, ignore slot changed event 568 return; 569 } 570 notifySlotChanged(index); 571 } 572 573 public void pause() { 574 mIsActive = false; 575 for (int i = mContentStart, n = mContentEnd; i < n; ++i) { 576 freeSlotContent(i); 577 } 578 } 579 580 public void resume() { 581 mIsActive = true; 582 for (int i = mContentStart, n = mContentEnd; i < n; ++i) { 583 prepareSlotContent(i); 584 } 585 updateAllImageRequests(); 586 } 587} 588