Adjust loading priority and throttle texture upload to make animation more smooth.

Change-Id: Ibed8d7250aaa3530386c8968a9cab8d29a35ac9a
diff --git a/new3d/src/com/android/gallery3d/data/ImageService.java b/new3d/src/com/android/gallery3d/data/ImageService.java
index 1465f23..485db0d 100644
--- a/new3d/src/com/android/gallery3d/data/ImageService.java
+++ b/new3d/src/com/android/gallery3d/data/ImageService.java
@@ -18,6 +18,7 @@
 
 import android.content.ContentResolver;
 import android.graphics.Bitmap;
+import android.os.Process;
 import android.provider.MediaStore.Images;
 import android.provider.MediaStore.Video;
 
@@ -28,6 +29,7 @@
 
 import java.util.concurrent.Executor;
 import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -42,7 +44,8 @@
 
     private final Executor mExecutor = new ThreadPoolExecutor(
             CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_TIME,
-            TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
+            TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(),
+            new MyThreadFactory());
 
     private final ContentResolver mContentResolver;
 
@@ -229,4 +232,14 @@
         }
     }
 
+    class MyThreadFactory implements ThreadFactory {
+        public Thread newThread(final Runnable r) {
+            return new Thread() {
+                public void run() {
+                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+                    r.run();
+                }
+            };
+        }
+    }
 }
diff --git a/new3d/src/com/android/gallery3d/data/LocalAlbum.java b/new3d/src/com/android/gallery3d/data/LocalAlbum.java
index 2a1078e..2609f05 100644
--- a/new3d/src/com/android/gallery3d/data/LocalAlbum.java
+++ b/new3d/src/com/android/gallery3d/data/LocalAlbum.java
@@ -94,6 +94,7 @@
         Uri uri = mBaseUri.buildUpon()
                 .appendQueryParameter("limit", start + "," + count).build();
         ArrayList<MediaItem> list = new ArrayList<MediaItem>();
+        Utils.assertNotInRenderThread();
         Cursor cursor = mResolver.query(
                 uri, mProjection, mWhereClause,
                 new String[]{String.valueOf(mBucketId)},
@@ -116,6 +117,7 @@
 
     @Override
     public int getMediaItemCount() {
+        Utils.assertNotInRenderThread();
         Cursor cursor = mResolver.query(
                 mBaseUri, COUNT_PROJECTION, mWhereClause,
                 new String[]{String.valueOf(mBucketId)}, null);
@@ -196,6 +198,7 @@
     public void delete(long uniqueId) {
         Utils.Assert(DataManager.extractParentId(uniqueId) == getMyId());
         int itemId = DataManager.extractSelfId(uniqueId);
+        Utils.assertNotInRenderThread();
         mResolver.delete(mBaseUri, DELETE_ITEM_WHERE_CLAUSE,
                 new String[] {String.valueOf(itemId)});
     }
@@ -205,6 +208,7 @@
     }
 
     public void deleteSelf() {
+        Utils.assertNotInRenderThread();
         mResolver.delete(mBaseUri, mWhereClause,
                 new String[]{String.valueOf(mBucketId)});
     }
diff --git a/new3d/src/com/android/gallery3d/data/LocalAlbumSet.java b/new3d/src/com/android/gallery3d/data/LocalAlbumSet.java
index b6333b5..e88dda1 100644
--- a/new3d/src/com/android/gallery3d/data/LocalAlbumSet.java
+++ b/new3d/src/com/android/gallery3d/data/LocalAlbumSet.java
@@ -104,6 +104,7 @@
     protected void onLoadFromDatabase() {
         Uri uri = mBaseUri.buildUpon().
                 appendQueryParameter("distinct", "true").build();
+        Utils.assertNotInRenderThread();
         Cursor cursor = mResolver.query(
                 uri, mProjection, null, null, null);
         if (cursor == null) throw new NullPointerException();
diff --git a/new3d/src/com/android/gallery3d/data/LocalImage.java b/new3d/src/com/android/gallery3d/data/LocalImage.java
index d8e3394..36e1384 100644
--- a/new3d/src/com/android/gallery3d/data/LocalImage.java
+++ b/new3d/src/com/android/gallery3d/data/LocalImage.java
@@ -33,7 +33,7 @@
     private static final int MICRO_TARGET_PIXELS = 128 * 128;
 
     private static final int FULLIMAGE_TARGET_SIZE = 2048;
-    private static final int FULLIMAGE_MAX_NUM_PIXELS = 5 * 1024 * 1024;
+    private static final int FULLIMAGE_MAX_NUM_PIXELS = 3 * 1024 * 1024;
     private static final String TAG = "LocalImage";
 
     // Must preserve order between these indices and the order of the terms in
diff --git a/new3d/src/com/android/gallery3d/data/PicasaAlbum.java b/new3d/src/com/android/gallery3d/data/PicasaAlbum.java
index c560aca..bc989fd 100644
--- a/new3d/src/com/android/gallery3d/data/PicasaAlbum.java
+++ b/new3d/src/com/android/gallery3d/data/PicasaAlbum.java
@@ -65,6 +65,7 @@
                 .appendQueryParameter("limit", start + "," + count).build();
 
         ArrayList<MediaItem> list = new ArrayList<MediaItem>();
+        Utils.assertNotInRenderThread();
         Cursor cursor = mResolver.query(uri,
                 SCHEMA.getProjection(), WHERE_CLAUSE,
                 new String[]{String.valueOf(mData.id)},
@@ -86,6 +87,7 @@
 
     @Override
     public int getMediaItemCount() {
+        Utils.assertNotInRenderThread();
         Cursor cursor = mResolver.query(
                 PicasaContentProvider.PHOTOS_URI,
                 COUNT_PROJECTION, WHERE_CLAUSE,
diff --git a/new3d/src/com/android/gallery3d/data/PicasaAlbumSet.java b/new3d/src/com/android/gallery3d/data/PicasaAlbumSet.java
index 017fd22..d4db0fa 100644
--- a/new3d/src/com/android/gallery3d/data/PicasaAlbumSet.java
+++ b/new3d/src/com/android/gallery3d/data/PicasaAlbumSet.java
@@ -24,6 +24,7 @@
 import com.android.gallery3d.picasa.AlbumEntry;
 import com.android.gallery3d.picasa.EntrySchema;
 import com.android.gallery3d.picasa.PicasaContentProvider;
+import com.android.gallery3d.util.Utils;
 
 import java.util.ArrayList;
 
@@ -72,6 +73,7 @@
 
     @Override
     protected void onLoadFromDatabase() {
+        Utils.assertNotInRenderThread();
         Cursor cursor = mResolver.query(
                 PicasaContentProvider.ALBUMS_URI,
                 SCHEMA.getProjection(), null, null, null);
diff --git a/new3d/src/com/android/gallery3d/ui/AlbumSlidingWindow.java b/new3d/src/com/android/gallery3d/ui/AlbumSlidingWindow.java
index ec65446..fde83da 100644
--- a/new3d/src/com/android/gallery3d/ui/AlbumSlidingWindow.java
+++ b/new3d/src/com/android/gallery3d/ui/AlbumSlidingWindow.java
@@ -231,7 +231,9 @@
                 if (mActiveRequestCount == 0) requestNonactiveImages();
             }
             if (bitmap != null) {
-                updateContent(new BitmapTexture(bitmap));
+                BitmapTexture texture = new BitmapTexture(bitmap);
+                texture.setThrottled(true);
+                updateContent(texture);
                 if (mListener != null) mListener.onContentInvalidated();
             }
         }
diff --git a/new3d/src/com/android/gallery3d/ui/BasicTexture.java b/new3d/src/com/android/gallery3d/ui/BasicTexture.java
index 276d949..35b633a 100644
--- a/new3d/src/com/android/gallery3d/ui/BasicTexture.java
+++ b/new3d/src/com/android/gallery3d/ui/BasicTexture.java
@@ -109,7 +109,7 @@
 
     // onBind is called before GLCanvas binds this texture.
     // It should make sure the data is uploaded to GL memory.
-    abstract protected void onBind(GLCanvas canvas);
+    abstract protected boolean onBind(GLCanvas canvas);
 
     public boolean isLoaded(GLCanvas canvas) {
         return mState == STATE_LOADED && mCanvasRef.get() == canvas;
diff --git a/new3d/src/com/android/gallery3d/ui/GLCanvasImp.java b/new3d/src/com/android/gallery3d/ui/GLCanvasImp.java
index 0fe08fb..0079802 100644
--- a/new3d/src/com/android/gallery3d/ui/GLCanvasImp.java
+++ b/new3d/src/com/android/gallery3d/ui/GLCanvasImp.java
@@ -227,7 +227,7 @@
         if (chunk.mDivX.length != 2 || chunk.mDivY.length != 2) {
             throw new RuntimeException("unsupported nine patch");
         }
-        bindTexture(tex);
+        if (!bindTexture(tex)) return;
         if (width <= 0 || height <= 0) return;
 
         int divX[] = mNinePatchX;
@@ -476,7 +476,7 @@
         if (width <= 0 || height <= 0) return;
 
         mGLState.setBlendEnabled(!texture.isOpaque() || alpha < OPAQUE_ALPHA);
-        bindTexture(texture);
+        if (!bindTexture(texture)) return;
         mGLState.setTextureAlpha(alpha);
         drawBoundTexture(texture, x, y, width, height);
     }
@@ -491,7 +491,7 @@
         target = mDrawTextureTargetRect;
 
         mGLState.setBlendEnabled(!texture.isOpaque() || mAlpha < OPAQUE_ALPHA);
-        bindTexture(texture);
+        if (!bindTexture(texture)) return;
         convertCoordinate(source, target, texture);
         setTextureCoords(source);
         mGLState.setTextureAlpha(mAlpha);
@@ -538,10 +538,11 @@
         drawMixed(from, to, ratio, x, y, w, h, mAlpha);
     }
 
-    private void bindTexture(BasicTexture texture) {
-        texture.onBind(this);
+    private boolean bindTexture(BasicTexture texture) {
+        if (!texture.onBind(this)) return false;
         mGLState.setTexture2DEnabled(true);
         mGL.glBindTexture(GL11.GL_TEXTURE_2D, texture.getId());
+        return true;
     }
 
     private void setTextureColor(float r, float g, float b, float alpha) {
@@ -572,7 +573,7 @@
                 || !to.isOpaque() || alpha < OPAQUE_ALPHA);
 
         final GL11 gl = mGL;
-        bindTexture(from);
+        if (!bindTexture(from)) return;
 
         //
         // The formula we want:
@@ -593,7 +594,13 @@
         }
 
         gl.glActiveTexture(GL11.GL_TEXTURE1);
-        bindTexture(to);
+        if (!bindTexture(to)) {
+            // Disable TEXTURE1.
+            gl.glDisable(GL11.GL_TEXTURE_2D);
+            // Switch back to the default texture unit.
+            gl.glActiveTexture(GL11.GL_TEXTURE0);
+            return;
+        }
         gl.glEnable(GL11.GL_TEXTURE_2D);
 
         // Interpolate the RGB and alpha values between both textures.
diff --git a/new3d/src/com/android/gallery3d/ui/GLRootView.java b/new3d/src/com/android/gallery3d/ui/GLRootView.java
index 0917b53..8d6f917 100644
--- a/new3d/src/com/android/gallery3d/ui/GLRootView.java
+++ b/new3d/src/com/android/gallery3d/ui/GLRootView.java
@@ -21,6 +21,7 @@
 import android.graphics.Rect;
 import android.opengl.GLSurfaceView;
 import android.os.SystemClock;
+import android.os.Process;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -86,6 +87,9 @@
 
     private ReentrantLock mRenderLock = new ReentrantLock();
 
+    private static final int TARGET_FRAME_TIME = 33;
+    private long mLastDrawFinishTime;
+
     public GLRootView(Context context) {
         this(context, null);
     }
@@ -219,6 +223,8 @@
     public void onSurfaceChanged(GL10 gl1, int width, int height) {
         Log.v(TAG, "onSurfaceChanged: " + width + "x" + height
                 + ", gl10: " + gl1.toString());
+        Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY);
+        Utils.setRenderThread();
         GL11 gl = (GL11) gl1;
         Utils.Assert(mGL == gl);
 
@@ -242,12 +248,22 @@
     }
 
     public void onDrawFrame(GL10 gl) {
+        long begin = SystemClock.uptimeMillis();
         mRenderLock.lock();
         try {
             onDrawFrameLocked(gl);
         } finally {
             mRenderLock.unlock();
         }
+        long end = SystemClock.uptimeMillis();
+
+        if (mLastDrawFinishTime != 0) {
+            long wait = mLastDrawFinishTime + TARGET_FRAME_TIME - end;
+            if (wait > 0) {
+                SystemClock.sleep(wait);
+            }
+        }
+        mLastDrawFinishTime = SystemClock.uptimeMillis();
     }
 
     private void onDrawFrameLocked(GL10 gl) {
@@ -256,6 +272,9 @@
         // release the unbound textures
         mCanvas.deleteRecycledTextures();
 
+        // reset texture upload limit
+        UploadedTexture.resetUploadLimit();
+
         mRenderRequested = false;
 
         if ((mFlags & FLAG_NEED_LAYOUT) != 0) layoutContentPane();
@@ -282,6 +301,10 @@
             mAnimations.clear();
         }
 
+        if (UploadedTexture.uploadLimitReached()) {
+            requestRender();
+        }
+
         if (!mRenderRequested
                 && !mIdleRunner.mActive && !mIdleListeners.isEmpty()) {
             mIdleRunner.mActive = true;
diff --git a/new3d/src/com/android/gallery3d/ui/GallerySlidingWindow.java b/new3d/src/com/android/gallery3d/ui/GallerySlidingWindow.java
index 02f84bc..ce8e282 100644
--- a/new3d/src/com/android/gallery3d/ui/GallerySlidingWindow.java
+++ b/new3d/src/com/android/gallery3d/ui/GallerySlidingWindow.java
@@ -291,7 +291,9 @@
                 if (mActiveRequestCount == 0) requestNonactiveImages();
             }
             if (bitmap != null) {
-                updateContent(new BitmapTexture(bitmap));
+                BitmapTexture texture = new BitmapTexture(bitmap);
+                texture.setThrottled(true);
+                updateContent(texture);
                 if (mListener != null) mListener.onContentInvalidated();
             }
         }
diff --git a/new3d/src/com/android/gallery3d/ui/RawTexture.java b/new3d/src/com/android/gallery3d/ui/RawTexture.java
index eee3a10..c1be435 100644
--- a/new3d/src/com/android/gallery3d/ui/RawTexture.java
+++ b/new3d/src/com/android/gallery3d/ui/RawTexture.java
@@ -36,10 +36,11 @@
     }
 
     @Override
-    protected void onBind(GLCanvas canvas) {
+    protected boolean onBind(GLCanvas canvas) {
         if (mCanvasRef.get() != canvas) {
             throw new RuntimeException("cannot bind to different canvas");
         }
+        return true;
     }
 
     public boolean isOpaque() {
diff --git a/new3d/src/com/android/gallery3d/ui/UploadedTexture.java b/new3d/src/com/android/gallery3d/ui/UploadedTexture.java
index 5121ec1..f9248e5 100644
--- a/new3d/src/com/android/gallery3d/ui/UploadedTexture.java
+++ b/new3d/src/com/android/gallery3d/ui/UploadedTexture.java
@@ -44,6 +44,9 @@
     private static final String TAG = "Texture";
     private boolean mContentValid = true;
     private boolean mOpaque = true;
+    private boolean mThrottled = false;
+    private static int sUploadedCount;
+    private static final int UPLOAD_LIMIT = 1;
 
     protected Bitmap mBitmap;
 
@@ -51,6 +54,10 @@
         super(null, 0, STATE_UNLOADED);
     }
 
+    protected void setThrottled(boolean throttled) {
+        mThrottled = throttled;
+    }
+
     private Bitmap getBitmap() {
         if (mBitmap == null) {
             mBitmap = onGetBitmap();
@@ -104,6 +111,9 @@
      */
     public void updateContent(GLCanvas canvas) {
         if (!isLoaded(canvas)) {
+            if (mThrottled && ++sUploadedCount > UPLOAD_LIMIT) {
+                return;
+            }
             uploadToCanvas(canvas);
         } else if (!mContentValid) {
             Bitmap bitmap = getBitmap();
@@ -117,6 +127,14 @@
         }
     }
 
+    public static void resetUploadLimit() {
+        sUploadedCount = 0;
+    }
+
+    public static boolean uploadLimitReached() {
+        return sUploadedCount > UPLOAD_LIMIT;
+    }
+
     static int[] sTextureId = new int[1];
     static int[] sCropRect = new int[4];
 
@@ -175,8 +193,9 @@
     }
 
     @Override
-    protected void onBind(GLCanvas canvas) {
+    protected boolean onBind(GLCanvas canvas) {
         updateContent(canvas);
+        return isContentValid(canvas);
     }
 
     public void setOpaque(boolean isOpaque) {
diff --git a/new3d/src/com/android/gallery3d/util/Utils.java b/new3d/src/com/android/gallery3d/util/Utils.java
index abdbe71..691813e 100644
--- a/new3d/src/com/android/gallery3d/util/Utils.java
+++ b/new3d/src/com/android/gallery3d/util/Utils.java
@@ -6,7 +6,9 @@
 import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
 import android.graphics.Bitmap.Config;
+import android.os.SystemClock;
 import android.util.DisplayMetrics;
+import android.util.Log;
 
 import java.nio.charset.Charset;
 
@@ -221,4 +223,23 @@
     public static byte[] getBytesInUtf8(String in) {
         return sUtf8Codec.encode(in).array();
     }
+
+    // Below are used the detect using database in the render thread. It only
+    // works most of the time, but that's ok because it's for debugging only.
+
+    private static volatile Thread sCurrentThread;
+    private static volatile boolean sWarned;
+
+    public static void setRenderThread() {
+        sCurrentThread = Thread.currentThread();
+    }
+
+    public static void assertNotInRenderThread() {
+        if (!sWarned) {
+            if (Thread.currentThread() == sCurrentThread) {
+                sWarned = true;
+                Log.w(TAG, new Throwable("Should not do this in render thread"));
+            }
+        }
+    }
 }
diff --git a/new3d/tests/src/com/android/gallery3d/ui/GLCanvasTest.java b/new3d/tests/src/com/android/gallery3d/ui/GLCanvasTest.java
index 5f0ea80..ce4d0bc 100644
--- a/new3d/tests/src/com/android/gallery3d/ui/GLCanvasTest.java
+++ b/new3d/tests/src/com/android/gallery3d/ui/GLCanvasTest.java
@@ -498,8 +498,9 @@
         }
 
         @Override
-        protected void onBind(GLCanvas canvas) {
+        protected boolean onBind(GLCanvas canvas) {
             mBindCalled++;
+            return true;
         }
 
         public boolean isOpaque() {
diff --git a/new3d/tests/src/com/android/gallery3d/ui/TextureTest.java b/new3d/tests/src/com/android/gallery3d/ui/TextureTest.java
index fcc4a6c..bee401a 100644
--- a/new3d/tests/src/com/android/gallery3d/ui/TextureTest.java
+++ b/new3d/tests/src/com/android/gallery3d/ui/TextureTest.java
@@ -21,8 +21,9 @@
         }
 
         @Override
-        protected void onBind(GLCanvas canvas) {
+        protected boolean onBind(GLCanvas canvas) {
             mOnBindCalled++;
+            return true;
         }
 
         public boolean isOpaque() {
@@ -167,7 +168,8 @@
         }
 
         @Override
-        protected void onBind(GLCanvas canvas) {
+        protected boolean onBind(GLCanvas canvas) {
+            return true;
         }
 
         public boolean isOpaque() {