OSDN Git Service

Upload textures in background.
authorOwen Lin <owenlin@google.com>
Thu, 29 Mar 2012 06:27:58 +0000 (14:27 +0800)
committerOwen Lin <owenlin@google.com>
Thu, 5 Apr 2012 07:15:39 +0000 (15:15 +0800)
Change-Id: I365f7be9aaab793366884249cbb10e8b6f0ab0e6

16 files changed:
src/com/android/gallery3d/app/AbstractGalleryActivity.java
src/com/android/gallery3d/app/AlbumDataAdapter.java
src/com/android/gallery3d/app/AlbumSetDataAdapter.java
src/com/android/gallery3d/data/ImageCacheRequest.java
src/com/android/gallery3d/data/MediaItem.java
src/com/android/gallery3d/ui/AlbumLabelMaker.java
src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java
src/com/android/gallery3d/ui/AlbumSetView.java
src/com/android/gallery3d/ui/AlbumSlidingWindow.java
src/com/android/gallery3d/ui/AlbumView.java
src/com/android/gallery3d/ui/BitmapPool.java
src/com/android/gallery3d/ui/GLRoot.java
src/com/android/gallery3d/ui/GLRootView.java
src/com/android/gallery3d/ui/SlotView.java
src/com/android/gallery3d/ui/TextureUploader.java [new file with mode: 0644]
src/com/android/gallery3d/ui/TileImageView.java

index 899e9bf..64c430d 100644 (file)
@@ -33,7 +33,7 @@ import android.view.WindowManager;
 
 import com.android.gallery3d.R;
 import com.android.gallery3d.data.DataManager;
-import com.android.gallery3d.ui.BitmapPool;
+import com.android.gallery3d.data.MediaItem;
 import com.android.gallery3d.ui.GLRoot;
 import com.android.gallery3d.ui.GLRootView;
 import com.android.gallery3d.util.ThreadPool;
@@ -177,7 +177,7 @@ public class AbstractGalleryActivity extends Activity implements GalleryActivity
         } finally {
             mGLRootView.unlockRenderThread();
         }
-        BitmapPool.clear();
+        MediaItem.getMicroThumbPool().clear();
     }
 
     @Override
index 1d10b89..ec46e50 100644 (file)
@@ -18,6 +18,7 @@ package com.android.gallery3d.app;
 
 import android.os.Handler;
 import android.os.Message;
+import android.os.Process;
 
 import com.android.gallery3d.common.Utils;
 import com.android.gallery3d.data.ContentListener;
@@ -314,6 +315,8 @@ public class AlbumDataAdapter implements AlbumView.Model {
 
         @Override
         public void run() {
+            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+
             boolean updateComplete = false;
             while (mActive) {
                 synchronized (this) {
index e739a96..eea039d 100644 (file)
@@ -18,6 +18,7 @@ package com.android.gallery3d.app;
 
 import android.os.Handler;
 import android.os.Message;
+import android.os.Process;
 import android.os.SystemClock;
 
 import com.android.gallery3d.common.Utils;
@@ -307,6 +308,8 @@ public class AlbumSetDataAdapter implements AlbumSetView.Model {
 
         @Override
         public void run() {
+            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+
             boolean updateComplete = false;
             while (mActive) {
                 synchronized (this) {
index 64dfc9f..d3d4f51 100644 (file)
@@ -22,7 +22,6 @@ import android.graphics.BitmapFactory;
 import com.android.gallery3d.app.GalleryApp;
 import com.android.gallery3d.common.BitmapUtils;
 import com.android.gallery3d.data.ImageCacheService.ImageData;
-import com.android.gallery3d.ui.BitmapPool;
 import com.android.gallery3d.util.ThreadPool.Job;
 import com.android.gallery3d.util.ThreadPool.JobContext;
 
@@ -56,7 +55,7 @@ abstract class ImageCacheRequest implements Job<Bitmap> {
             options.inPreferredConfig = Bitmap.Config.ARGB_8888;
             Bitmap bitmap;
             if (mType == MediaItem.TYPE_MICROTHUMBNAIL) {
-                bitmap = BitmapPool.decode(jc, BitmapPool.TYPE_MICRO_THUMB,
+                bitmap = MediaItem.getMicroThumbPool().decode(jc,
                         data.mData, data.mOffset, data.mData.length - data.mOffset, options);
             } else {
                 bitmap = DecodeUtils.decode(jc,
index b2632f1..2b8f8a3 100644 (file)
@@ -19,8 +19,9 @@ package com.android.gallery3d.data;
 import android.graphics.Bitmap;
 import android.graphics.BitmapRegionDecoder;
 
-import com.android.gallery3d.util.ThreadPool.Job;
+import com.android.gallery3d.ui.BitmapPool;
 import com.android.gallery3d.ui.ScreenNail;
+import com.android.gallery3d.util.ThreadPool.Job;
 
 // MediaItem represents an image or a video item.
 public abstract class MediaItem extends MediaObject {
@@ -39,6 +40,9 @@ public abstract class MediaItem extends MediaObject {
 
     public static final String MIME_TYPE_JPEG = "image/jpeg";
 
+    private static final BitmapPool sMicroThumbPool =
+            new BitmapPool(MICROTHUMBNAIL_TARGET_SIZE, MICROTHUMBNAIL_TARGET_SIZE);
+
     // TODO: fix default value for latlng and change this.
     public static final double INVALID_LATLNG = 0f;
 
@@ -108,4 +112,8 @@ public abstract class MediaItem extends MediaObject {
                     "should only request thumb/microthumb from cache");
         }
     }
+
+    public static BitmapPool getMicroThumbPool() {
+        return sMicroThumbPool;
+    }
 }
index 22fe16d..68fa695 100644 (file)
@@ -8,6 +8,7 @@ import android.graphics.Bitmap.Config;
 import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.PorterDuff;
 import android.graphics.Typeface;
 import android.text.TextPaint;
 import android.text.TextUtils;
@@ -27,6 +28,9 @@ public class AlbumLabelMaker {
     private final TextPaint mCountPaint;
     private final Context mContext;
 
+    private int mLabelWidth;
+    private BitmapPool mBitmapPool;
+
     private final LazyLoadedBitmap mLocalSetIcon;
     private final LazyLoadedBitmap mPicasaIcon;
     private final LazyLoadedBitmap mCameraIcon;
@@ -89,14 +93,20 @@ public class AlbumLabelMaker {
         }
     }
 
+    public synchronized void setLabelWidth(int width) {
+        if (mLabelWidth == width) return;
+        mLabelWidth = width;
+        mBitmapPool = new BitmapPool(mLabelWidth, mSpec.labelBackgroundHeight);
+    }
+
     public ThreadPool.Job<Bitmap> requestLabel(
-            String title, String count, int sourceType, int slotWidth) {
-        return new AlbumLabelJob(null, title, count, sourceType, slotWidth);
+            String title, String count, int sourceType) {
+        return new AlbumLabelJob(null, title, count, sourceType);
     }
 
     public ThreadPool.Job<Bitmap> requestLabel(
-            MediaSet album, int sourceType, int slotWidth) {
-        return new AlbumLabelJob(album, null, null, sourceType, slotWidth);
+            MediaSet album, int sourceType) {
+        return new AlbumLabelJob(album, null, null, sourceType);
     }
 
     private static void drawText(Canvas canvas,
@@ -114,15 +124,13 @@ public class AlbumLabelMaker {
         private final String mTitle;
         private final String mCount;
         private final int mSourceType;
-        private final int mSlotWidth;
 
         public AlbumLabelJob(MediaSet album,
-                String title, String count, int sourceType, int slotWidth) {
+                String title, String count, int sourceType) {
             mAlbum = album;
             mTitle = title;
             mCount = count;
             mSourceType = sourceType;
-            mSlotWidth = slotWidth;
         }
 
         @Override
@@ -136,22 +144,36 @@ public class AlbumLabelMaker {
                     ? Utils.ensureNotNull(mCount)
                     : String.valueOf(album.getTotalMediaItemCount());
             Bitmap icon = getOverlayAlbumIcon(mSourceType);
-            Bitmap bitmap = Bitmap.createBitmap(mSlotWidth,
-                    s.labelBackgroundHeight, Config.ARGB_8888);
-            Canvas canvas = new Canvas(bitmap);
+
+            Bitmap bitmap = null;
+            Canvas canvas;
+            int labelWidth;
+
+            synchronized (this) {
+                labelWidth = mLabelWidth;
+                bitmap = mBitmapPool.getBitmap();
+            }
+            if (bitmap == null) {
+                bitmap = Bitmap.createBitmap(labelWidth,
+                        s.labelBackgroundHeight, Config.ARGB_8888);
+                canvas = new Canvas(bitmap);
+            } else {
+                canvas = new Canvas(bitmap);
+                canvas.drawColor(0, PorterDuff.Mode.SRC);
+            }
 
             // draw title
             if (jc.isCancelled()) return null;
             int x = s.leftMargin;
             int y = s.titleOffset;
-            drawText(canvas, x, y, title, mSlotWidth - s.leftMargin, mTitlePaint);
+            drawText(canvas, x, y, title, labelWidth - s.leftMargin, mTitlePaint);
 
             // draw the count
             if (jc.isCancelled()) return null;
             if (icon != null) x = s.iconSize;
             y += s.titleFontSize + s.countOffset;
             drawText(canvas, x, y, count,
-                    mSlotWidth - s.leftMargin - s.iconSize, mCountPaint);
+                    labelWidth - s.leftMargin - s.iconSize, mCountPaint);
 
             // draw the icon
             if (icon != null) {
@@ -166,4 +188,12 @@ public class AlbumLabelMaker {
             return bitmap;
         }
     }
+
+    public void reycleLabel(Bitmap label) {
+        mBitmapPool.recycle(label);
+    }
+
+    public void clearRecycledLabels() {
+        mBitmapPool.clear();
+    }
 }
index 6eb3911..467b522 100644 (file)
@@ -57,6 +57,7 @@ public class AlbumSetSlidingWindow implements AlbumSetView.ModelListener {
     private final ThreadPool mThreadPool;
     private final AlbumLabelMaker mLabelMaker;
     private final String mLoadingText;
+    private final TextureUploader mTextureUploader;
 
     private int mActiveRequestCount = 0;
     private boolean mIsActive = false;
@@ -94,6 +95,7 @@ public class AlbumSetSlidingWindow implements AlbumSetView.ModelListener {
 
         mLabelMaker = new AlbumLabelMaker(activity.getAndroidContext(), labelSpec);
         mLoadingText = activity.getAndroidContext().getString(R.string.loading);
+        mTextureUploader = new TextureUploader(activity.getGLRoot());
 
         mHandler = new SynchronizedHandler(activity.getGLRoot()) {
             @Override
@@ -168,7 +170,11 @@ public class AlbumSetSlidingWindow implements AlbumSetView.ModelListener {
                 0, Math.max(0, mSize - data.length));
         int contentEnd = Math.min(contentStart + data.length, mSize);
         setContentWindow(contentStart, contentEnd);
-        if (mIsActive) updateAllImageRequests();
+
+        if (mIsActive) {
+            updateTextureUploadQueue();
+            updateAllImageRequests();
+        }
     }
 
     // We would like to request non active slots in the following order:
@@ -236,8 +242,8 @@ public class AlbumSetSlidingWindow implements AlbumSetView.ModelListener {
             entry.label = null;
         }
         if (album != null) {
-            entry.labelLoader = new AlbumLabelLoader(
-                    slotIndex, album, entry.sourceType, mSlotWidth);
+            entry.labelLoader =
+                    new AlbumLabelLoader(slotIndex, album, entry.sourceType);
         }
 
         entry.coverItem = cover;
@@ -271,22 +277,23 @@ public class AlbumSetSlidingWindow implements AlbumSetView.ModelListener {
         return loader.isRequestInProgress();
     }
 
-    private void notifySlotChanged(int slotIndex) {
-        // If the updated content is not cached, ignore it
-        if (slotIndex < mContentStart || slotIndex >= mContentEnd) {
-            Log.w(TAG, String.format(
-                    "invalid update: %s is outside (%s, %s)",
-                    slotIndex, mContentStart, mContentEnd) );
-            return;
+    private void queueTextureForUpload(boolean isActive, Texture texture) {
+        if ((texture == null) || !(texture instanceof BitmapTexture)) return;
+        if (isActive) {
+            mTextureUploader.addFgTexture((BitmapTexture) texture);
+        } else {
+            mTextureUploader.addBgTexture((BitmapTexture) texture);
         }
+    }
 
-        AlbumSetEntry entry = mData[slotIndex % mData.length];
-        MediaSet set = mSource.getMediaSet(slotIndex);
-        MediaItem coverItem = mSource.getCoverItem(slotIndex);
-        updateAlbumSetEntry(entry, slotIndex, set, coverItem);
-        updateAllImageRequests();
-        if (mListener != null && isActiveSlot(slotIndex)) {
-            mListener.onContentChanged();
+    private void updateTextureUploadQueue() {
+        if (!mIsActive) return;
+        mTextureUploader.clear();
+        for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
+            AlbumSetEntry entry = mData[i % mData.length];
+            boolean isActive = isActiveSlot(i);
+            queueTextureForUpload(isActive, entry.label);
+            queueTextureForUpload(isActive, entry.content);
         }
     }
 
@@ -318,13 +325,30 @@ public class AlbumSetSlidingWindow implements AlbumSetView.ModelListener {
             // paused, ignore slot changed event
             return;
         }
-        notifySlotChanged(index);
+
+        // If the updated content is not cached, ignore it
+        if (index < mContentStart || index >= mContentEnd) {
+            Log.w(TAG, String.format(
+                    "invalid update: %s is outside (%s, %s)",
+                    index, mContentStart, mContentEnd) );
+            return;
+        }
+
+        AlbumSetEntry entry = mData[index % mData.length];
+        MediaSet set = mSource.getMediaSet(index);
+        MediaItem coverItem = mSource.getCoverItem(index);
+        updateAlbumSetEntry(entry, index, set, coverItem);
+        updateAllImageRequests();
+        updateTextureUploadQueue();
+        if (mListener != null && isActiveSlot(index)) {
+            mListener.onContentChanged();
+        }
     }
 
     public BitmapTexture getLoadingTexture() {
         if (mLoadingLabel == null) {
             Bitmap bitmap = mLabelMaker.requestLabel(mLoadingText, null,
-                    SelectionDrawer.DATASOURCE_TYPE_NOT_CATEGORIZED, mSlotWidth)
+                    SelectionDrawer.DATASOURCE_TYPE_NOT_CATEGORIZED)
                     .run(ThreadPool.JOB_CONTEXT_STUB);
             mLoadingLabel = new BitmapTexture(bitmap);
             mLoadingLabel.setOpaque(false);
@@ -337,6 +361,7 @@ public class AlbumSetSlidingWindow implements AlbumSetView.ModelListener {
         for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
             freeSlotContent(i);
         }
+        mLabelMaker.clearRecycledLabels();
     }
 
     public void resume() {
@@ -362,7 +387,7 @@ public class AlbumSetSlidingWindow implements AlbumSetView.ModelListener {
 
         @Override
         protected void recycleBitmap(Bitmap bitmap) {
-            BitmapPool.recycle(BitmapPool.TYPE_MICRO_THUMB, bitmap);
+            MediaItem.getMicroThumbPool().recycle(bitmap);
         }
 
         @Override
@@ -379,16 +404,19 @@ public class AlbumSetSlidingWindow implements AlbumSetView.ModelListener {
         @Override
         public void updateEntry() {
             Bitmap bitmap = getBitmap();
-            if (bitmap == null) return;
+            if (bitmap == null) return; // error or recycled
 
             AlbumSetEntry entry = mData[mSlotIndex % mData.length];
-            BitmapTexture content = new BitmapTexture(bitmap);
-            entry.content = content;
+            BitmapTexture texture = new BitmapTexture(bitmap);
+            entry.content = texture;
 
             if (isActiveSlot(mSlotIndex)) {
+                mTextureUploader.addFgTexture(texture);
                 --mActiveRequestCount;
                 if (mActiveRequestCount == 0) requestNonactiveImages();
                 if (mListener != null) mListener.onContentChanged();
+            } else {
+                mTextureUploader.addBgTexture(texture);
             }
         }
     }
@@ -439,24 +467,23 @@ public class AlbumSetSlidingWindow implements AlbumSetView.ModelListener {
         private final MediaSet mMediaSet;
         private final int mSlotIndex;
         private final int mSourceType;
-        private final int mLabelWidth;
 
-        public AlbumLabelLoader(int slotIndex,
-                MediaSet mediaSet, int sourceType, int labelWidth) {
+        public AlbumLabelLoader(
+                int slotIndex, MediaSet mediaSet, int sourceType) {
             mSlotIndex = slotIndex;
             mMediaSet = mediaSet;
             mSourceType = sourceType;
-            mLabelWidth = labelWidth;
         }
 
         @Override
         protected Future<Bitmap> submitBitmapTask(FutureListener<Bitmap> l) {
             return mThreadPool.submit(mLabelMaker.requestLabel(
-                    mMediaSet, mSourceType, mLabelWidth), l);
+                    mMediaSet, mSourceType), l);
         }
 
         @Override
         protected void recycleBitmap(Bitmap bitmap) {
+            mLabelMaker.reycleLabel(bitmap);
         }
 
         @Override
@@ -466,28 +493,33 @@ public class AlbumSetSlidingWindow implements AlbumSetView.ModelListener {
 
         @Override
         public void updateEntry() {
-            if (isRecycled()) return;
-
             Bitmap bitmap = getBitmap();
-            if (bitmap == null) return;
+            if (bitmap == null) return; // Error or recycled
 
             AlbumSetEntry entry = mData[mSlotIndex % mData.length];
-            BitmapTexture content = new BitmapTexture(bitmap);
-            content.setOpaque(false);
-            entry.label = content;
+            BitmapTexture texture = new BitmapTexture(bitmap);
+            texture.setOpaque(false);
+            entry.label = texture;
 
             if (isActiveSlot(mSlotIndex)) {
+                mTextureUploader.addFgTexture(texture);
                 --mActiveRequestCount;
                 if (mActiveRequestCount == 0) requestNonactiveImages();
                 if (mListener != null) mListener.onContentChanged();
+            } else {
+                mTextureUploader.addBgTexture(texture);
             }
         }
     }
 
     public void onSlotSizeChanged(int width, int height) {
         if (mSlotWidth == width) return;
+
         mSlotWidth = width;
         mLoadingLabel = null;
+        mLabelMaker.setLabelWidth(mSlotWidth);
+
+        if (!mIsActive) return;
 
         for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
             AlbumSetEntry entry = mData[i % mData.length];
@@ -498,8 +530,9 @@ public class AlbumSetSlidingWindow implements AlbumSetView.ModelListener {
             }
             entry.labelLoader = (entry.album == null)
                     ? null
-                    : new AlbumLabelLoader(i, entry.album, entry.sourceType, width);
+                    : new AlbumLabelLoader(i, entry.album, entry.sourceType);
         }
         updateAllImageRequests();
+        updateTextureUploadQueue();
     }
 }
index 70ecbb4..8a169b1 100644 (file)
@@ -98,6 +98,13 @@ public class AlbumSetView implements SlotView.SlotRenderer {
         }
     }
 
+    private static Texture checkTexture(GLCanvas canvas, Texture texture) {
+        return ((texture == null) || ((texture instanceof UploadedTexture)
+                && !((UploadedTexture) texture).isContentValid(canvas)))
+                ? null
+                : texture;
+    }
+
     @Override
     public int renderSlot(GLCanvas canvas, int index, int pass, int width, int height) {
         AlbumSetEntry entry = mDataWindow.get(index);
@@ -107,8 +114,7 @@ public class AlbumSetView implements SlotView.SlotRenderer {
 
     private int renderContent(GLCanvas canvas,
             int pass, AlbumSetEntry entry, int width, int height) {
-        // Fit the content into the box
-        Texture content = entry.content;
+        Texture content = checkTexture(canvas, entry.content);
 
         if (content == null) {
             content = mWaitLoadingTexture;
@@ -120,6 +126,7 @@ public class AlbumSetView implements SlotView.SlotRenderer {
             content = entry.content;
         }
 
+        // Fit the content into the box
         int w = content.getWidth();
         int h = content.getHeight();
 
@@ -150,7 +157,7 @@ public class AlbumSetView implements SlotView.SlotRenderer {
 
         // We show the loading message only when the album is still loading
         // (Not when we are still preparing the label)
-        Texture content = entry.label;
+        Texture content = checkTexture(canvas, entry.label);
         if (entry.album == null) {
             content = mDataWindow.getLoadingTexture();
         }
index c88ca7d..af6dc46 100644 (file)
@@ -53,6 +53,11 @@ public class AlbumSlidingWindow implements AlbumView.ModelListener {
     }
 
     private final AlbumView.Model mSource;
+    private final AlbumEntry mData[];
+    private final SynchronizedHandler mHandler;
+    private final JobLimiter mThreadPool;
+    private final TextureUploader mTextureUploader;
+
     private int mSize;
 
     private int mContentStart = 0;
@@ -63,11 +68,6 @@ public class AlbumSlidingWindow implements AlbumView.ModelListener {
 
     private Listener mListener;
 
-    private final AlbumEntry mData[];
-
-    private SynchronizedHandler mHandler;
-    private JobLimiter mThreadPool;
-
     private int mActiveRequestCount = 0;
     private boolean mIsActive = false;
 
@@ -87,6 +87,7 @@ public class AlbumSlidingWindow implements AlbumView.ModelListener {
         };
 
         mThreadPool = new JobLimiter(activity.getThreadPool(), JOB_LIMIT);
+        mTextureUploader = new TextureUploader(activity.getGLRoot());
     }
 
     public void setListener(Listener listener) {
@@ -156,9 +157,29 @@ public class AlbumSlidingWindow implements AlbumView.ModelListener {
                 0, Math.max(0, mSize - data.length));
         int contentEnd = Math.min(contentStart + data.length, mSize);
         setContentWindow(contentStart, contentEnd);
+        updateUploadedTextures();
         if (mIsActive) updateAllImageRequests();
     }
 
+    private void uploadTexture(boolean isActive, Texture texture) {
+        if ((texture == null) || !(texture instanceof BitmapTexture)) return;
+        if (isActive) {
+            mTextureUploader.addFgTexture((BitmapTexture) texture);
+        } else {
+            mTextureUploader.addBgTexture((BitmapTexture) texture);
+        }
+    }
+
+    private void updateUploadedTextures() {
+        if (!mIsActive) return;
+        mTextureUploader.clear();
+        for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
+            AlbumEntry entry = mData[i % mData.length];
+            boolean isActive = isActiveSlot(i);
+            uploadTexture(isActive, entry.content);
+        }
+    }
+
     // We would like to request non active slots in the following order:
     // Order:    8 6 4 2                   1 3 5 7
     //         |---------|---------------|---------|
@@ -244,8 +265,8 @@ public class AlbumSlidingWindow implements AlbumView.ModelListener {
     }
 
     private class ThumbnailLoader extends BitmapLoader  {
-        final int mSlotIndex;
-        final MediaItem mItem;
+        private final int mSlotIndex;
+        private final MediaItem mItem;
 
         public ThumbnailLoader(int slotIndex, MediaItem item) {
             mSlotIndex = slotIndex;
@@ -254,7 +275,7 @@ public class AlbumSlidingWindow implements AlbumView.ModelListener {
 
         @Override
         protected void recycleBitmap(Bitmap bitmap) {
-            BitmapPool.recycle(BitmapPool.TYPE_MICRO_THUMB, bitmap);
+            MediaItem.getMicroThumbPool().recycle(bitmap);
         }
 
         @Override
@@ -269,16 +290,19 @@ public class AlbumSlidingWindow implements AlbumView.ModelListener {
         }
 
         public void updateEntry() {
-            if (isRecycled()) return;
+            Bitmap bitmap = getBitmap();
+            if (bitmap == null) return; // error or recycled
 
             AlbumEntry entry = mData[mSlotIndex % mData.length];
-            Bitmap bitmap = entry.contentLoader.getBitmap();
-            if (bitmap != null) entry.content = new BitmapTexture(bitmap);
+            entry.content = new BitmapTexture(bitmap);
 
             if (isActiveSlot(mSlotIndex)) {
+                mTextureUploader.addFgTexture((BitmapTexture) entry.content);
                 --mActiveRequestCount;
                 if (mActiveRequestCount == 0) requestNonactiveImages();
                 if (mListener != null) mListener.onContentChanged();
+            } else {
+                mTextureUploader.addBgTexture((BitmapTexture) entry.content);
             }
         }
     }
index 827d57d..1ed3ded 100644 (file)
@@ -79,10 +79,17 @@ public class AlbumView implements SlotView.SlotRenderer {
         mSlotView.invalidate();
     }
 
+    private static Texture checkTexture(GLCanvas canvas, Texture texture) {
+        return ((texture == null) || ((texture instanceof UploadedTexture)
+                && !((UploadedTexture) texture).isContentValid(canvas)))
+                ? null
+                : texture;
+    }
+
     @Override
     public int renderSlot(GLCanvas canvas, int index, int pass, int width, int height) {
         AlbumSlidingWindow.AlbumEntry entry = mDataWindow.get(index);
-        Texture content = entry.content;
+        Texture content = checkTexture(canvas, entry.content);
 
         if (content == null) {
             content = mWaitLoadingTexture;
index e910aec..2cd3a4e 100644 (file)
@@ -7,7 +7,6 @@ import android.graphics.BitmapFactory;
 import android.graphics.BitmapFactory.Options;
 
 import com.android.gallery3d.data.DecodeUtils;
-import com.android.gallery3d.data.MediaItem;
 import com.android.gallery3d.util.ThreadPool.JobContext;
 
 import java.io.FileDescriptor;
@@ -16,63 +15,47 @@ import java.util.ArrayList;
 public class BitmapPool {
     private static final String TAG = "BitmapPool";
 
-    public static final int TYPE_MICRO_THUMB = 0;
-    private static final int TYPE_COUNT = 1;
     private static final int POOL_SIZE = 16;
-    private static final int EXPECTED_WIDTH[] = {MediaItem.MICROTHUMBNAIL_TARGET_SIZE};
-    private static final int EXPECTED_HEIGHT[] = {MediaItem.MICROTHUMBNAIL_TARGET_SIZE};
+    private final ArrayList<Bitmap> mPool = new ArrayList<Bitmap>(POOL_SIZE);
 
-    @SuppressWarnings("unchecked")
-    private static final ArrayList<Bitmap> sPools[] = new ArrayList[TYPE_COUNT];
-    static {
-        for (int i = 0; i < TYPE_COUNT; ++i) {
-            sPools[i] = new ArrayList<Bitmap>();
-        }
-    }
+    private final int mWidth;
+    private final int mHeight;
 
-    private BitmapPool() {
+    public BitmapPool(int width, int height) {
+        mWidth = width;
+        mHeight = height;
     }
 
-    public static Bitmap getBitmap(int type) {
-        ArrayList<Bitmap> list = sPools[type];
-        synchronized (list) {
-            int size = list.size();
-            return size > 0 ? list.remove(size - 1) : null;
-        }
+    public synchronized Bitmap getBitmap() {
+        int size = mPool.size();
+        return size > 0 ? mPool.remove(size - 1) : null;
     }
 
-    public static void recycle(int type, Bitmap bitmap) {
+    public void recycle(Bitmap bitmap) {
         if (bitmap == null) return;
-        if ((bitmap.getWidth() != EXPECTED_WIDTH[type])
-                || (bitmap.getHeight() != EXPECTED_HEIGHT[type])) {
+        if ((bitmap.getWidth() != mWidth) || (bitmap.getHeight() != mHeight)) {
             bitmap.recycle();
             return;
         }
-        ArrayList<Bitmap> list = sPools[type];
-        synchronized (list) {
-            if (list.size() < POOL_SIZE) list.add(bitmap);
+        synchronized (this) {
+            if (mPool.size() < POOL_SIZE) mPool.add(bitmap);
         }
     }
 
-    public static void clear() {
-        for (int i = 0; i < TYPE_COUNT; ++i) {
-            ArrayList<Bitmap> list = sPools[i];
-            synchronized (list) {
-                list.clear();
-            }
-        }
+    public synchronized void clear() {
+        mPool.clear();
     }
 
-    public static Bitmap decode(JobContext jc, int type,
+    public Bitmap decode(JobContext jc,
             byte[] data, int offset, int length, BitmapFactory.Options options) {
         if (options == null) options = new BitmapFactory.Options();
         if (options.inSampleSize < 1) options.inSampleSize = 1;
         options.inPreferredConfig = Bitmap.Config.ARGB_8888;
-        options.inBitmap = (options.inSampleSize == 1) ? getBitmap(type) : null;
+        options.inBitmap = (options.inSampleSize == 1) ? getBitmap() : null;
         try {
             Bitmap bitmap = DecodeUtils.decode(jc, data, offset, length, options);
             if (options.inBitmap != null && options.inBitmap != bitmap) {
-                recycle(type, bitmap);
+                recycle(options.inBitmap);
                 options.inBitmap = null;
             }
             return bitmap;
@@ -80,7 +63,7 @@ public class BitmapPool {
             if (options.inBitmap == null) throw e;
 
             Log.w(TAG, "decode fail with a given bitmap, try decode to a new bitmap");
-            recycle(type, options.inBitmap);
+            recycle(options.inBitmap);
             options.inBitmap = null;
             return DecodeUtils.decode(jc, data, offset, length, options);
         }
@@ -88,16 +71,16 @@ public class BitmapPool {
 
     // This is the same as the method above except the source data comes
     // from a file descriptor instead of a byte array.
-    public static Bitmap decode(int type,
-            JobContext jc, FileDescriptor fileDescriptor, Options options) {
+    public Bitmap decode(JobContext jc,
+            FileDescriptor fileDescriptor, Options options) {
         if (options == null) options = new BitmapFactory.Options();
         if (options.inSampleSize < 1) options.inSampleSize = 1;
         options.inPreferredConfig = Bitmap.Config.ARGB_8888;
-        options.inBitmap = (options.inSampleSize == 1) ? getBitmap(type) : null;
+        options.inBitmap = (options.inSampleSize == 1) ? getBitmap() : null;
         try {
             Bitmap bitmap = DecodeUtils.decode(jc, fileDescriptor, options);
             if (options.inBitmap != null&& options.inBitmap != bitmap) {
-                recycle(type, bitmap);
+                recycle(options.inBitmap);
                 options.inBitmap = null;
             }
             return bitmap;
@@ -105,7 +88,7 @@ public class BitmapPool {
             if (options.inBitmap == null) throw e;
 
             Log.w(TAG, "decode fail with a given bitmap, try decode to a new bitmap");
-            recycle(type, options.inBitmap);
+            recycle(options.inBitmap);
             options.inBitmap = null;
             return DecodeUtils.decode(jc, fileDescriptor, options);
         }
index 834e85c..fe040ba 100644 (file)
@@ -20,8 +20,11 @@ import com.android.gallery3d.anim.CanvasAnimation;
 
 public interface GLRoot {
 
+    // Listener will be called when GL is idle AND before each frame.
+    // Mainly used for uploading textures.
     public static interface OnGLIdleListener {
-        public boolean onGLIdle(GLRoot root, GLCanvas canvas);
+        public boolean onGLIdle(
+                GLCanvas canvas, boolean renderRequested);
     }
 
     public void addOnGLIdleListener(OnGLIdleListener listener);
index 51853ff..bed2908 100644 (file)
@@ -30,8 +30,8 @@ import com.android.gallery3d.common.Utils;
 import com.android.gallery3d.util.GalleryUtils;
 import com.android.gallery3d.util.Profile;
 
+import java.util.ArrayDeque;
 import java.util.ArrayList;
-import java.util.LinkedList;
 import java.util.concurrent.locks.ReentrantLock;
 
 import javax.microedition.khronos.egl.EGLConfig;
@@ -82,8 +82,8 @@ public class GLRootView extends GLSurfaceView
     private final ArrayList<CanvasAnimation> mAnimations =
             new ArrayList<CanvasAnimation>();
 
-    private final LinkedList<OnGLIdleListener> mIdleListeners =
-            new LinkedList<OnGLIdleListener>();
+    private final ArrayDeque<OnGLIdleListener> mIdleListeners =
+            new ArrayDeque<OnGLIdleListener>();
 
     private final IdleRunner mIdleRunner = new IdleRunner();
 
@@ -267,6 +267,7 @@ public class GLRootView extends GLSurfaceView
         } finally {
             mRenderLock.unlock();
         }
+
         if (DEBUG_PROFILE_SLOW_ONLY) {
             long t = System.nanoTime();
             long durationInMs = (t - mLastDrawFinishTime) / 1000000;
@@ -305,7 +306,6 @@ public class GLRootView extends GLSurfaceView
             gl.glScissor(clip.left, clip.top, clip.width(), clip.height());
         }
 
-
         if (mContentView != null) {
            mContentView.render(mCanvas);
         }
@@ -323,9 +323,7 @@ public class GLRootView extends GLSurfaceView
         }
 
         synchronized (mIdleListeners) {
-            if (!mRenderRequested && !mIdleListeners.isEmpty()) {
-                mIdleRunner.enable();
-            }
+            if (!mIdleListeners.isEmpty()) mIdleRunner.enable();
         }
 
         if (DEBUG_INVALIDATE) {
@@ -370,19 +368,18 @@ public class GLRootView extends GLSurfaceView
             OnGLIdleListener listener;
             synchronized (mIdleListeners) {
                 mActive = false;
-                if (mRenderRequested) return;
                 if (mIdleListeners.isEmpty()) return;
                 listener = mIdleListeners.removeFirst();
             }
             mRenderLock.lock();
             try {
-                if (!listener.onGLIdle(GLRootView.this, mCanvas)) return;
+                if (!listener.onGLIdle(mCanvas, mRenderRequested)) return;
             } finally {
                 mRenderLock.unlock();
             }
             synchronized (mIdleListeners) {
                 mIdleListeners.addLast(listener);
-                enable();
+                if (!mRenderRequested) enable();
             }
         }
 
index aef97a4..5e12c74 100644 (file)
@@ -84,6 +84,9 @@ public class SlotView extends GLView {
     public static final int OVERSCROLL_SYSTEM = 1;
     public static final int OVERSCROLL_NONE = 2;
 
+    // to prevent allocating memory
+    private final Rect mTempRect = new Rect();
+
     public SlotView(Context context, Spec spec) {
         mGestureDetector =
                 new GestureDetector(context, new MyGestureListener());
@@ -105,7 +108,7 @@ public class SlotView extends GLView {
         if (index < 0 || index >= slotCount) {
             return;
         }
-        Rect rect = mLayout.getSlotRect(index);
+        Rect rect = mLayout.getSlotRect(index, mTempRect);
         int position = WIDE
                 ? (rect.left + rect.right - getWidth()) / 2
                 : (rect.top + rect.bottom - getHeight()) / 2;
@@ -113,7 +116,7 @@ public class SlotView extends GLView {
     }
 
     public void makeSlotVisible(int index) {
-        Rect rect = mLayout.getSlotRect(index);
+        Rect rect = mLayout.getSlotRect(index, mTempRect);
         int visibleBegin = WIDE ? mScrollX : mScrollY;
         int visibleLength = WIDE ? getWidth() : getHeight();
         int visibleEnd = visibleBegin + visibleLength;
@@ -198,7 +201,7 @@ public class SlotView extends GLView {
     }
 
     public Rect getSlotRect(int slotIndex) {
-        return mLayout.getSlotRect(slotIndex);
+        return mLayout.getSlotRect(slotIndex, new Rect());
     }
 
     @Override
@@ -315,7 +318,7 @@ public class SlotView extends GLView {
     private int renderItem(
             GLCanvas canvas, int index, int pass, boolean paperActive) {
         canvas.save(GLCanvas.SAVE_FLAG_ALPHA | GLCanvas.SAVE_FLAG_MATRIX);
-        Rect rect = getSlotRect(index);
+        Rect rect = mLayout.getSlotRect(index, mTempRect);
         if (paperActive) {
             canvas.multiplyMatrix(mPaper.getTransform(rect, mScrollX), 0);
         } else {
@@ -443,7 +446,7 @@ public class SlotView extends GLView {
             return vPadding != mVerticalPadding || hPadding != mHorizontalPadding;
         }
 
-        public Rect getSlotRect(int index) {
+        public Rect getSlotRect(int index, Rect rect) {
             int col, row;
             if (WIDE) {
                 col = index / mUnitCount;
@@ -455,7 +458,8 @@ public class SlotView extends GLView {
 
             int x = mHorizontalPadding + col * (mSlotWidth + mSlotGap);
             int y = mVerticalPadding + row * (mSlotHeight + mSlotGap);
-            return new Rect(x, y, x + mSlotWidth, y + mSlotHeight);
+            rect.set(x, y, x + mSlotWidth, y + mSlotHeight);
+            return rect;
         }
 
         public int getSlotWidth() {
diff --git a/src/com/android/gallery3d/ui/TextureUploader.java b/src/com/android/gallery3d/ui/TextureUploader.java
new file mode 100644 (file)
index 0000000..a372eab
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.ui;
+
+import com.android.gallery3d.ui.GLRoot.OnGLIdleListener;
+
+import java.util.ArrayDeque;
+
+public class TextureUploader implements OnGLIdleListener {
+    private static final int INIT_CAPACITY = 64;
+    private static final int QUOTA_PER_FRAME = 1;
+
+    private final ArrayDeque<UploadedTexture> mFgTextures =
+            new ArrayDeque<UploadedTexture>(INIT_CAPACITY);
+    private final ArrayDeque<UploadedTexture> mBgTextures =
+            new ArrayDeque<UploadedTexture>(INIT_CAPACITY);
+    private final GLRoot mGLRoot;
+    private transient boolean mIsQueued = false;
+
+    public TextureUploader(GLRoot root) {
+        mGLRoot = root;
+    }
+
+    public synchronized void clear() {
+        mFgTextures.clear();
+        mBgTextures.clear();
+    }
+
+    // caller should hold synchronized on "this"
+    private void queueSelfIfNeed() {
+        if (mIsQueued) return;
+        mIsQueued = true;
+        mGLRoot.addOnGLIdleListener(this);
+    }
+
+    public synchronized void addBgTexture(UploadedTexture t) {
+        mBgTextures.addLast(t);
+        queueSelfIfNeed();
+    }
+
+    public synchronized void addFgTexture(UploadedTexture t) {
+        mFgTextures.addLast(t);
+        queueSelfIfNeed();
+    }
+
+    private int upload(GLCanvas canvas, ArrayDeque<UploadedTexture> deque,
+            int uploadQuota, boolean isBackground) {
+        while (uploadQuota > 0) {
+            UploadedTexture t;
+            synchronized (this) {
+                if (deque.isEmpty()) break;
+                t = deque.removeFirst();
+            }
+            if (!t.isContentValid(canvas)) {
+                t.updateContent(canvas);
+
+                // It will took some more time for a texture to be drawn for
+                // the first time.
+                // Thus, when scrolling, if a new column appears on screen,
+                // it may cause a UI jank even these textures are uploaded.
+                if (isBackground) t.draw(canvas, 0, 0);
+                --uploadQuota;
+            }
+        }
+        return uploadQuota;
+    }
+
+    @Override
+    public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) {
+        int uploadQuota = QUOTA_PER_FRAME;
+        uploadQuota = upload(canvas, mFgTextures, uploadQuota, false);
+        if (uploadQuota < QUOTA_PER_FRAME) mGLRoot.requestRender();
+
+        // don't upload background texture if there is pending render request
+        if (!renderRequested) {
+            upload(canvas, mBgTextures, uploadQuota, true);
+        }
+        synchronized (this) {
+            mIsQueued = !mFgTextures.isEmpty() || !mBgTextures.isEmpty();
+            return mIsQueued;
+        }
+    }
+}
index d2ce167..610a346 100644 (file)
@@ -548,7 +548,8 @@ public class TileImageView extends GLView {
         AtomicBoolean mActive = new AtomicBoolean(false);
 
         @Override
-        public boolean onGLIdle(GLRoot root, GLCanvas canvas) {
+        public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) {
+            if (renderRequested) return false;
             int quota = UPLOAD_LIMIT;
             Tile tile;
             while (true) {