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