Enable invalidation of carousel textures.

Mirrors existing implementation of detail texture invalidation.  Also
fixes issue where eraseCurrent was being ignored.

Fixes http://b/3246908.

Change-Id: I7348536b43b1ccf6b7df2389a1100c3d5b1671a8
diff --git a/carousel/java/com/android/ex/carousel/CarouselController.java b/carousel/java/com/android/ex/carousel/CarouselController.java
index 7d213f7..d432e28 100644
--- a/carousel/java/com/android/ex/carousel/CarouselController.java
+++ b/carousel/java/com/android/ex/carousel/CarouselController.java
@@ -350,6 +350,24 @@
     }
 
     /**
+     * Sets the specified texture as invalid. If {@code eraseCurrent} is true,
+     * the texture will be immediately cleared from view and an invalidate
+     * handler will be called. If {@code eraseCurrent} is false, a replacement
+     * texture will be requested, and the old texture will be left in place in
+     * the meantime.
+     *
+     * @param n the card to invalidate the detail texture for
+     * @param eraseCurrent whether to erase the current texture
+     */
+    public void invalidateTexture(int n, boolean eraseCurrent) {
+        if (mRenderScript != null && mRS != null) {
+            if (DBG) Log.v(TAG, "invalidateTexture(" + n + ", " + eraseCurrent + ")");
+            mRenderScript.invalidateTexture(n, eraseCurrent);
+            if (DBG) Log.v(TAG, "done");
+        }
+    }
+
+    /**
      * Sets the specified detail texture as invalid. If eraseCurrent is true, the texture will be
      * immediately cleared from view and an invalidate handler will be called. If eraseCurrent is
      * false, a replacement texture will be requested, and the old texture will be left in place
@@ -359,8 +377,8 @@
      */
     public void invalidateDetailTexture(int n, boolean eraseCurrent) {
         if (mRenderScript != null && mRS != null) {
-            if (DBG) Log.v(TAG, "invalidateDetailTexture(" + n + ")");
-            mRenderScript.invalidateDetailTexture(n, false);
+            if (DBG) Log.v(TAG, "invalidateDetailTexture(" + n + ", " + eraseCurrent + ")");
+            mRenderScript.invalidateDetailTexture(n, eraseCurrent);
             if (DBG) Log.v(TAG, "done");
         }
     }
diff --git a/carousel/java/com/android/ex/carousel/CarouselRS.java b/carousel/java/com/android/ex/carousel/CarouselRS.java
index a0cffdb..8e0c644 100644
--- a/carousel/java/com/android/ex/carousel/CarouselRS.java
+++ b/carousel/java/com/android/ex/carousel/CarouselRS.java
@@ -578,7 +578,7 @@
                 height = bitmap.getHeight();
             } else {
                 if (item.detailTexture != null) {
-                    if (DBG) Log.v(TAG, "unloading texture " + n);
+                    if (DBG) Log.v(TAG, "unloading detail texture " + n);
                     // Don't wait for GC to free native memory.
                     // Only works if textures are not shared.
                     item.detailTexture.destroy();
@@ -590,6 +590,28 @@
         }
     }
 
+    void invalidateTexture(int n, boolean eraseCurrent)
+    {
+        if (n < 0) throw new IllegalArgumentException("Index cannot be negative");
+
+        synchronized(this) {
+            ScriptField_Card.Item item = mCards.get(n);
+            if (item == null) {
+                // This card was never created, so there's nothing to invalidate.
+                return;
+            }
+            if (eraseCurrent && item.texture != null) {
+                if (DBG) Log.v(TAG, "unloading texture " + n);
+                // Don't wait for GC to free native memory.
+                // Only works if textures are not shared.
+                item.texture.destroy();
+                item.texture = null;
+            }
+            mCards.set(item, n, false); // This is primarily used for reference counting.
+            mScript.invoke_invalidateTexture(n, eraseCurrent);
+        }
+    }
+
     void invalidateDetailTexture(int n, boolean eraseCurrent)
     {
         if (n < 0) throw new IllegalArgumentException("Index cannot be negative");
@@ -601,7 +623,7 @@
                 return;
             }
             if (eraseCurrent && item.detailTexture != null) {
-                if (DBG) Log.v(TAG, "unloading texture " + n);
+                if (DBG) Log.v(TAG, "unloading detail texture " + n);
                 // Don't wait for GC to free native memory.
                 // Only works if textures are not shared.
                 item.detailTexture.destroy();
diff --git a/carousel/java/com/android/ex/carousel/carousel.rs b/carousel/java/com/android/ex/carousel/carousel.rs
index 1de38de..0b92c82 100644
--- a/carousel/java/com/android/ex/carousel/carousel.rs
+++ b/carousel/java/com/android/ex/carousel/carousel.rs
@@ -449,8 +449,11 @@
 {
     if (n < 0 || n >= cardCount) return;
     rsSetObject(&cards[n].texture, texture);
+    if (cards[n].textureState != STATE_STALE &&
+        cards[n].textureState != STATE_UPDATING) {
+        cards[n].textureTimeStamp = rsUptimeMillis();
+    }
     cards[n].textureState = (texture.p != 0) ? STATE_LOADED : STATE_INVALID;
-    cards[n].textureTimeStamp = rsUptimeMillis();
 }
 
 void setDetailTexture(int n, float offx, float offy, float loffx, float loffy, rs_allocation texture)
@@ -468,6 +471,17 @@
     cards[n].detailTextureState = (texture.p != 0) ? STATE_LOADED : STATE_INVALID;
 }
 
+void invalidateTexture(int n, bool eraseCurrent)
+{
+    if (n < 0 || n >= cardCount) return;
+    if (eraseCurrent) {
+        cards[n].textureState = STATE_INVALID;
+        rsClearObject(&cards[n].texture);
+    } else {
+        cards[n].textureState = STATE_STALE;
+    }
+}
+
 void invalidateDetailTexture(int n, bool eraseCurrent)
 {
     if (n < 0 || n >= cardCount) return;
@@ -603,7 +617,9 @@
 
             // Bind the appropriate shader network.  If there's no alpha blend, then
             // switch to single shader for better performance.
-            const bool loaded = cards[i].textureState == STATE_LOADED;
+            const int state = cards[i].textureState;
+            const bool loaded = (state == STATE_LOADED) || (state == STATE_STALE) ||
+                (state == STATE_UPDATING);
             if (shaderConstants->fadeAmount == 1.0f || shaderConstants->fadeAmount < 0.01f) {
                 rsgBindProgramFragment(singleTextureFragmentProgram);
                 rsgBindTexture(singleTextureFragmentProgram, 0,
@@ -1422,6 +1438,14 @@
                 } else {
                     if (debugTextureLoading) rsDebug("Couldn't send CMD_REQUEST_TEXTURE", 0);
                 }
+            } else if (cards[i].textureState == STATE_STALE) {
+                data[0] = i;
+                bool enqueued = rsSendToClient(CMD_REQUEST_TEXTURE, data, sizeof(data));
+                if (enqueued) {
+                    cards[i].textureState = STATE_UPDATING;
+                } else {
+                    if (debugTextureLoading) rsDebug("Couldn't send CMD_REQUEST_TEXTURE", 0);
+                }
             }
             // request detail texture from client if not loaded
             if (cards[i].detailTextureState == STATE_INVALID) {