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