Add updateDetailTexture to allow updating detail textures

Change-Id: Id0d3146142bc7019523c038cacbfffb71db56594
diff --git a/carousel/java/com/android/ex/carousel/CarouselController.java b/carousel/java/com/android/ex/carousel/CarouselController.java
index 09e51ca..68b2f7c 100644
--- a/carousel/java/com/android/ex/carousel/CarouselController.java
+++ b/carousel/java/com/android/ex/carousel/CarouselController.java
@@ -320,6 +320,22 @@
     }
 
     /**
+     * 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
+     * in the meantime.
+     * @param n the card to invalidate the detail texture for
+     * @param eraseCurrent whether to erase the current texture
+     */
+    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, "done");
+        }
+    }
+
+    /**
      * Sets the bitmap to show on a card when the card draws the very first time.
      * Generally, this bitmap will only be seen during the first few frames of startup
      * or when the number of cards are changed.  It can be ignored in most cases,
diff --git a/carousel/java/com/android/ex/carousel/CarouselRS.java b/carousel/java/com/android/ex/carousel/CarouselRS.java
index a4e7ef3..b0f208d 100644
--- a/carousel/java/com/android/ex/carousel/CarouselRS.java
+++ b/carousel/java/com/android/ex/carousel/CarouselRS.java
@@ -19,8 +19,6 @@
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.renderscript.*;
-import android.renderscript.ProgramStore.BlendDstFunc;
-import android.renderscript.ProgramStore.BlendSrcFunc;
 import android.renderscript.RenderScript.RSMessage;
 import android.util.Log;
 
@@ -552,6 +550,27 @@
         }
     }
 
+    void invalidateDetailTexture(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) {
+                throw new IllegalStateException("invalidateDetailTexture without an existing card");
+            }
+            if (eraseCurrent && item.detailTexture != 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.detailTexture.destroy();
+                item.detailTexture = null;
+            }
+            mCards.set(item, n, false); // This is primarily used for reference counting.
+            mScript.invoke_invalidateDetailTexture(n, eraseCurrent);
+        }
+    }
+
     public void setGeometry(int n, Mesh geometry)
     {
         if (n < 0) throw new IllegalArgumentException("Index cannot be negative");
diff --git a/carousel/java/com/android/ex/carousel/carousel.rs b/carousel/java/com/android/ex/carousel/carousel.rs
index 99da954..bb846ce 100644
--- a/carousel/java/com/android/ex/carousel/carousel.rs
+++ b/carousel/java/com/android/ex/carousel/carousel.rs
@@ -73,6 +73,8 @@
 enum {
     STATE_INVALID = 0, // item hasn't been loaded
     STATE_LOADING, // we've requested an item but are waiting for it to load
+    STATE_STALE, // we have an old item, but should request an update
+    STATE_UPDATING, // we've requested an update, and will display the old one in the meantime
     STATE_LOADED // item was delivered
 };
 
@@ -190,8 +192,8 @@
 
 #pragma rs export_func(createCards, copyCards, lookAt, setRadius)
 #pragma rs export_func(doStart, doStop, doMotion, doLongPress)
-#pragma rs export_func(setTexture, setGeometry, setDetailTexture, debugCamera)
-#pragma rs export_func(setCarouselRotationAngle)
+#pragma rs export_func(setTexture, setGeometry, setDetailTexture, invalidateDetailTexture)
+#pragma rs export_func(debugCamera, setCarouselRotationAngle)
 
 // Local variables
 static float bias; // rotation bias, in radians. Used for animation and dragging.
@@ -435,12 +437,26 @@
 {
     if (n < 0 || n >= cardCount) return;
     rsSetObject(&cards[n].detailTexture, texture);
+    if (cards[n].detailTextureState != STATE_STALE &&
+        cards[n].detailTextureState != STATE_UPDATING) {
+        cards[n].detailTextureTimeStamp = rsUptimeMillis();
+    }
     cards[n].detailTextureOffset.x = offx;
     cards[n].detailTextureOffset.y = offy;
     cards[n].detailLineOffset.x = loffx;
     cards[n].detailLineOffset.y = loffy;
     cards[n].detailTextureState = (texture.p != 0) ? STATE_LOADED : STATE_INVALID;
-    cards[n].detailTextureTimeStamp = rsUptimeMillis();
+}
+
+void invalidateDetailTexture(int n, bool eraseCurrent)
+{
+    if (n < 0 || n >= cardCount) return;
+    if (eraseCurrent) {
+        cards[n].detailTextureState = STATE_INVALID;
+        rsClearObject(&cards[n].detailTexture);
+    } else {
+        cards[n].detailTextureState = STATE_STALE;
+    }
 }
 
 void setGeometry(int n, rs_mesh geometry)
@@ -645,7 +661,10 @@
 
     for (int i = cardCount-1; i >= 0; --i) {
         if (cards[i].cardVisible) {
-            if (cards[i].detailTextureState == STATE_LOADED && cards[i].detailTexture.p != 0) {
+            const int state = cards[i].detailTextureState;
+            const bool isLoaded = (state == STATE_LOADED) || (state == STATE_STALE) ||
+                (state == STATE_UPDATING);
+            if (isLoaded && cards[i].detailTexture.p != 0) {
                 const float lineWidth = rsAllocationGetDimX(detailLineTexture);
 
                 // Compute position in screen space of top corner or bottom corner of card
@@ -1379,6 +1398,14 @@
                 } else {
                     if (debugTextureLoading) rsDebug("Couldn't send CMD_REQUEST_DETAIL_TEXTURE", 0);
                 }
+            } else if (cards[i].detailTextureState == STATE_STALE) {
+                data[0] = i;
+                bool enqueued = rsSendToClient(CMD_REQUEST_DETAIL_TEXTURE, data, sizeof(data));
+                if (enqueued) {
+                    cards[i].detailTextureState = STATE_UPDATING;
+                } else {
+                    if (debugTextureLoading) rsDebug("Couldn't send CMD_REQUEST_DETAIL_TEXTURE", 0);
+                }
             }
             // request geometry from client if not loaded
             if (cards[i].geometryState == STATE_INVALID) {