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 17 package com.android.gallery3d.ui; 18 19 import android.graphics.Bitmap; 20 import android.os.Message; 21 22 import com.android.gallery3d.app.AbstractGalleryActivity; 23 import com.android.gallery3d.app.AlbumDataLoader; 24 import com.android.gallery3d.common.Utils; 25 import com.android.gallery3d.data.BitmapPool; 26 import com.android.gallery3d.data.MediaItem; 27 import com.android.gallery3d.data.MediaObject; 28 import com.android.gallery3d.data.Path; 29 import com.android.gallery3d.data.MediaObject.PanoramaSupportCallback; 30 import com.android.gallery3d.util.Future; 31 import com.android.gallery3d.util.FutureListener; 32 import com.android.gallery3d.util.JobLimiter; 33 34 public 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 { onSizeChanged(int size)42 public void onSizeChanged(int size); onContentChanged()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 TiledTexture 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 TiledTexture.Uploader mTileUploader; 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; PanoSupportListener(AlbumEntry entry)80 public PanoSupportListener (AlbumEntry entry) { 81 mEntry = entry; 82 } 83 @Override panoramaInfoAvailable(MediaObject mediaObject, boolean isPanorama, boolean isPanorama360)84 public void panoramaInfoAvailable(MediaObject mediaObject, boolean isPanorama, 85 boolean isPanorama360) { 86 if (mEntry != null) mEntry.isPanorama = isPanorama; 87 } 88 } 89 AlbumSlidingWindow(AbstractGalleryActivity activity, AlbumDataLoader source, int cacheSize)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 mTileUploader = new TiledTexture.Uploader(activity.getGLRoot()); 107 } 108 setListener(Listener listener)109 public void setListener(Listener listener) { 110 mListener = listener; 111 } 112 get(int slotIndex)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 isActiveSlot(int slotIndex)121 public boolean isActiveSlot(int slotIndex) { 122 return slotIndex >= mActiveStart && slotIndex < mActiveEnd; 123 } 124 setContentWindow(int contentStart, int contentEnd)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 setActiveWindow(int start, int end)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 uploadBgTextureInSlot(int index)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 mTileUploader.addTexture(entry.bitmapTexture); 185 } 186 } 187 } 188 updateTextureUploadQueue()189 private void updateTextureUploadQueue() { 190 if (!mIsActive) return; 191 mTileUploader.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 mTileUploader.addTexture(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 ----------->| requestNonactiveImages()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 requestSlotImage(int slotIndex)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 cancelNonactiveImages()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 cancelSlotImage(int slotIndex)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 freeSlotContent(int slotIndex)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 prepareSlotContent(int slotIndex)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 updateAllImageRequests()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 ThumbnailLoader(int slotIndex, MediaItem item)291 public ThumbnailLoader(int slotIndex, MediaItem item) { 292 mSlotIndex = slotIndex; 293 mItem = item; 294 } 295 296 @Override recycleBitmap(Bitmap bitmap)297 protected void recycleBitmap(Bitmap bitmap) { 298 BitmapPool pool = MediaItem.getMicroThumbPool(); 299 if (pool != null) pool.recycle(bitmap); 300 } 301 302 @Override submitBitmapTask(FutureListener<Bitmap> l)303 protected Future<Bitmap> submitBitmapTask(FutureListener<Bitmap> l) { 304 return mThreadPool.submit( 305 mItem.requestImage(MediaItem.TYPE_MICROTHUMBNAIL), this); 306 } 307 308 @Override onLoadComplete(Bitmap bitmap)309 protected void onLoadComplete(Bitmap bitmap) { 310 mHandler.obtainMessage(MSG_UPDATE_ENTRY, this).sendToTarget(); 311 } 312 updateEntry()313 public void updateEntry() { 314 Bitmap bitmap = getBitmap(); 315 if (bitmap == null) return; // error or recycled 316 AlbumEntry entry = mData[mSlotIndex % mData.length]; 317 entry.bitmapTexture = new TiledTexture(bitmap); 318 entry.content = entry.bitmapTexture; 319 320 if (isActiveSlot(mSlotIndex)) { 321 mTileUploader.addTexture(entry.bitmapTexture); 322 --mActiveRequestCount; 323 if (mActiveRequestCount == 0) requestNonactiveImages(); 324 if (mListener != null) mListener.onContentChanged(); 325 } else { 326 mTileUploader.addTexture(entry.bitmapTexture); 327 } 328 } 329 } 330 331 @Override onSizeChanged(int size)332 public void onSizeChanged(int size) { 333 if (mSize != size) { 334 mSize = size; 335 if (mListener != null) mListener.onSizeChanged(mSize); 336 if (mContentEnd > mSize) mContentEnd = mSize; 337 if (mActiveEnd > mSize) mActiveEnd = mSize; 338 } 339 } 340 341 @Override onContentChanged(int index)342 public void onContentChanged(int index) { 343 if (index >= mContentStart && index < mContentEnd && mIsActive) { 344 freeSlotContent(index); 345 prepareSlotContent(index); 346 updateAllImageRequests(); 347 if (mListener != null && isActiveSlot(index)) { 348 mListener.onContentChanged(); 349 } 350 } 351 } 352 resume()353 public void resume() { 354 mIsActive = true; 355 TiledTexture.prepareResources(); 356 for (int i = mContentStart, n = mContentEnd; i < n; ++i) { 357 prepareSlotContent(i); 358 } 359 updateAllImageRequests(); 360 } 361 pause()362 public void pause() { 363 mIsActive = false; 364 mTileUploader.clear(); 365 TiledTexture.freeResources(); 366 for (int i = mContentStart, n = mContentEnd; i < n; ++i) { 367 freeSlotContent(i); 368 } 369 } 370 } 371