Merge "add animation and float variables Test: RemoteCompose CTS test Bug: 339721781" into main
diff --git a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
index 55f2dee..00262be 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
@@ -30,7 +30,7 @@
 
     ArrayList<Operation> mOperations;
     RemoteComposeState mRemoteComposeState = new RemoteComposeState();
-
+    TimeVariables mTimeVariables = new TimeVariables();
     // Semantic version of the document
     Version mVersion = new Version(0, 1, 0);
 
@@ -70,6 +70,7 @@
 
     public void setWidth(int width) {
         this.mWidth = width;
+        mRemoteComposeState.setWindowWidth(width);
     }
 
     public int getHeight() {
@@ -78,6 +79,8 @@
 
     public void setHeight(int height) {
         this.mHeight = height;
+        mRemoteComposeState.setWindowHeight(height);
+
     }
 
     public RemoteComposeBuffer getBuffer() {
@@ -111,21 +114,21 @@
     /**
      * Sets the way the player handles the content
      *
-     * @param scroll set the horizontal behavior (NONE|SCROLL_HORIZONTAL|SCROLL_VERTICAL)
+     * @param scroll    set the horizontal behavior (NONE|SCROLL_HORIZONTAL|SCROLL_VERTICAL)
      * @param alignment set the alignment of the content (TOP|CENTER|BOTTOM|START|END)
-     * @param sizing set the type of sizing for the content (NONE|SIZING_LAYOUT|SIZING_SCALE)
-     * @param mode set the mode of sizing, either LAYOUT modes or SCALE modes
-     *             the LAYOUT modes are:
-     *             - LAYOUT_MATCH_PARENT
-     *             - LAYOUT_WRAP_CONTENT
-     *             or adding an horizontal mode and a vertical mode:
-     *             - LAYOUT_HORIZONTAL_MATCH_PARENT
-     *             - LAYOUT_HORIZONTAL_WRAP_CONTENT
-     *             - LAYOUT_HORIZONTAL_FIXED
-     *             - LAYOUT_VERTICAL_MATCH_PARENT
-     *             - LAYOUT_VERTICAL_WRAP_CONTENT
-     *             - LAYOUT_VERTICAL_FIXED
-     *             The LAYOUT_*_FIXED modes will use the intrinsic document size
+     * @param sizing    set the type of sizing for the content (NONE|SIZING_LAYOUT|SIZING_SCALE)
+     * @param mode      set the mode of sizing, either LAYOUT modes or SCALE modes
+     *                  the LAYOUT modes are:
+     *                  - LAYOUT_MATCH_PARENT
+     *                  - LAYOUT_WRAP_CONTENT
+     *                  or adding an horizontal mode and a vertical mode:
+     *                  - LAYOUT_HORIZONTAL_MATCH_PARENT
+     *                  - LAYOUT_HORIZONTAL_WRAP_CONTENT
+     *                  - LAYOUT_HORIZONTAL_FIXED
+     *                  - LAYOUT_VERTICAL_MATCH_PARENT
+     *                  - LAYOUT_VERTICAL_WRAP_CONTENT
+     *                  - LAYOUT_VERTICAL_FIXED
+     *                  The LAYOUT_*_FIXED modes will use the intrinsic document size
      */
     public void setRootContentBehavior(int scroll, int alignment, int sizing, int mode) {
         this.mContentScroll = scroll;
@@ -138,8 +141,8 @@
      * Given dimensions w x h of where to paint the content, returns the corresponding scale factor
      * according to the contentSizing information
      *
-     * @param w horizontal dimension of the rendering area
-     * @param h vertical dimension of the rendering area
+     * @param w           horizontal dimension of the rendering area
+     * @param h           vertical dimension of the rendering area
      * @param scaleOutput will contain the computed scale factor
      */
     public void computeScale(float w, float h, float[] scaleOutput) {
@@ -154,37 +157,43 @@
                     float scale = Math.min(1f, Math.min(scaleX, scaleY));
                     contentScaleX = scale;
                     contentScaleY = scale;
-                } break;
+                }
+                break;
                 case RootContentBehavior.SCALE_FIT: {
                     float scaleX = w / mWidth;
                     float scaleY = h / mHeight;
                     float scale = Math.min(scaleX, scaleY);
                     contentScaleX = scale;
                     contentScaleY = scale;
-                } break;
+                }
+                break;
                 case RootContentBehavior.SCALE_FILL_WIDTH: {
                     float scale = w / mWidth;
                     contentScaleX = scale;
                     contentScaleY = scale;
-                } break;
+                }
+                break;
                 case RootContentBehavior.SCALE_FILL_HEIGHT: {
                     float scale = h / mHeight;
                     contentScaleX = scale;
                     contentScaleY = scale;
-                } break;
+                }
+                break;
                 case RootContentBehavior.SCALE_CROP: {
                     float scaleX = w / mWidth;
                     float scaleY = h / mHeight;
                     float scale = Math.max(scaleX, scaleY);
                     contentScaleX = scale;
                     contentScaleY = scale;
-                } break;
+                }
+                break;
                 case RootContentBehavior.SCALE_FILL_BOUNDS: {
                     float scaleX = w / mWidth;
                     float scaleY = h / mHeight;
                     contentScaleX = scaleX;
                     contentScaleY = scaleY;
-                } break;
+                }
+                break;
                 default:
                     // nothing
             }
@@ -197,10 +206,10 @@
      * Given dimensions w x h of where to paint the content, returns the corresponding translation
      * according to the contentAlignment information
      *
-     * @param w horizontal dimension of the rendering area
-     * @param h vertical dimension of the rendering area
-     * @param contentScaleX the horizontal scale we are going to use for the content
-     * @param contentScaleY the vertical scale we are going to use for the content
+     * @param w               horizontal dimension of the rendering area
+     * @param h               vertical dimension of the rendering area
+     * @param contentScaleX   the horizontal scale we are going to use for the content
+     * @param contentScaleY   the vertical scale we are going to use for the content
      * @param translateOutput will contain the computed translation
      */
     private void computeTranslate(float w, float h, float contentScaleX, float contentScaleY,
@@ -215,26 +224,32 @@
         switch (horizontalContentAlignment) {
             case RootContentBehavior.ALIGNMENT_START: {
                 // nothing
-            } break;
+            }
+            break;
             case RootContentBehavior.ALIGNMENT_HORIZONTAL_CENTER: {
                 translateX = (w - contentWidth) / 2f;
-            } break;
+            }
+            break;
             case RootContentBehavior.ALIGNMENT_END: {
                 translateX = w - contentWidth;
-            } break;
+            }
+            break;
             default:
                 // nothing (same as alignment_start)
         }
         switch (verticalContentAlignment) {
             case RootContentBehavior.ALIGNMENT_TOP: {
                 // nothing
-            } break;
+            }
+            break;
             case RootContentBehavior.ALIGNMENT_VERTICAL_CENTER: {
                 translateY = (h - contentHeight) / 2f;
-            } break;
+            }
+            break;
             case RootContentBehavior.ALIGNMENT_BOTTOM: {
                 translateY = h - contentHeight;
-            } break;
+            }
+            break;
             default:
                 // nothing (same as alignment_top)
         }
@@ -291,7 +306,13 @@
             this.mMetadata = metadata;
         }
 
-        public boolean contains(float x, float y)  {
+        /**
+         * Returns true if x,y coordinate is within bounds
+         * @param x x-coordinate
+         * @param y y-coordinate
+         * @return x,y coordinate is within bounds
+         */
+        public boolean contains(float x, float y) {
             return x >= mLeft && x < mRight
                     && y >= mTop && y < mBottom;
         }
@@ -341,16 +362,22 @@
     public void initializeContext(RemoteContext context) {
         mRemoteComposeState.reset();
         mClickAreas.clear();
-
+        mRemoteComposeState.setNextId(RemoteComposeState.START_ID);
         context.mDocument = this;
         context.mRemoteComposeState = mRemoteComposeState;
-
         // mark context to be in DATA mode, which will skip the painting ops.
         context.mMode = RemoteContext.ContextMode.DATA;
-        for (Operation op: mOperations) {
+        mTimeVariables.updateTime(context);
+
+        for (Operation op : mOperations) {
+            if (op instanceof VariableSupport) {
+                ((VariableSupport) op).updateVariables(context);
+                ((VariableSupport) op).registerListening(context);
+            }
             op.apply(context);
         }
         context.mMode = RemoteContext.ContextMode.UNSET;
+
     }
 
     ///////////////////////////////////////////////////////////////////////////////////////////////
@@ -375,7 +402,7 @@
      * @param minorVersion minor version number, increased when adding new features
      * @param patch        patch level, increased upon bugfixes
      */
-    void  setVersion(int majorVersion, int minorVersion, int patch) {
+    void setVersion(int majorVersion, int minorVersion, int patch) {
         mVersion = new Version(majorVersion, minorVersion, patch);
     }
 
@@ -389,13 +416,13 @@
      * the click coordinates will be the one reported; the order of addition of those click areas
      * is therefore meaningful.
      *
-     * @param id       the id of the area, which will be reported on click
+     * @param id                 the id of the area, which will be reported on click
      * @param contentDescription the content description (used for accessibility)
-     * @param left     the left coordinate of the click area (in pixels)
-     * @param top      the top coordinate of the click area (in pixels)
-     * @param right    the right coordinate of the click area (in pixels)
-     * @param bottom   the bottom coordinate of the click area (in pixels)
-     * @param metadata arbitrary metadata associated with the are, also reported on click
+     * @param left               the left coordinate of the click area (in pixels)
+     * @param top                the top coordinate of the click area (in pixels)
+     * @param right              the right coordinate of the click area (in pixels)
+     * @param bottom             the bottom coordinate of the click area (in pixels)
+     * @param metadata           arbitrary metadata associated with the are, also reported on click
      */
     public void addClickArea(int id, String contentDescription,
                              float left, float top, float right, float bottom, String metadata) {
@@ -417,7 +444,7 @@
      * listeners.
      */
     public void onClick(float x, float y) {
-        for (ClickAreaRepresentation clickArea: mClickAreas) {
+        for (ClickAreaRepresentation clickArea : mClickAreas) {
             if (clickArea.contains(x, y)) {
                 warnClickListeners(clickArea);
             }
@@ -430,7 +457,7 @@
      * @param id the click area id
      */
     public void performClick(int id) {
-        for (ClickAreaRepresentation clickArea: mClickAreas) {
+        for (ClickAreaRepresentation clickArea : mClickAreas) {
             if (clickArea.mId == id) {
                 warnClickListeners(clickArea);
             }
@@ -441,17 +468,36 @@
      * Warn click listeners when a click area is activated
      */
     private void warnClickListeners(ClickAreaRepresentation clickArea) {
-        for (ClickCallbacks listener: mClickListeners) {
+        for (ClickCallbacks listener : mClickListeners) {
             listener.click(clickArea.mId, clickArea.mMetadata);
         }
     }
 
-    ///////////////////////////////////////////////////////////////////////////////////////////////
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        for (Operation op : mOperations) {
+            builder.append(op.toString());
+            builder.append("\n");
+        }
+        return builder.toString();
+    }
+
+    //////////////////////////////////////////////////////////////////////////
     // Painting
-    ///////////////////////////////////////////////////////////////////////////////////////////////
+    //////////////////////////////////////////////////////////////////////////
 
     private final float[] mScaleOutput = new float[2];
     private final float[] mTranslateOutput = new float[2];
+    private int mRepaintNext = -1; // delay to next repaint -1 = don't 1 = asap
+
+    /**
+     * Returns > 0 if it needs to repaint
+     * @return
+     */
+    public int needsRepaint() {
+        return mRepaintNext;
+    }
 
     /**
      * Paint the document
@@ -475,6 +521,11 @@
             context.mPaintContext.translate(mTranslateOutput[0], mTranslateOutput[1]);
             context.mPaintContext.scale(mScaleOutput[0], mScaleOutput[1]);
         }
+        mTimeVariables.updateTime(context);
+        context.loadFloat(RemoteContext.ID_WINDOW_WIDTH, getWidth());
+        context.loadFloat(RemoteContext.ID_WINDOW_HEIGHT, getHeight());
+        mRepaintNext = context.updateOps();
+
         for (Operation op : mOperations) {
             // operations will only be executed if no theme is set (ie UNSPECIFIED)
             // or the theme is equal as the one passed in argument to paint.
diff --git a/core/java/com/android/internal/widget/remotecompose/core/Operations.java b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
index 54b277a..4b45ab6 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Operations.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
@@ -19,6 +19,7 @@
 import com.android.internal.widget.remotecompose.core.operations.ClickArea;
 import com.android.internal.widget.remotecompose.core.operations.ClipPath;
 import com.android.internal.widget.remotecompose.core.operations.ClipRect;
+import com.android.internal.widget.remotecompose.core.operations.ColorExpression;
 import com.android.internal.widget.remotecompose.core.operations.DrawArc;
 import com.android.internal.widget.remotecompose.core.operations.DrawBitmap;
 import com.android.internal.widget.remotecompose.core.operations.DrawBitmapInt;
@@ -28,9 +29,12 @@
 import com.android.internal.widget.remotecompose.core.operations.DrawPath;
 import com.android.internal.widget.remotecompose.core.operations.DrawRect;
 import com.android.internal.widget.remotecompose.core.operations.DrawRoundRect;
+import com.android.internal.widget.remotecompose.core.operations.DrawText;
+import com.android.internal.widget.remotecompose.core.operations.DrawTextAnchored;
 import com.android.internal.widget.remotecompose.core.operations.DrawTextOnPath;
-import com.android.internal.widget.remotecompose.core.operations.DrawTextRun;
 import com.android.internal.widget.remotecompose.core.operations.DrawTweenPath;
+import com.android.internal.widget.remotecompose.core.operations.FloatConstant;
+import com.android.internal.widget.remotecompose.core.operations.FloatExpression;
 import com.android.internal.widget.remotecompose.core.operations.Header;
 import com.android.internal.widget.remotecompose.core.operations.MatrixRestore;
 import com.android.internal.widget.remotecompose.core.operations.MatrixRotate;
@@ -42,7 +46,10 @@
 import com.android.internal.widget.remotecompose.core.operations.PathData;
 import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior;
 import com.android.internal.widget.remotecompose.core.operations.RootContentDescription;
+import com.android.internal.widget.remotecompose.core.operations.ShaderData;
 import com.android.internal.widget.remotecompose.core.operations.TextData;
+import com.android.internal.widget.remotecompose.core.operations.TextFromFloat;
+import com.android.internal.widget.remotecompose.core.operations.TextMerge;
 import com.android.internal.widget.remotecompose.core.operations.Theme;
 import com.android.internal.widget.remotecompose.core.operations.utilities.IntMap;
 
@@ -67,9 +74,10 @@
     public static final int DRAW_BITMAP = 44;
     public static final int DRAW_BITMAP_INT = 66;
     public static final int DATA_BITMAP = 101;
+    public static final int DATA_SHADER = 45;
     public static final int DATA_TEXT = 102;
 
-/////////////////////////////=====================
+    /////////////////////////////=====================
     public static final int CLIP_PATH = 38;
     public static final int CLIP_RECT = 39;
     public static final int PAINT_VALUES = 40;
@@ -91,6 +99,12 @@
     public static final int MATRIX_SAVE = 130;
     public static final int MATRIX_RESTORE = 131;
     public static final int MATRIX_SET = 132;
+    public static final int DATA_FLOAT = 80;
+    public static final int ANIMATED_FLOAT = 81;
+    public static final int DRAW_TEXT_ANCHOR = 133;
+    public static final int COLOR_EXPRESSIONS = 134;
+    public static final int TEXT_FROM_FLOAT = 135;
+    public static final int TEXT_MERGE = 136;
 
     /////////////////////////////////////////======================
     public static IntMap<CompanionOperation> map = new IntMap<>();
@@ -114,7 +128,7 @@
         map.put(DRAW_RECT, DrawRect.COMPANION);
         map.put(DRAW_ROUND_RECT, DrawRoundRect.COMPANION);
         map.put(DRAW_TEXT_ON_PATH, DrawTextOnPath.COMPANION);
-        map.put(DRAW_TEXT_RUN, DrawTextRun.COMPANION);
+        map.put(DRAW_TEXT_RUN, DrawText.COMPANION);
         map.put(DRAW_TWEEN_PATH, DrawTweenPath.COMPANION);
         map.put(DATA_PATH, PathData.COMPANION);
         map.put(PAINT_VALUES, PaintData.COMPANION);
@@ -126,6 +140,13 @@
         map.put(MATRIX_TRANSLATE, MatrixTranslate.COMPANION);
         map.put(CLIP_PATH, ClipPath.COMPANION);
         map.put(CLIP_RECT, ClipRect.COMPANION);
+        map.put(DATA_SHADER, ShaderData.COMPANION);
+        map.put(DATA_FLOAT, FloatConstant.COMPANION);
+        map.put(ANIMATED_FLOAT, FloatExpression.COMPANION);
+        map.put(DRAW_TEXT_ANCHOR, DrawTextAnchored.COMPANION);
+        map.put(COLOR_EXPRESSIONS, ColorExpression.COMPANION);
+        map.put(TEXT_FROM_FLOAT, TextFromFloat.COMPANION);
+        map.put(TEXT_MERGE, TextMerge.COMPANION);
 
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java b/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java
index eece8ad52..ecd0efc 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java
@@ -68,7 +68,35 @@
 
     public abstract void drawTextOnPath(int textId, int pathId, float hOffset, float vOffset);
 
-    public abstract void drawTextRun(int textID,
+    /**
+     * Return the dimensions (left, top, right, bottom).
+     * Relative to a drawTextRun x=0, y=0;
+     *
+     * @param textId
+     * @param start
+     * @param end    if end is -1 it means the whole string
+     * @param monospace measure with better support for monospace
+     * @param bounds the bounds (left, top, right, bottom)
+     */
+    public abstract void getTextBounds(int textId,
+                                       int start,
+                                       int end,
+                                       boolean monospace,
+                                       float[]bounds);
+
+    /**
+     * Draw a text starting ast x,y
+     *
+     * @param textId reference to the text
+     * @param start
+     * @param end
+     * @param contextStart
+     * @param contextEnd
+     * @param x
+     * @param y
+     * @param rtl
+     */
+    public abstract void drawTextRun(int textId,
                                      int start,
                                      int end,
                                      int contextStart,
@@ -77,6 +105,14 @@
                                      float y,
                                      boolean rtl);
 
+    /**
+     * Draw an interpolation between two paths
+     * @param path1Id
+     * @param path2Id
+     * @param tween  0.0 = is path1 1.0 is path2
+     * @param start
+     * @param stop
+     */
     public abstract void drawTweenPath(int path1Id,
                                        int path2Id,
                                        float tween,
@@ -85,21 +121,70 @@
 
     public abstract void applyPaint(PaintBundle mPaintData);
 
-    public abstract void mtrixScale(float scaleX, float scaleY, float centerX, float centerY);
+    /**
+     * Scale the rendering by scaleX and saleY (1.0 = no scale).
+     * Scaling is done about centerX,centerY.
+     *
+     * @param scaleX
+     * @param scaleY
+     * @param centerX
+     * @param centerY
+     */
+    public abstract void matrixScale(float scaleX, float scaleY, float centerX, float centerY);
 
+    /**
+     * Translate the rendering
+     * @param translateX
+     * @param translateY
+     */
     public abstract void matrixTranslate(float translateX, float translateY);
 
+    /**
+     * Skew the rendering
+     * @param skewX
+     * @param skewY
+     */
     public abstract void matrixSkew(float skewX, float skewY);
 
+    /**
+     * Rotate the rendering.
+     * Note rotates are cumulative.
+     * @param rotate angle to rotate
+     * @param pivotX x-coordinate about which to rotate
+     * @param pivotY y-coordinate about which to rotate
+     */
     public abstract void matrixRotate(float rotate, float pivotX, float pivotY);
 
+    /**
+     * Save the current state of the transform
+     */
     public abstract void matrixSave();
 
+    /**
+     * Restore the previously saved state of the transform
+     */
     public abstract void matrixRestore();
 
+    /**
+     * Set the clip to a rectangle.
+     * Drawing outside the current clip region will have no effect
+     * @param left
+     * @param top
+     * @param right
+     * @param bottom
+     */
     public abstract void clipRect(float left, float top, float right, float bottom);
 
+    /**
+     * Clip based on a path.
+     * @param pathId
+     * @param regionOp
+     */
     public abstract void clipPath(int pathId, int regionOp);
 
+    /**
+     * Reset the paint
+     */
+    public abstract void reset();
 }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
index c2e8131..52fc314 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
@@ -19,6 +19,7 @@
 import com.android.internal.widget.remotecompose.core.operations.ClickArea;
 import com.android.internal.widget.remotecompose.core.operations.ClipPath;
 import com.android.internal.widget.remotecompose.core.operations.ClipRect;
+import com.android.internal.widget.remotecompose.core.operations.ColorExpression;
 import com.android.internal.widget.remotecompose.core.operations.DrawArc;
 import com.android.internal.widget.remotecompose.core.operations.DrawBitmap;
 import com.android.internal.widget.remotecompose.core.operations.DrawBitmapInt;
@@ -28,9 +29,12 @@
 import com.android.internal.widget.remotecompose.core.operations.DrawPath;
 import com.android.internal.widget.remotecompose.core.operations.DrawRect;
 import com.android.internal.widget.remotecompose.core.operations.DrawRoundRect;
+import com.android.internal.widget.remotecompose.core.operations.DrawText;
+import com.android.internal.widget.remotecompose.core.operations.DrawTextAnchored;
 import com.android.internal.widget.remotecompose.core.operations.DrawTextOnPath;
-import com.android.internal.widget.remotecompose.core.operations.DrawTextRun;
 import com.android.internal.widget.remotecompose.core.operations.DrawTweenPath;
+import com.android.internal.widget.remotecompose.core.operations.FloatConstant;
+import com.android.internal.widget.remotecompose.core.operations.FloatExpression;
 import com.android.internal.widget.remotecompose.core.operations.Header;
 import com.android.internal.widget.remotecompose.core.operations.MatrixRestore;
 import com.android.internal.widget.remotecompose.core.operations.MatrixRotate;
@@ -43,8 +47,12 @@
 import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior;
 import com.android.internal.widget.remotecompose.core.operations.RootContentDescription;
 import com.android.internal.widget.remotecompose.core.operations.TextData;
+import com.android.internal.widget.remotecompose.core.operations.TextFromFloat;
+import com.android.internal.widget.remotecompose.core.operations.TextMerge;
 import com.android.internal.widget.remotecompose.core.operations.Theme;
+import com.android.internal.widget.remotecompose.core.operations.Utils;
 import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
+import com.android.internal.widget.remotecompose.core.operations.utilities.easing.FloatAnimation;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -58,9 +66,20 @@
  * Provides an abstract buffer to encode/decode RemoteCompose operations
  */
 public class RemoteComposeBuffer {
+    public static final int EASING_CUBIC_STANDARD = FloatAnimation.CUBIC_STANDARD;
+    public static final int EASING_CUBIC_ACCELERATE = FloatAnimation.CUBIC_ACCELERATE;
+    public static final int EASING_CUBIC_DECELERATE = FloatAnimation.CUBIC_DECELERATE;
+    public static final int EASING_CUBIC_LINEAR = FloatAnimation.CUBIC_LINEAR;
+    public static final int EASING_CUBIC_ANTICIPATE = FloatAnimation.CUBIC_ANTICIPATE;
+    public static final int EASING_CUBIC_OVERSHOOT = FloatAnimation.CUBIC_OVERSHOOT;
+    public static final int EASING_CUBIC_CUSTOM = FloatAnimation.CUBIC_CUSTOM;
+    public static final int EASING_SPLINE_CUSTOM = FloatAnimation.SPLINE_CUSTOM;
+    public static final int EASING_EASE_OUT_BOUNCE = FloatAnimation.EASE_OUT_BOUNCE;
+    public static final int EASING_EASE_OUT_ELASTIC = FloatAnimation.EASE_OUT_ELASTIC;
     WireBuffer mBuffer = new WireBuffer();
     Platform mPlatform = null;
     RemoteComposeState mRemoteComposeState;
+    private static final boolean DEBUG = false;
 
     /**
      * Provides an abstract buffer to encode/decode RemoteCompose operations
@@ -171,7 +190,7 @@
      *
      * @param text the string to inject in the buffer
      */
-    int addText(String text) {
+    public int addText(String text) {
         int id = mRemoteComposeState.dataGetId(text);
         if (id == -1) {
             id = mRemoteComposeState.cache(text);
@@ -350,7 +369,6 @@
         addDrawPath(id);
     }
 
-
     /**
      * Draw the specified path
      *
@@ -426,12 +444,160 @@
                                float y,
                                boolean rtl) {
         int textId = addText(text);
-        DrawTextRun.COMPANION.apply(
+        DrawText.COMPANION.apply(
                 mBuffer, textId, start, end,
                 contextStart, contextEnd, x, y, rtl);
     }
 
     /**
+     * Draw the text, with origin at (x,y). The origin is interpreted
+     * based on the Align setting in the paint.
+     *
+     * @param textId       The text to be drawn
+     * @param start        The index of the first character in text to draw
+     * @param end          (end - 1) is the index of the last character in text to draw
+     * @param contextStart
+     * @param contextEnd
+     * @param x            The x-coordinate of the origin of the text being drawn
+     * @param y            The y-coordinate of the baseline of the text being drawn
+     * @param rtl          Draw RTTL
+     */
+    public void addDrawTextRun(int textId,
+                               int start,
+                               int end,
+                               int contextStart,
+                               int contextEnd,
+                               float x,
+                               float y,
+                               boolean rtl) {
+        DrawText.COMPANION.apply(
+                mBuffer, textId, start, end,
+                contextStart, contextEnd, x, y, rtl);
+    }
+
+    /**
+     * Draw a text on canvas at relative to position (x, y),
+     * offset panX and panY.
+     * <br>
+     * The panning factors (panX, panY)  mapped to the
+     * resulting bounding box of the text, in such a way that a
+     * panning factor of (0.0, 0.0) would center the text at (x, y)
+     * <ul>
+     * <li> Panning of -1.0, -1.0 - the text above & right of x,y.</li>
+     * <li>Panning of  1.0,  1.0 - the text is below and to the left</li>
+     * <li>Panning of  1.0,  0.0 - the test is centered & to the right of x,y</li>
+     * </ul>
+     * Setting panY to NaN results in y being the baseline of the text.
+     *
+     * @param text  text to draw
+     * @param x     Coordinate of the Anchor
+     * @param y     Coordinate of the Anchor
+     * @param panX  justifies text -1.0=right, 0.0=center, 1.0=left
+     * @param panY  position text -1.0=above, 0.0=center, 1.0=below, Nan=baseline
+     * @param flags 1 = RTL
+     */
+    public void drawTextAnchored(String text,
+                                 float x,
+                                 float y,
+                                 float panX,
+                                 float panY,
+                                 int flags) {
+        int textId = addText(text);
+        DrawTextAnchored.COMPANION.apply(
+                mBuffer, textId,
+                x, y,
+                panX, panY,
+                flags);
+    }
+
+    /**
+     * Add a text and id so that it can be used
+     *
+     * @param text
+     * @return
+     */
+    public int createTextId(String text) {
+        return addText(text);
+    }
+
+    /**
+     * Merge two text (from id's) output one id
+     * @param id1 left id
+     * @param id2 right id
+     * @return new id that merges the two text
+     */
+    public int textMerge(int id1, int id2) {
+        int textId = addText(id1 + "+" + id2);
+        TextMerge.COMPANION.apply(mBuffer, textId, id1, id2);
+        return textId;
+    }
+
+    public static final int PAD_AFTER_SPACE = TextFromFloat.PAD_AFTER_SPACE;
+    public static final int PAD_AFTER_NONE = TextFromFloat.PAD_AFTER_NONE;
+    public static final int PAD_AFTER_ZERO = TextFromFloat.PAD_AFTER_ZERO;
+    public static final int PAD_PRE_SPACE = TextFromFloat.PAD_PRE_SPACE;
+    public static final int PAD_PRE_NONE = TextFromFloat.PAD_PRE_NONE;
+    public static final int PAD_PRE_ZERO = TextFromFloat.PAD_PRE_ZERO;
+
+    /**
+     * Create a TextFromFloat command which creates text from a Float.
+     *
+     * @param value        The value to convert
+     * @param digitsBefore the digits before the decimal point
+     * @param digitsAfter  the digits after the decimal point
+     * @param flags        configure the behaviour using PAD_PRE_* and PAD_AFTER* flags
+     * @return id of the string that can be passed to drawTextAnchored
+     */
+    public int createTextFromFloat(float value, short digitsBefore,
+                                   short digitsAfter, int flags) {
+        String placeHolder = Utils.floatToString(value)
+                + "(" + digitsBefore + "," + digitsAfter + "," + flags + ")";
+        int id = mRemoteComposeState.dataGetId(placeHolder);
+        if (id == -1) {
+            id = mRemoteComposeState.cache(placeHolder);
+            //   TextData.COMPANION.apply(mBuffer, id, text);
+        }
+        TextFromFloat.COMPANION.apply(mBuffer, id, value, digitsBefore,
+                digitsAfter, flags);
+        return id;
+    }
+
+    /**
+     * Draw a text on canvas at relative to position (x, y),
+     * offset panX and panY.
+     * <br>
+     * The panning factors (panX, panY)  mapped to the
+     * resulting bounding box of the text, in such a way that a
+     * panning factor of (0.0, 0.0) would center the text at (x, y)
+     * <ul>
+     * <li> Panning of -1.0, -1.0 - the text above & right of x,y.</li>
+     * <li>Panning of  1.0,  1.0 - the text is below and to the left</li>
+     * <li>Panning of  1.0,  0.0 - the test is centered & to the right of x,y</li>
+     * </ul>
+     * Setting panY to NaN results in y being the baseline of the text.
+     *
+     * @param textId text to draw
+     * @param x      Coordinate of the Anchor
+     * @param y      Coordinate of the Anchor
+     * @param panX   justifies text -1.0=right, 0.0=center, 1.0=left
+     * @param panY   position text -1.0=above, 0.0=center, 1.0=below, Nan=baseline
+     * @param flags  1 = RTL
+     */
+    public void drawTextAnchored(int textId,
+                                 float x,
+                                 float y,
+                                 float panX,
+                                 float panY,
+                                 int flags) {
+
+        DrawTextAnchored.COMPANION.apply(
+                mBuffer, textId,
+                x, y,
+                panX, panY,
+                flags);
+    }
+
+    /**
      * draw an interpolation between two paths that have the same pattern
      * <p>
      * Warning paths objects are not immutable and this is not taken into consideration
@@ -490,6 +656,10 @@
         return id;
     }
 
+    /**
+     * Adds a paint Bundle to the doc
+     * @param paint
+     */
     public void addPaint(PaintBundle paint) {
         PaintData.COMPANION.apply(mBuffer, paint);
     }
@@ -499,7 +669,9 @@
         mBuffer.setIndex(0);
         while (mBuffer.available()) {
             int opId = mBuffer.readByte();
-            System.out.println(">>> " + opId);
+            if (DEBUG) {
+                Utils.log(">> " + opId);
+            }
             CompanionOperation operation = Operations.map.get(opId);
             if (operation == null) {
                 throw new RuntimeException("Unknown operation encountered " + opId);
@@ -519,7 +691,6 @@
         Theme.COMPANION.apply(mBuffer, theme);
     }
 
-
     static String version() {
         return "v1.0";
     }
@@ -654,8 +825,8 @@
     /**
      * Add a pre-concat of the current matrix with the specified scale.
      *
-     * @param scaleX  The amount to scale in X
-     * @param scaleY  The amount to scale in Y
+     * @param scaleX The amount to scale in X
+     * @param scaleY The amount to scale in Y
      */
     public void addMatrixScale(float scaleX, float scaleY) {
         MatrixScale.COMPANION.apply(mBuffer, scaleX, scaleY, Float.NaN, Float.NaN);
@@ -673,12 +844,174 @@
         MatrixScale.COMPANION.apply(mBuffer, scaleX, scaleY, centerX, centerY);
     }
 
+    /**
+     * sets the clip based on clip id
+     * @param pathId 0 clears the clip
+     */
     public void addClipPath(int pathId) {
         ClipPath.COMPANION.apply(mBuffer, pathId);
     }
 
+    /**
+     * Sets the clip based on clip rec
+     * @param left
+     * @param top
+     * @param right
+     * @param bottom
+     */
     public void addClipRect(float left, float top, float right, float bottom) {
         ClipRect.COMPANION.apply(mBuffer, left, top, right, bottom);
     }
+
+    /**
+     * Add a float return a NaN number pointing to that float
+     * @param value
+     * @return
+     */
+    public float addFloat(float value) {
+        int id = mRemoteComposeState.cacheFloat(value);
+        FloatConstant.COMPANION.apply(mBuffer, id, value);
+        return Utils.asNan(id);
+    }
+
+    /**
+     * Add a float that is a computation based on variables
+     * @param value A RPN style float operation i.e. "4, 3, ADD" outputs 7
+     * @return NaN id of the result of the calculation
+     */
+    public float addAnimatedFloat(float... value) {
+        int id = mRemoteComposeState.cache(value);
+        FloatExpression.COMPANION.apply(mBuffer, id, value, null);
+        return Utils.asNan(id);
+    }
+
+    /**
+     * Add a float that is a computation based on variables.
+     * see packAnimation
+     * @param value A RPN style float operation i.e. "4, 3, ADD" outputs 7
+     * @param animation Array of floats that represents animation
+     * @return NaN id of the result of the calculation
+     */
+    public float addAnimatedFloat(float[] value, float[] animation) {
+        int id = mRemoteComposeState.cache(value);
+        FloatExpression.COMPANION.apply(mBuffer, id, value, animation);
+        return Utils.asNan(id);
+    }
+
+    /**
+     * Add a color that represents the tween between two colors
+     * @param color1
+     * @param color2
+     * @param tween
+     * @return id of the color (color ids are short)
+     */
+    public short addColorExpression(int color1, int color2, float tween) {
+        ColorExpression c = new ColorExpression(0, 0, color1, color2, tween);
+        short id = (short) mRemoteComposeState.cache(c);
+        c.mId = id;
+        c.write(mBuffer);
+        return id;
+    }
+
+    /**
+     * Add a color that represents the tween between two colors where color1
+     * is the id of a color
+     * @param color1
+     * @param color2
+     * @param tween
+     * @return id of the color (color ids are short)
+     */
+    public short addColorExpression(short color1, int color2, float tween) {
+        ColorExpression c = new ColorExpression(0, 1, color1, color2, tween);
+        short id = (short) mRemoteComposeState.cache(c);
+        c.mId = id;
+        c.write(mBuffer);
+        return id;
+    }
+
+    /**
+     * Add a color that represents the tween between two colors where color2
+     * is the id of a color
+     * @param color1
+     * @param color2
+     * @param tween
+     * @return id of the color (color ids are short)
+     */
+    public short addColorExpression(int color1, short color2, float tween) {
+        ColorExpression c = new ColorExpression(0, 2, color1, color2, tween);
+        short id = (short) mRemoteComposeState.cache(c);
+        c.mId = id;
+        c.write(mBuffer);
+        return id;
+    }
+
+    /**
+     * Add a color that represents the tween between two colors where color1 &
+     * color2 are the ids of colors
+     * @param color1
+     * @param color2
+     * @param tween
+     * @return id of the color (color ids are short)
+     */
+    public short addColorExpression(short color1, short color2, float tween) {
+        ColorExpression c = new ColorExpression(0, 3, color1, color2, tween);
+        short id = (short) mRemoteComposeState.cache(c);
+        c.mId = id;
+        c.write(mBuffer);
+        return id;
+    }
+
+    /**
+     *  Color calculated by Hue saturation and value.
+     *  (as floats they can be variables used to create color transitions)
+     * @param hue
+     * @param sat
+     * @param value
+     * @return id of the color (color ids are short)
+     */
+    public short addColorExpression(float hue, float sat, float value) {
+        ColorExpression c = new ColorExpression(0, hue, sat, value);
+        short id = (short) mRemoteComposeState.cache(c);
+        c.mId = id;
+        c.write(mBuffer);
+        return id;
+    }
+
+    /**
+     * Color calculated by Alpha, Hue saturation and value.
+     * (as floats they can be variables used to create color transitions)
+     * @param alpha
+     * @param hue
+     * @param sat
+     * @param value
+     * @return id of the color (color ids are short)
+     */
+    public short addColorExpression(int alpha, float hue, float sat, float value) {
+        ColorExpression c = new ColorExpression(0, alpha, hue, sat, value);
+        short id = (short) mRemoteComposeState.cache(c);
+        c.mId = id;
+        c.write(mBuffer);
+        return id;
+    }
+
+    /**
+     * create and animation based on description and return as an array of
+     * floats. see addAnimatedFloat
+     * @param duration
+     * @param type
+     * @param spec
+     * @param initialValue
+     * @param wrap
+     * @return
+     */
+    public static float[] packAnimation(float duration,
+                                        int type,
+                                        float[] spec,
+                                        float initialValue,
+                                        float wrap) {
+
+        return FloatAnimation.packToFloatArray(duration, type, spec, initialValue, wrap);
+    }
+
 }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
index 17e8c83..66a37e67 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
@@ -15,26 +15,53 @@
  */
 package com.android.internal.widget.remotecompose.core;
 
+import static com.android.internal.widget.remotecompose.core.RemoteContext.ID_CONTINUOUS_SEC;
+import static com.android.internal.widget.remotecompose.core.RemoteContext.ID_TIME_IN_MIN;
+import static com.android.internal.widget.remotecompose.core.RemoteContext.ID_TIME_IN_SEC;
+import static com.android.internal.widget.remotecompose.core.RemoteContext.ID_WINDOW_HEIGHT;
+import static com.android.internal.widget.remotecompose.core.RemoteContext.ID_WINDOW_WIDTH;
+
 import com.android.internal.widget.remotecompose.core.operations.utilities.IntMap;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 
 /**
  * Represents runtime state for a RemoteCompose document
+ * State includes things like the value of variables
  */
 public class RemoteComposeState {
-
+    public static final int START_ID = 42;
+    private static final int MAX_FLOATS = 500;
     private final IntMap<Object> mIntDataMap = new IntMap<>();
     private final IntMap<Boolean> mIntWrittenMap = new IntMap<>();
     private final HashMap<Object, Integer> mDataIntMap = new HashMap();
+    private final float[] mFloatMap = new float[MAX_FLOATS]; // efficient cache
+    private final int[] mColorMap = new int[MAX_FLOATS]; // efficient cache
+    private int mNextId = START_ID;
 
-    private static int sNextId = 42;
+    {
+        for (int i = 0; i < mFloatMap.length; i++) {
+            mFloatMap[i] = Float.NaN;
+        }
+    }
 
-    public Object getFromId(int id)  {
+    /**
+     * Get Object based on id. The system will cache things like bitmaps
+     * Paths etc. They can be accessed with this command
+     * @param id
+     * @return
+     */
+    public Object getFromId(int id) {
         return mIntDataMap.get(id);
     }
 
-    public boolean containsId(int id)  {
+    /**
+     * true if the cache contain this id
+     * @param id
+     * @return
+     */
+    public boolean containsId(int id) {
         return mIntDataMap.get(id) != null;
     }
 
@@ -69,6 +96,65 @@
     }
 
     /**
+     * Insert an item in the cache
+     */
+    public void update(int id, Object item) {
+        mDataIntMap.remove(mIntDataMap.get(id));
+        mDataIntMap.put(item, id);
+        mIntDataMap.put(id, item);
+    }
+
+    /**
+     * Insert an item in the cache
+     */
+    public int cacheFloat(float item) {
+        int id = nextId();
+        mFloatMap[id] = item;
+        return id;
+    }
+
+    /**
+     * Insert an item in the cache
+     */
+    public void cacheFloat(int id, float item) {
+        mFloatMap[id] = item;
+    }
+
+    /**
+     * Insert an item in the cache
+     */
+    public void updateFloat(int id, float item) {
+        mFloatMap[id] = item;
+    }
+
+    /**
+     * get float
+     */
+    public float getFloat(int id) {
+        return mFloatMap[id];
+    }
+
+    /**
+     * Get the float value
+     *
+     * @param id
+     * @return
+     */
+    public int getColor(int id) {
+        return mColorMap[id];
+    }
+
+    /**
+     * Modify the color at id.
+     * @param id
+     * @param color
+     */
+    public void updateColor(int id, int color) {
+        mColorMap[id] = color;
+    }
+
+
+    /**
      * Method to determine if a cached value has been written to the documents WireBuffer based on
      * its id.
      */
@@ -79,22 +165,90 @@
     /**
      * Method to mark that a value, represented by its id, has been written to the WireBuffer
      */
-    public void  markWritten(int id) {
+    public void markWritten(int id) {
         mIntWrittenMap.put(id, true);
     }
 
     /**
-     *  Clear the record of the values that have been written to the WireBuffer.
+     * Clear the record of the values that have been written to the WireBuffer.
      */
     void reset() {
         mIntWrittenMap.clear();
     }
 
-    public static int nextId() {
-        return sNextId++;
+    /**
+     * Get the next available id
+     * @return
+     */
+    public int nextId() {
+        return mNextId++;
     }
-    public static void setNextId(int id) {
-        sNextId = id;
+
+    /**
+     * Set the next id
+     * @param id
+     */
+    public void setNextId(int id) {
+        mNextId = id;
+    }
+
+    IntMap<ArrayList<VariableSupport>> mVarListeners = new IntMap<>();
+    ArrayList<VariableSupport> mAllVarListeners = new ArrayList<>();
+
+    private void add(int id, VariableSupport variableSupport) {
+        ArrayList<VariableSupport> v = mVarListeners.get(id);
+        if (v == null) {
+            v = new ArrayList<VariableSupport>();
+            mVarListeners.put(id, v);
+        }
+        v.add(variableSupport);
+        mAllVarListeners.add(variableSupport);
+    }
+
+    /**
+     * Commands that listen to variables add themselves.
+     * @param id
+     * @param variableSupport
+     */
+    public void listenToVar(int id, VariableSupport variableSupport) {
+        add(id, variableSupport);
+    }
+
+    /**
+     * List of Commands that need to be updated
+     * @param context
+     * @return
+     */
+    public int getOpsToUpdate(RemoteContext context) {
+        for (VariableSupport vs : mAllVarListeners) {
+            vs.updateVariables(context);
+        }
+        if (mVarListeners.get(ID_CONTINUOUS_SEC) != null) {
+            return 1;
+        }
+        if (mVarListeners.get(ID_TIME_IN_SEC) != null) {
+            return 1000;
+        }
+        if (mVarListeners.get(ID_TIME_IN_MIN) != null) {
+            return 1000 * 60;
+        }
+        return -1;
+    }
+
+    /**
+     * Set the width of the overall document on screen.
+     * @param width
+     */
+    public void setWindowWidth(float width) {
+        updateFloat(ID_WINDOW_WIDTH, width);
+    }
+
+    /**
+     * Set the width of the overall document on screen.
+     * @param height
+     */
+    public void setWindowHeight(float height) {
+        updateFloat(ID_WINDOW_HEIGHT, height);
     }
 
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
index d16cbc5..7e72168 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 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.
@@ -15,7 +15,10 @@
  */
 package com.android.internal.widget.remotecompose.core;
 
+import com.android.internal.widget.remotecompose.core.operations.FloatExpression;
+import com.android.internal.widget.remotecompose.core.operations.ShaderData;
 import com.android.internal.widget.remotecompose.core.operations.Theme;
+import com.android.internal.widget.remotecompose.core.operations.Utils;
 
 /**
  * Specify an abstract context used to playback RemoteCompose documents
@@ -27,7 +30,7 @@
 public abstract class RemoteContext {
     protected CoreDocument mDocument;
     public RemoteComposeState mRemoteComposeState;
-
+    long mStart = System.nanoTime(); // todo This should be set at a hi level
     protected PaintContext mPaintContext = null;
     ContextMode mMode = ContextMode.UNSET;
 
@@ -37,9 +40,40 @@
     public float mWidth = 0f;
     public float mHeight = 0f;
 
+    /**
+     * Load a path under an id.
+     * Paths can be use in clip drawPath and drawTweenPath
+     * @param instanceId
+     * @param floatPath
+     */
     public abstract void loadPathData(int instanceId, float[] floatPath);
 
     /**
+     * Associate a name with a give id.
+     *
+     * @param varName
+     * @param varId
+     * @param varType
+     */
+    public abstract void loadVariableName(String varName, int varId, int varType);
+
+    /**
+     * Save a color under a given id
+     * @param id
+     * @param color
+     */
+    public abstract void loadColor(int id, int color);
+
+    /**
+     * gets the time animation clock as float in seconds
+     * @return a monotonic time in seconds (arbitrary zero point)
+     */
+    public float getAnimationTime() {
+        return (System.nanoTime() - mStart) * 1E-9f;
+    }
+
+
+    /**
      * The context can be used in a few different mode, allowing operations to skip being executed:
      * - UNSET : all operations will get executed
      * - DATA : only operations dealing with DATA (eg loading a bitmap) should execute
@@ -96,6 +130,8 @@
     public void header(int majorVersion, int minorVersion, int patchVersion,
                        int width, int height, long capabilities
     ) {
+        mRemoteComposeState.setWindowWidth(width);
+        mRemoteComposeState.setWindowHeight(height);
         mDocument.setVersion(majorVersion, minorVersion, patchVersion);
         mDocument.setWidth(width);
         mDocument.setHeight(height);
@@ -137,9 +173,105 @@
     ///////////////////////////////////////////////////////////////////////////////////////////////
     // Data handling
     ///////////////////////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Save a bitmap under an imageId
+     * @param imageId
+     * @param width
+     * @param height
+     * @param bitmap
+     */
     public abstract void loadBitmap(int imageId, int width, int height, byte[] bitmap);
+
+    /**
+     * Save a string under a given id
+     * @param id
+     * @param text
+     */
     public abstract void loadText(int id, String text);
 
+    /**
+     * Get a string given an id
+     * @param id
+     * @return
+     */
+    public abstract String getText(int id);
+
+    /**
+     * Load a float
+     * @param id
+     * @param value
+     */
+    public abstract void loadFloat(int id, float value);
+
+    /**
+     * Load an animated float associated with an id
+     * Todo: Remove?
+     * @param id
+     * @param animatedFloat
+     */
+    public abstract void loadAnimatedFloat(int id, FloatExpression animatedFloat);
+
+    /**
+     * Save a shader under and ID
+     * @param id
+     * @param value
+     */
+    public abstract void loadShader(int id, ShaderData value);
+
+    /**
+     * Get a float given an id
+     * @param id
+     * @return
+     */
+    public abstract float getFloat(int id);
+
+    /**
+     * Get the color given and ID
+     * @param id
+     * @return
+     */
+    public abstract int getColor(int id);
+
+    /**
+     * called to notify system that a command is interested in a variable
+     * @param id
+     * @param variableSupport
+     */
+    public abstract void listensTo(int id, VariableSupport variableSupport);
+
+    /**
+     * Notify commands with variables have changed
+     * @return
+     */
+    public abstract int updateOps();
+
+    /**
+     * Get a shader given the id
+     * @param id
+     * @return
+     */
+    public abstract ShaderData getShader(int id);
+
+    public static final int ID_CONTINUOUS_SEC = 1;
+    public static final int ID_TIME_IN_SEC = 2;
+    public static final int ID_TIME_IN_MIN = 3;
+    public static final int ID_TIME_IN_HR = 4;
+    public static final int ID_WINDOW_WIDTH = 5;
+    public static final int ID_WINDOW_HEIGHT = 6;
+    public static final int ID_COMPONENT_WIDTH = 7;
+    public static final int ID_COMPONENT_HEIGHT = 8;
+    public static final int ID_CALENDAR_MONTH = 9;
+
+    public static final float FLOAT_CONTINUOUS_SEC = Utils.asNan(ID_CONTINUOUS_SEC);
+    public static final float FLOAT_TIME_IN_SEC = Utils.asNan(ID_TIME_IN_SEC);
+    public static final float FLOAT_TIME_IN_MIN = Utils.asNan(ID_TIME_IN_MIN);
+    public static final float FLOAT_TIME_IN_HR = Utils.asNan(ID_TIME_IN_HR);
+    public static final float FLOAT_CALENDAR_MONTH = Utils.asNan(ID_CALENDAR_MONTH);
+    public static final float FLOAT_WINDOW_WIDTH = Utils.asNan(ID_WINDOW_WIDTH);
+    public static final float FLOAT_WINDOW_HEIGHT = Utils.asNan(ID_WINDOW_HEIGHT);
+    public static final float FLOAT_COMPONENT_WIDTH = Utils.asNan(ID_COMPONENT_WIDTH);
+    public static final float FLOAT_COMPONENT_HEIGHT = Utils.asNan(ID_COMPONENT_HEIGHT);
     ///////////////////////////////////////////////////////////////////////////////////////////////
     // Click handling
     ///////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java b/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java
new file mode 100644
index 0000000..e9708b7
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 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.internal.widget.remotecompose.core;
+
+import java.time.LocalDateTime;
+
+/**
+ * This generates the standard system variables for time.
+ */
+public class TimeVariables {
+    /**
+     * This class populates all time variables in the system
+     * @param context
+     */
+    public void updateTime(RemoteContext context) {
+        LocalDateTime dateTime = LocalDateTime.now();
+        // This define the time in the format
+        // seconds run from Midnight=0 quantized to seconds hour 0..3599
+        // minutes run from Midnight=0 quantized to minutes 0..1439
+        // hours run from Midnight=0 quantized to Hours 0-23
+        // CONTINUOUS_SEC is seconds from midnight looping every hour 0-3600
+        // CONTINUOUS_SEC is accurate to milliseconds due to float precession
+        int month = dateTime.getDayOfMonth();
+        int hour = dateTime.getHour();
+        int minute = dateTime.getMinute();
+        int seconds = dateTime.getSecond();
+        int currentMinute = hour * 60 + minute;
+        int currentSeconds = minute * 60 + seconds;
+        float sec = currentSeconds + dateTime.getNano() * 1E-9f;
+
+        context.loadFloat(RemoteContext.ID_CONTINUOUS_SEC, sec);
+        context.loadFloat(RemoteContext.ID_TIME_IN_SEC, currentSeconds);
+        context.loadFloat(RemoteContext.ID_TIME_IN_MIN, currentMinute);
+        context.loadFloat(RemoteContext.ID_TIME_IN_HR, hour);
+        context.loadFloat(RemoteContext.ID_CALENDAR_MONTH, month);
+
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/VariableSupport.java b/core/java/com/android/internal/widget/remotecompose/core/VariableSupport.java
new file mode 100644
index 0000000..d59b1bc
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/VariableSupport.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 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.internal.widget.remotecompose.core;
+
+/**
+ * Interface for operators that interact with variables
+ * Threw this they register to listen to particular variables
+ * and are notified when they change
+ */
+public interface VariableSupport {
+    /**
+     * Call to allow an operator to register interest in variables.
+     * Typically they call context.listensTo(id, this)
+     * @param context
+     */
+    void registerListening(RemoteContext context);
+
+    /**
+     * Called to be notified that the variables you are interested have changed.
+     * @param context
+     */
+    void updateVariables(RemoteContext context);
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java
index 76b7144..f186322 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java
@@ -51,11 +51,12 @@
 
     @Override
     public String toString() {
-        return "BITMAP DATA $imageId";
+        return "BITMAP DATA " + mImageId;
     }
 
     public static class Companion implements CompanionOperation {
-        private Companion() {}
+        private Companion() {
+        }
 
         @Override
         public String name() {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java
index 8d4a787..e6d5fe7 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java
@@ -24,6 +24,11 @@
 
 import java.util.List;
 
+/**
+ * Defines a path that clips a the subsequent drawing commands
+ * Use MatrixSave and MatrixRestore commands to remove clip
+ * TODO allow id 0 to mean null?
+ */
 public class ClipPath extends PaintOperation {
     public static final Companion COMPANION = new Companion();
     int mId;
@@ -93,5 +98,4 @@
     public void paint(PaintContext context) {
         context.clipPath(mId, mRegionOp);
     }
-}
-
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java
index 803618a..613eceb 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java
@@ -15,88 +15,36 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
-import com.android.internal.widget.remotecompose.core.CompanionOperation;
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
-import com.android.internal.widget.remotecompose.core.PaintOperation;
-import com.android.internal.widget.remotecompose.core.WireBuffer;
 
-import java.util.List;
-
-public class ClipRect extends PaintOperation {
-    public static final Companion COMPANION = new Companion();
-    float mLeft;
-    float mTop;
-    float mRight;
-    float mBottom;
+/**
+ * Support clip with a rectangle
+ */
+public class ClipRect extends DrawBase4 {
+    public static final Companion COMPANION =
+            new Companion(Operations.CLIP_RECT) {
+                @Override
+                public Operation construct(float x1,
+                                           float y1,
+                                           float x2,
+                                           float y2) {
+                    return new ClipRect(x1, y1, x2, y2);
+                }
+            };
 
     public ClipRect(
             float left,
             float top,
             float right,
             float bottom) {
-        mLeft = left;
-        mTop = top;
-        mRight = right;
-        mBottom = bottom;
-
-    }
-
-    @Override
-    public void write(WireBuffer buffer) {
-        COMPANION.apply(buffer, mLeft, mTop, mRight, mBottom);
-    }
-
-    @Override
-    public String toString() {
-        return "ClipRect " + mLeft + " " + mTop
-                + " " + mRight + " " + mBottom + ";";
-    }
-
-    public static class Companion implements CompanionOperation {
-        private Companion() {
-        }
-
-        @Override
-        public void read(WireBuffer buffer, List<Operation> operations) {
-            float sLeft = buffer.readFloat();
-            float srcTop = buffer.readFloat();
-            float srcRight = buffer.readFloat();
-            float srcBottom = buffer.readFloat();
-
-            ClipRect op = new ClipRect(sLeft, srcTop, srcRight, srcBottom);
-            operations.add(op);
-        }
-
-        @Override
-        public String name() {
-            return "ClipRect";
-        }
-
-        @Override
-        public int id() {
-            return Operations.CLIP_RECT;
-        }
-
-        public void apply(WireBuffer buffer,
-                          float left,
-                          float top,
-                          float right,
-                          float bottom) {
-            buffer.start(Operations.CLIP_RECT);
-            buffer.writeFloat(left);
-            buffer.writeFloat(top);
-            buffer.writeFloat(right);
-            buffer.writeFloat(bottom);
-        }
+        super(left, top, right, bottom);
+        mName = "ClipRect";
     }
 
     @Override
     public void paint(PaintContext context) {
-        context.clipRect(mLeft,
-                mTop,
-                mRight,
-                mBottom);
+        context.clipRect(mX1, mY1, mX2, mY2);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java
new file mode 100644
index 0000000..7d28cea
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2023 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.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+/**
+ * Operation to Colors
+ * Color modes
+ * mMode = 0 two colors and a tween
+ * mMode = 1 color1 is a colorID.
+ * mMode = 2 color2 is a colorID.
+ * mMode = 3 color1 & color2 are ids
+ * mMode = 4  H S V mode
+ */
+public class ColorExpression implements Operation, VariableSupport {
+    public int mId;
+    int mMode;
+    public int mColor1;
+    public int mColor2;
+    public float mTween = 0.0f;
+
+
+    public float mHue = 0; // only in Mode 4
+    public float mSat = 0;
+    public float mValue = 0;
+    public float mOutHue = 0; // only in Mode 4
+    public float mOutSat = 0;
+    public float mOutValue = 0;
+    public int mAlpha = 0xFF; // only used in hsv mode
+
+    public float mOutTween = 0.0f;
+    public int mOutColor1;
+    public int mOutColor2;
+    public static final Companion COMPANION = new Companion();
+    public static final int HSV_MODE = 4;
+    public ColorExpression(int id, float hue, float sat, float value) {
+        mMode = HSV_MODE;
+        mAlpha = 0xFF;
+        mOutHue = mHue = hue;
+        mOutSat = mSat = sat;
+        mOutValue = mValue = value;
+        mColor1 = Float.floatToRawIntBits(hue);
+        mColor2 = Float.floatToRawIntBits(sat);
+        mTween = value;
+    }
+    public ColorExpression(int id, int alpha, float hue, float sat, float value) {
+        mMode = HSV_MODE;
+        mAlpha = alpha;
+        mOutHue = mHue = hue;
+        mOutSat = mSat = sat;
+        mOutValue = mValue = value;
+        mColor1 = Float.floatToRawIntBits(hue);
+        mColor2 = Float.floatToRawIntBits(sat);
+        mTween = value;
+    }
+
+    public ColorExpression(int id, int mode, int color1, int color2, float tween) {
+        this.mId = id;
+        this.mMode = mode & 0xFF;
+        this.mAlpha = (mode >> 16) & 0xFF;
+        if (mMode == HSV_MODE) {
+            mOutHue = mHue = Float.intBitsToFloat(color1);
+            mOutSat = mSat = Float.intBitsToFloat(color2);
+            mOutValue = mValue = tween;
+        }
+        this.mColor1 = color1;
+        this.mColor2 = color2;
+        this.mTween = tween;
+        this.mOutTween = tween;
+        this.mOutColor1 = color1;
+        this.mOutColor2 = color2;
+
+    }
+
+    @Override
+    public void updateVariables(RemoteContext context) {
+        if (mMode == 4) {
+            if (Float.isNaN(mHue)) {
+                mOutHue = context.getFloat(Utils.idFromNan(mHue));
+            }
+            if (Float.isNaN(mSat)) {
+                mOutSat = context.getFloat(Utils.idFromNan(mSat));
+            }
+            if (Float.isNaN(mValue)) {
+                mOutValue = context.getFloat(Utils.idFromNan(mValue));
+            }
+        }
+        if (Float.isNaN(mTween)) {
+            mOutTween = context.getFloat(Utils.idFromNan(mTween));
+        }
+        if ((mMode & 1) == 1) {
+            mOutColor1 = context.getColor(mColor1);
+        }
+        if ((mMode & 2) == 2) {
+            mOutColor2 = context.getColor(mColor2);
+        }
+    }
+
+
+    @Override
+    public void registerListening(RemoteContext context) {
+        if (mMode == 4) {
+            if (Float.isNaN(mHue)) {
+                context.listensTo(Utils.idFromNan(mHue), this);
+            }
+            if (Float.isNaN(mSat)) {
+                context.listensTo(Utils.idFromNan(mSat), this);
+            }
+            if (Float.isNaN(mValue)) {
+                context.listensTo(Utils.idFromNan(mValue), this);
+            }
+            return;
+        }
+        if (Float.isNaN(mTween)) {
+            context.listensTo(Utils.idFromNan(mTween), this);
+        }
+        if ((mMode & 1) == 1) {
+            context.listensTo(mColor1, this);
+        }
+        if ((mMode & 2) == 2) {
+            context.listensTo(mColor2, this);
+        }
+    }
+
+    @Override
+    public void apply(RemoteContext context) {
+        if (mMode == 4) {
+            context.loadColor(mId, (mAlpha << 24)
+                    | (0xFFFFFF & Utils.hsvToRgb(mOutHue, mOutSat, mOutValue)));
+            return;
+        }
+        if (mOutTween == 0.0) {
+            context.loadColor(mId, mColor1);
+        } else {
+            if ((mMode & 1) == 1) {
+                mOutColor1 = context.getColor(mColor1);
+            }
+            if ((mMode & 2) == 2) {
+                mOutColor2 = context.getColor(mColor2);
+            }
+
+            context.loadColor(mId,
+                    Utils.interpolateColor(mOutColor1, mOutColor2, mOutTween));
+        }
+
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        int mode = mMode | (mAlpha << 16);
+        COMPANION.apply(buffer, mId, mode, mColor1, mColor2, mTween);
+    }
+
+    @Override
+    public String toString() {
+        if (mMode == 4) {
+            return "ColorExpression[" + mId + "] = hsv (" + Utils.floatToString(mHue)
+                    + ", " + Utils.floatToString(mSat)
+                    + ", " + Utils.floatToString(mValue) + ")";
+        }
+
+        String c1 = (mMode & 1) == 1 ? "[" + mColor1 + "]" : Utils.colorInt(mColor1);
+        String c2 = (mMode & 2) == 2 ? "[" + mColor2 + "]" : Utils.colorInt(mColor2);
+        return "ColorExpression[" + mId + "] = tween(" + c1
+                + ", " + c2 + ", "
+                + Utils.floatToString(mTween) + ")";
+    }
+
+    public static class Companion implements CompanionOperation {
+        private Companion() {
+        }
+
+        @Override
+        public String name() {
+            return "ColorExpression";
+        }
+
+        @Override
+        public int id() {
+            return Operations.COLOR_EXPRESSIONS;
+        }
+
+        /**
+         * Call to write a ColorExpression object on the buffer
+         * @param buffer
+         * @param id of the ColorExpression object
+         * @param mode if colors are id or actual values
+         * @param color1
+         * @param color2
+         * @param tween
+         */
+        public void apply(WireBuffer buffer,
+                          int id, int mode,
+                          int color1, int color2, float tween) {
+            buffer.start(Operations.COLOR_EXPRESSIONS);
+            buffer.writeInt(id);
+            buffer.writeInt(mode);
+            buffer.writeInt(color1);
+            buffer.writeInt(color2);
+            buffer.writeFloat(tween);
+
+        }
+
+        @Override
+        public void read(WireBuffer buffer, List<Operation> operations) {
+            int id = buffer.readInt();
+            int mode = buffer.readInt();
+            int color1 = buffer.readInt();
+            int color2 = buffer.readInt();
+            float tween = buffer.readFloat();
+
+            operations.add(new ColorExpression(id, mode, color1, color2, tween));
+        }
+    }
+
+    @Override
+    public String deepToString(String indent) {
+        return indent + toString();
+    }
+
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java
index e829975..c176864 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java
@@ -15,107 +15,36 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
-import com.android.internal.widget.remotecompose.core.CompanionOperation;
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
-import com.android.internal.widget.remotecompose.core.PaintOperation;
-import com.android.internal.widget.remotecompose.core.WireBuffer;
 
-import java.util.List;
+public class DrawArc extends DrawBase6 {
+    public static final Companion COMPANION =
+            new Companion(Operations.DRAW_ARC) {
+                @Override
+                public Operation construct(float v1,
+                                           float v2,
+                                           float v3,
+                                           float v4,
+                                           float v5,
+                                           float v6) {
+                    return new DrawArc(v1, v2, v3, v4, v5, v6);
+                }
+            };
 
-public class DrawArc extends PaintOperation {
-    public static final Companion COMPANION = new Companion();
-    float mLeft;
-    float mTop;
-    float mRight;
-    float mBottom;
-    float mStartAngle;
-    float mSweepAngle;
-
-    public DrawArc(
-            float left,
-            float top,
-            float right,
-            float bottom,
-            float startAngle,
-            float sweepAngle) {
-        mLeft = left;
-        mTop = top;
-        mRight = right;
-        mBottom = bottom;
-        mStartAngle = startAngle;
-        mSweepAngle = sweepAngle;
-    }
-
-    @Override
-    public void write(WireBuffer buffer) {
-        COMPANION.apply(buffer, mLeft,
-                mTop,
-                mRight,
-                mBottom,
-                mStartAngle,
-                mSweepAngle);
-    }
-
-    @Override
-    public String toString() {
-        return "DrawArc " + mLeft + " " + mTop
-                + " " + mRight + " " + mBottom + " "
-                + "- " + mStartAngle + " " + mSweepAngle + ";";
-    }
-
-    public static class Companion implements CompanionOperation {
-        private Companion() {
-        }
-
-        @Override
-        public void read(WireBuffer buffer, List<Operation> operations) {
-            float sLeft = buffer.readFloat();
-            float srcTop = buffer.readFloat();
-            float srcRight = buffer.readFloat();
-            float srcBottom = buffer.readFloat();
-            float mStartAngle = buffer.readFloat();
-            float mSweepAngle = buffer.readFloat();
-            DrawArc op = new DrawArc(sLeft, srcTop, srcRight, srcBottom,
-                    mStartAngle, mSweepAngle);
-            operations.add(op);
-        }
-
-        @Override
-        public String name() {
-            return "DrawArc";
-        }
-
-        @Override
-        public int id() {
-            return Operations.DRAW_ARC;
-        }
-
-        public void apply(WireBuffer buffer,
-                          float left,
-                          float top,
-                          float right,
-                          float bottom,
-                          float startAngle,
-                          float sweepAngle) {
-            buffer.start(Operations.DRAW_ARC);
-            buffer.writeFloat(left);
-            buffer.writeFloat(top);
-            buffer.writeFloat(right);
-            buffer.writeFloat(bottom);
-            buffer.writeFloat(startAngle);
-            buffer.writeFloat(sweepAngle);
-        }
+    public DrawArc(float v1,
+                   float v2,
+                   float v3,
+                   float v4,
+                   float v5,
+                   float v6) {
+        super(v1, v2, v3, v4, v5, v6);
+        mName = "DrawArc";
     }
 
     @Override
     public void paint(PaintContext context) {
-        context.drawArc(mLeft,
-                mTop,
-                mRight,
-                mBottom,
-                mStartAngle,
-                mSweepAngle);
+        context.drawArc(mV1, mV2, mV3, mV4, mV5, mV6);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java
new file mode 100644
index 0000000..0963c13
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2024 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.internal.widget.remotecompose.core.operations;
+
+import static com.android.internal.widget.remotecompose.core.operations.Utils.floatToString;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+/**
+ * Base class for commands that take 3 float
+ */
+public abstract class DrawBase2 extends PaintOperation
+        implements VariableSupport {
+    public static final Companion COMPANION =
+            new Companion(Operations.DRAW_CIRCLE) {
+                @Override
+                public Operation construct(float x1, float y1) {
+                    // subclass should return new DrawX(x1, y1);
+                    return null;
+                }
+            };
+    protected String mName = "DrawRectBase";
+    float mV1;
+    float mV2;
+    float mValue1;
+    float mValue2;
+
+    public DrawBase2(float v1, float v2) {
+        mValue1 = v1;
+        mValue2 = v2;
+        mV1 = v1;
+        mV2 = v2;
+    }
+
+    @Override
+    public void updateVariables(RemoteContext context) {
+        mV1 = (Float.isNaN(mValue1))
+                ? context.getFloat(Utils.idFromNan(mValue1)) : mValue1;
+        mV2 = (Float.isNaN(mValue2))
+                ? context.getFloat(Utils.idFromNan(mValue2)) : mValue2;
+    }
+
+    @Override
+    public void registerListening(RemoteContext context) {
+        if (Float.isNaN(mValue1)) {
+            context.listensTo(Utils.idFromNan(mValue1), this);
+        }
+        if (Float.isNaN(mValue2)) {
+            context.listensTo(Utils.idFromNan(mValue2), this);
+        }
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        COMPANION.apply(buffer, mV1, mV2);
+    }
+
+    @Override
+    public String toString() {
+        return mName + " " + floatToString(mV1) + " " + floatToString(mV2);
+    }
+
+    public static class Companion implements CompanionOperation {
+        public final int OP_CODE;
+
+        protected Companion(int code) {
+            OP_CODE = code;
+        }
+
+        @Override
+        public void read(WireBuffer buffer, List<Operation> operations) {
+            float v1 = buffer.readFloat();
+            float v2 = buffer.readFloat();
+
+            Operation op = construct(v1, v2);
+            operations.add(op);
+        }
+
+        /**
+         * Override to construct a 2 float value operation
+         * @param x1
+         * @param y1
+         * @return
+         */
+        public Operation construct(float x1, float y1) {
+            return null;
+        }
+
+        @Override
+        public String name() {
+            return "DrawRect";
+        }
+
+        @Override
+        public int id() {
+            return OP_CODE;
+        }
+
+        /**
+         * Writes out the operation to the buffer
+         * @param buffer
+         * @param x1
+         * @param y1
+         */
+        public void apply(WireBuffer buffer,
+                          float x1,
+                          float y1) {
+            buffer.start(OP_CODE);
+            buffer.writeFloat(x1);
+            buffer.writeFloat(y1);
+        }
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java
new file mode 100644
index 0000000..56b2f1f
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2024 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.internal.widget.remotecompose.core.operations;
+
+import static com.android.internal.widget.remotecompose.core.operations.Utils.floatToString;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+/**
+ * Base class for commands that take 3 float
+ */
+public abstract class DrawBase3 extends PaintOperation
+        implements VariableSupport {
+    public static final Companion COMPANION =
+            new Companion(Operations.DRAW_CIRCLE) {
+                @Override
+                public Operation construct(float x1, float y1, float x2) {
+                    // subclass should return new DrawX(x1, y1, x2, y2);
+                    return null;
+                }
+            };
+    protected String mName = "DrawRectBase";
+    float mV1;
+    float mV2;
+    float mV3;
+    float mValue1;
+    float mValue2;
+    float mValue3;
+
+    public DrawBase3(
+            float v1,
+            float v2,
+            float v3) {
+        mValue1 = v1;
+        mValue2 = v2;
+        mValue3 = v3;
+
+        mV1 = v1;
+        mV2 = v2;
+        mV3 = v3;
+    }
+
+    @Override
+    public void updateVariables(RemoteContext context) {
+        mV1 = (Float.isNaN(mValue1))
+                ? context.getFloat(Utils.idFromNan(mValue1)) : mValue1;
+        mV2 = (Float.isNaN(mValue2))
+                ? context.getFloat(Utils.idFromNan(mValue2)) : mValue2;
+        mV3 = (Float.isNaN(mValue3))
+                ? context.getFloat(Utils.idFromNan(mValue3)) : mValue3;
+    }
+
+    @Override
+    public void registerListening(RemoteContext context) {
+        if (Float.isNaN(mValue1)) {
+            context.listensTo(Utils.idFromNan(mValue1), this);
+        }
+        if (Float.isNaN(mValue2)) {
+            context.listensTo(Utils.idFromNan(mValue2), this);
+        }
+        if (Float.isNaN(mValue3)) {
+            context.listensTo(Utils.idFromNan(mValue3), this);
+        }
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        COMPANION.apply(buffer, mV1, mV2, mV3);
+    }
+
+    @Override
+    public String toString() {
+        return mName + " " + floatToString(mV1) + " " + floatToString(mV2)
+                + " " + floatToString(mV3);
+    }
+
+    public static class Companion implements CompanionOperation {
+        public final int OP_CODE;
+
+        protected Companion(int code) {
+            OP_CODE = code;
+        }
+
+        @Override
+        public void read(WireBuffer buffer, List<Operation> operations) {
+            float v1 = buffer.readFloat();
+            float v2 = buffer.readFloat();
+            float v3 = buffer.readFloat();
+
+            Operation op = construct(v1, v2, v3);
+            operations.add(op);
+        }
+
+        /**
+         * Construct and Operation from the 3 variables.
+         * This must be overridden by subclasses
+         * @param x1
+         * @param y1
+         * @param x2
+         * @return
+         */
+        public Operation construct(float x1,
+                                   float y1,
+                                   float x2) {
+            return null;
+        }
+
+        @Override
+        public String name() {
+            return "DrawRect";
+        }
+
+        @Override
+        public int id() {
+            return OP_CODE;
+        }
+
+        /**
+         * Writes out the operation to the buffer
+         * @param buffer
+         * @param x1
+         * @param y1
+         * @param x2
+         */
+        public void apply(WireBuffer buffer,
+                          float x1,
+                          float y1,
+                          float x2) {
+            buffer.start(OP_CODE);
+            buffer.writeFloat(x1);
+            buffer.writeFloat(y1);
+            buffer.writeFloat(x2);
+
+        }
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java
new file mode 100644
index 0000000..ec35a16
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2024 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.internal.widget.remotecompose.core.operations;
+
+import static com.android.internal.widget.remotecompose.core.operations.Utils.floatToString;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+/**
+ * Base class for draw commands that take 4 floats
+ */
+public abstract class DrawBase4 extends PaintOperation
+        implements VariableSupport {
+    public static final Companion COMPANION =
+            new Companion(Operations.DRAW_RECT) {
+                @Override
+                public Operation construct(float x1, float y1, float x2, float y2) {
+                    //   return new DrawRectBase(x1, y1, x2, y2);
+                    return null;
+                }
+            };
+    protected String mName = "DrawRectBase";
+    float mX1;
+    float mY1;
+    float mX2;
+    float mY2;
+    float mX1Value;
+    float mY1Value;
+    float mX2Value;
+    float mY2Value;
+
+    public DrawBase4(
+            float x1,
+            float y1,
+            float x2,
+            float y2) {
+        mX1Value = x1;
+        mY1Value = y1;
+        mX2Value = x2;
+        mY2Value = y2;
+
+        mX1 = x1;
+        mY1 = y1;
+        mX2 = x2;
+        mY2 = y2;
+    }
+
+    @Override
+    public void updateVariables(RemoteContext context) {
+        mX1 = (Float.isNaN(mX1Value))
+                ? context.getFloat(Utils.idFromNan(mX1Value)) : mX1Value;
+        mY1 = (Float.isNaN(mY1Value))
+                ? context.getFloat(Utils.idFromNan(mY1Value)) : mY1Value;
+        mX2 = (Float.isNaN(mX2Value))
+                ? context.getFloat(Utils.idFromNan(mX2Value)) : mX2Value;
+        mY2 = (Float.isNaN(mY2Value))
+                ? context.getFloat(Utils.idFromNan(mY2Value)) : mY2Value;
+    }
+
+    @Override
+    public void registerListening(RemoteContext context) {
+        if (Float.isNaN(mX1Value)) {
+            context.listensTo(Utils.idFromNan(mX1Value), this);
+        }
+        if (Float.isNaN(mY1Value)) {
+            context.listensTo(Utils.idFromNan(mY1Value), this);
+        }
+        if (Float.isNaN(mX2Value)) {
+            context.listensTo(Utils.idFromNan(mX2Value), this);
+        }
+        if (Float.isNaN(mY2Value)) {
+            context.listensTo(Utils.idFromNan(mY2Value), this);
+        }
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        COMPANION.apply(buffer, mX1, mY1, mX2, mY2);
+    }
+
+    @Override
+    public String toString() {
+        return mName + " " + floatToString(mX1Value, mX1) + " " + floatToString(mY1Value, mY1)
+                + " " + floatToString(mX2Value, mX2) + " " + floatToString(mY2Value, mY2);
+    }
+
+    public static class Companion implements CompanionOperation {
+        public final int OP_CODE;
+
+        protected Companion(int code) {
+            OP_CODE = code;
+        }
+
+        @Override
+        public void read(WireBuffer buffer, List<Operation> operations) {
+            float sLeft = buffer.readFloat();
+            float srcTop = buffer.readFloat();
+            float srcRight = buffer.readFloat();
+            float srcBottom = buffer.readFloat();
+
+            Operation op = construct(sLeft, srcTop, srcRight, srcBottom);
+            operations.add(op);
+        }
+
+        /**
+         * Construct and Operation from the 3 variables.
+         * @param x1
+         * @param y1
+         * @param x2
+         * @param y2
+         * @return
+         */
+        public Operation construct(float x1,
+                                   float y1,
+                                   float x2,
+                                   float y2) {
+            return null;
+        }
+
+        @Override
+        public String name() {
+            return "DrawRect";
+        }
+
+        @Override
+        public int id() {
+            return OP_CODE;
+        }
+
+        /**
+         * Writes out the operation to the buffer
+         * @param buffer
+         * @param x1
+         * @param y1
+         * @param x2
+         * @param y2
+         */
+        public void apply(WireBuffer buffer,
+                          float x1,
+                          float y1,
+                          float x2,
+                          float y2) {
+            buffer.start(OP_CODE);
+            buffer.writeFloat(x1);
+            buffer.writeFloat(y1);
+            buffer.writeFloat(x2);
+            buffer.writeFloat(y2);
+        }
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java
new file mode 100644
index 0000000..2f4335e
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2024 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.internal.widget.remotecompose.core.operations;
+
+import static com.android.internal.widget.remotecompose.core.operations.Utils.floatToString;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+/**
+ * Base class for draw commands the take 6 floats
+ */
+public abstract class DrawBase6 extends PaintOperation
+        implements VariableSupport {
+    public static final Companion COMPANION =
+            new Companion(Operations.DRAW_RECT) {
+                public Operation construct(float x1, float y1, float x2, float y2) {
+                    //   return new DrawRectBase(x1, y1, x2, y2);
+                    return null;
+                }
+            };
+    protected String mName = "DrawRectBase";
+    float mV1;
+    float mV2;
+    float mV3;
+    float mV4;
+    float mV5;
+    float mV6;
+    float mValue1;
+    float mValue2;
+    float mValue3;
+    float mValue4;
+    float mValue5;
+    float mValue6;
+
+    public DrawBase6(
+            float v1,
+            float v2,
+            float v3,
+            float v4,
+            float v5,
+            float v6) {
+        mValue1 = v1;
+        mValue2 = v2;
+        mValue3 = v3;
+        mValue4 = v4;
+        mValue5 = v5;
+        mValue6 = v6;
+
+        mV1 = v1;
+        mV2 = v2;
+        mV3 = v3;
+        mV4 = v4;
+        mV5 = v5;
+        mV6 = v6;
+    }
+
+    @Override
+    public void updateVariables(RemoteContext context) {
+        mV1 = (Float.isNaN(mValue1))
+                ? context.getFloat(Utils.idFromNan(mValue1)) : mValue1;
+        mV2 = (Float.isNaN(mValue2))
+                ? context.getFloat(Utils.idFromNan(mValue2)) : mValue2;
+        mV3 = (Float.isNaN(mValue3))
+                ? context.getFloat(Utils.idFromNan(mValue3)) : mValue3;
+        mV4 = (Float.isNaN(mValue4))
+                ? context.getFloat(Utils.idFromNan(mValue4)) : mValue4;
+        mV5 = (Float.isNaN(mValue5))
+                ? context.getFloat(Utils.idFromNan(mValue5)) : mValue5;
+        mV6 = (Float.isNaN(mValue6))
+                ? context.getFloat(Utils.idFromNan(mValue6)) : mValue6;
+    }
+
+    @Override
+    public void registerListening(RemoteContext context) {
+        if (Float.isNaN(mValue1)) {
+            context.listensTo(Utils.idFromNan(mValue1), this);
+        }
+        if (Float.isNaN(mValue2)) {
+            context.listensTo(Utils.idFromNan(mValue2), this);
+        }
+        if (Float.isNaN(mValue3)) {
+            context.listensTo(Utils.idFromNan(mValue3), this);
+        }
+        if (Float.isNaN(mValue4)) {
+            context.listensTo(Utils.idFromNan(mValue4), this);
+        }
+        if (Float.isNaN(mValue5)) {
+            context.listensTo(Utils.idFromNan(mValue5), this);
+        }
+        if (Float.isNaN(mValue6)) {
+            context.listensTo(Utils.idFromNan(mValue6), this);
+        }
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        COMPANION.apply(buffer, mV1, mV2, mV3, mV4, mV5, mV6);
+    }
+
+    @Override
+    public String toString() {
+        return mName + " " + floatToString(mV1) + " " + floatToString(mV2)
+                + " " + floatToString(mV3) + " " + floatToString(mV4);
+    }
+
+    public static class Companion implements CompanionOperation {
+        public final int OP_CODE;
+
+        protected Companion(int code) {
+            OP_CODE = code;
+        }
+
+        @Override
+        public void read(WireBuffer buffer, List<Operation> operations) {
+            float sv1 = buffer.readFloat();
+            float sv2 = buffer.readFloat();
+            float sv3 = buffer.readFloat();
+            float sv4 = buffer.readFloat();
+            float sv5 = buffer.readFloat();
+            float sv6 = buffer.readFloat();
+
+            Operation op = construct(sv1, sv2, sv3, sv4, sv5, sv6);
+            operations.add(op);
+        }
+
+        /**
+         * writes out a the operation to the buffer.
+         * @param v1
+         * @param v2
+         * @param v3
+         * @param v4
+         * @param v5
+         * @param v6
+         * @return
+         */
+        public Operation construct(float v1,
+                                   float v2,
+                                   float v3,
+                                   float v4,
+                                   float v5,
+                                   float v6) {
+            return null;
+        }
+
+        @Override
+        public String name() {
+            return "DrawRect";
+        }
+
+        @Override
+        public int id() {
+            return OP_CODE;
+        }
+
+        /**
+         * Writes out the operation to the buffer
+         * @param buffer
+         * @param v1
+         * @param v2
+         * @param v3
+         * @param v4
+         * @param v5
+         * @param v6
+         */
+        public void apply(WireBuffer buffer,
+                          float v1,
+                          float v2,
+                          float v3,
+                          float v4,
+                          float v5,
+                          float v6) {
+            buffer.start(OP_CODE);
+            buffer.writeFloat(v1);
+            buffer.writeFloat(v2);
+            buffer.writeFloat(v3);
+            buffer.writeFloat(v4);
+            buffer.writeFloat(v5);
+            buffer.writeFloat(v6);
+        }
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java
index 2e971f5..ca40d12 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java
@@ -20,16 +20,22 @@
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
 import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
 import com.android.internal.widget.remotecompose.core.WireBuffer;
 
 import java.util.List;
 
-public class DrawBitmap extends PaintOperation {
+public class DrawBitmap extends PaintOperation implements VariableSupport {
     public static final Companion COMPANION = new Companion();
     float mLeft;
     float mTop;
     float mRight;
     float mBottom;
+    float mOutputLeft;
+    float mOutputTop;
+    float mOutputRight;
+    float mOutputBottom;
     int mId;
     int mDescriptionId = 0;
 
@@ -49,6 +55,34 @@
     }
 
     @Override
+    public void updateVariables(RemoteContext context) {
+        mOutputLeft = (Float.isNaN(mLeft))
+                ? context.getFloat(Utils.idFromNan(mLeft)) : mLeft;
+        mOutputTop = (Float.isNaN(mTop))
+                ? context.getFloat(Utils.idFromNan(mTop)) : mTop;
+        mOutputRight = (Float.isNaN(mRight))
+                ? context.getFloat(Utils.idFromNan(mRight)) : mRight;
+        mOutputBottom = (Float.isNaN(mBottom))
+                ? context.getFloat(Utils.idFromNan(mBottom)) : mBottom;
+    }
+
+    @Override
+    public void registerListening(RemoteContext context) {
+        if (Float.isNaN(mLeft)) {
+            context.listensTo(Utils.idFromNan(mLeft), this);
+        }
+        if (Float.isNaN(mTop)) {
+            context.listensTo(Utils.idFromNan(mTop), this);
+        }
+        if (Float.isNaN(mRight)) {
+            context.listensTo(Utils.idFromNan(mRight), this);
+        }
+        if (Float.isNaN(mBottom)) {
+            context.listensTo(Utils.idFromNan(mBottom), this);
+        }
+    }
+
+    @Override
     public void write(WireBuffer buffer) {
         COMPANION.apply(buffer, mId, mLeft, mTop, mRight, mBottom, mDescriptionId);
     }
@@ -105,9 +139,9 @@
 
     @Override
     public void paint(PaintContext context) {
-        context.drawBitmap(mId, mLeft,
-                mTop,
-                mRight,
-                mBottom);
+        context.drawBitmap(mId, mOutputLeft,
+                mOutputTop,
+                mOutputRight,
+                mOutputBottom);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java
index 9ce754d..3a22e4f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java
@@ -1,89 +1,31 @@
-/*
- * Copyright (C) 2024 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.internal.widget.remotecompose.core.operations;
 
-import com.android.internal.widget.remotecompose.core.CompanionOperation;
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
-import com.android.internal.widget.remotecompose.core.PaintOperation;
-import com.android.internal.widget.remotecompose.core.WireBuffer;
 
-import java.util.List;
+public class DrawCircle extends DrawBase3 {
+    public static final Companion COMPANION =
+            new Companion(Operations.DRAW_CIRCLE) {
+                @Override
+                public Operation construct(float x1,
+                                           float y1,
+                                           float x2
+                ) {
+                    return new DrawCircle(x1, y1, x2);
+                }
+            };
 
-public class DrawCircle extends PaintOperation {
-    public static final Companion COMPANION = new Companion();
-    float mCenterX;
-    float mCenterY;
-    float mRadius;
-
-    public DrawCircle(float centerX, float centerY, float radius) {
-        mCenterX = centerX;
-        mCenterY = centerY;
-        mRadius = radius;
-    }
-
-    @Override
-    public void write(WireBuffer buffer) {
-        COMPANION.apply(buffer, mCenterX,
-                mCenterY,
-                mRadius);
-    }
-
-    @Override
-    public String toString() {
-        return "";
-    }
-
-    public static class Companion implements CompanionOperation {
-        private Companion() {
-        }
-
-        @Override
-        public void read(WireBuffer buffer, List<Operation> operations) {
-            float centerX = buffer.readFloat();
-            float centerY = buffer.readFloat();
-            float radius = buffer.readFloat();
-
-            DrawCircle op = new DrawCircle(centerX, centerY, radius);
-            operations.add(op);
-        }
-
-        @Override
-        public String name() {
-            return "";
-        }
-
-        @Override
-        public int id() {
-            return 0;
-        }
-
-        public void apply(WireBuffer buffer, float centerX, float centerY, float radius) {
-            buffer.start(Operations.DRAW_CIRCLE);
-            buffer.writeFloat(centerX);
-            buffer.writeFloat(centerY);
-            buffer.writeFloat(radius);
-        }
+    public DrawCircle(
+            float left,
+            float top,
+            float right) {
+        super(left, top, right);
+        mName = "DrawCircle";
     }
 
     @Override
     public void paint(PaintContext context) {
-        context.drawCircle(mCenterX,
-                mCenterY,
-                mRadius);
+        context.drawCircle(mV1, mV2, mV3);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java
index c7a8315..c70c6ea 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java
@@ -15,83 +15,28 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
-import com.android.internal.widget.remotecompose.core.CompanionOperation;
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
-import com.android.internal.widget.remotecompose.core.PaintOperation;
-import com.android.internal.widget.remotecompose.core.WireBuffer;
 
-import java.util.List;
-
-public class DrawLine extends PaintOperation {
-    public static final Companion COMPANION = new Companion();
-    float mX1;
-    float mY1;
-    float mX2;
-    float mY2;
+public class DrawLine extends DrawBase4 {
+    public static final Companion COMPANION = new Companion(Operations.DRAW_LINE) {
+        @Override
+        public Operation construct(float x1,
+                                   float y1,
+                                   float x2,
+                                   float y2) {
+            return new DrawLine(x1, y1, x2, y2);
+        }
+    };
 
     public DrawLine(
-            float x1,
-            float y1,
-            float x2,
-            float y2) {
-        mX1 = x1;
-        mY1 = y1;
-        mX2 = x2;
-        mY2 = y2;
-    }
-
-    @Override
-    public void write(WireBuffer buffer) {
-        COMPANION.apply(buffer, mX1,
-                mY1,
-                mX2,
-                mY2);
-    }
-
-    @Override
-    public String toString() {
-        return "DrawArc " + mX1 + " " + mY1
-                + " " + mX2 + " " + mY2 + ";";
-    }
-
-    public static class Companion implements CompanionOperation {
-        private Companion() {
-        }
-
-        @Override
-        public void read(WireBuffer buffer, List<Operation> operations) {
-            float x1 = buffer.readFloat();
-            float y1 = buffer.readFloat();
-            float x2 = buffer.readFloat();
-            float y2 = buffer.readFloat();
-
-            DrawLine op = new DrawLine(x1, y1, x2, y2);
-            operations.add(op);
-        }
-
-        @Override
-        public String name() {
-            return "DrawLine";
-        }
-
-        @Override
-        public int id() {
-            return Operations.DRAW_LINE;
-        }
-
-        public void apply(WireBuffer buffer,
-                          float x1,
-                          float y1,
-                          float x2,
-                          float y2) {
-            buffer.start(Operations.DRAW_LINE);
-            buffer.writeFloat(x1);
-            buffer.writeFloat(y1);
-            buffer.writeFloat(x2);
-            buffer.writeFloat(y2);
-        }
+            float left,
+            float top,
+            float right,
+            float bottom) {
+        super(left, top, right, bottom);
+        mName = "DrawLine";
     }
 
     @Override
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java
index 7143753..ba17994 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java
@@ -15,88 +15,33 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
-import com.android.internal.widget.remotecompose.core.CompanionOperation;
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
-import com.android.internal.widget.remotecompose.core.PaintOperation;
-import com.android.internal.widget.remotecompose.core.WireBuffer;
 
-import java.util.List;
-
-public class DrawOval extends PaintOperation {
-    public static final Companion COMPANION = new Companion();
-    float mLeft;
-    float mTop;
-    float mRight;
-    float mBottom;
-
+public class DrawOval extends DrawBase4 {
+    public static final Companion COMPANION =
+            new Companion(Operations.DRAW_OVAL) {
+                @Override
+                public Operation construct(float x1,
+                                           float y1,
+                                           float x2,
+                                           float y2) {
+                    return new DrawOval(x1, y1, x2, y2);
+                }
+            };
 
     public DrawOval(
             float left,
             float top,
             float right,
             float bottom) {
-        mLeft = left;
-        mTop = top;
-        mRight = right;
-        mBottom = bottom;
-    }
-
-    @Override
-    public void write(WireBuffer buffer) {
-        COMPANION.apply(buffer, mLeft, mTop, mRight, mBottom);
-    }
-
-    @Override
-    public String toString() {
-        return "DrawOval " + mLeft + " " + mTop
-                + " " + mRight + " " + mBottom + ";";
-    }
-
-    public static class Companion implements CompanionOperation {
-        private Companion() {
-        }
-
-        @Override
-        public void read(WireBuffer buffer, List<Operation> operations) {
-            float sLeft = buffer.readFloat();
-            float srcTop = buffer.readFloat();
-            float srcRight = buffer.readFloat();
-            float srcBottom = buffer.readFloat();
-
-            DrawOval op = new DrawOval(sLeft, srcTop, srcRight, srcBottom);
-            operations.add(op);
-        }
-
-        @Override
-        public String name() {
-            return "DrawOval";
-        }
-
-        @Override
-        public int id() {
-            return Operations.DRAW_OVAL;
-        }
-
-        public void apply(WireBuffer buffer,
-                          float left,
-                          float top,
-                          float right,
-                          float bottom) {
-            buffer.start(Operations.DRAW_OVAL);
-            buffer.writeFloat(left);
-            buffer.writeFloat(top);
-            buffer.writeFloat(right);
-            buffer.writeFloat(bottom);
-        }
+        super(left, top, right, bottom);
+        mName = "DrawOval";
     }
 
     @Override
     public void paint(PaintContext context) {
-        context.drawOval(mLeft,
-                mTop,
-                mRight,
-                mBottom);
+        context.drawOval(mX1, mY1, mX2, mY2);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java
index 7b8a9e9..6dbc5a6 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java
@@ -41,7 +41,7 @@
 
     @Override
     public String toString() {
-        return "DrawPath " + ";";
+        return "DrawPath " + "[" + mId + "]" + ", " + mStart + ", " + mEnd;
     }
 
     public static class Companion implements CompanionOperation {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java
index 4775241..633aed4 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java
@@ -15,88 +15,37 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
-import com.android.internal.widget.remotecompose.core.CompanionOperation;
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
-import com.android.internal.widget.remotecompose.core.PaintOperation;
-import com.android.internal.widget.remotecompose.core.WireBuffer;
 
-import java.util.List;
-
-public class DrawRect extends PaintOperation {
-    public static final Companion COMPANION = new Companion();
-    float mLeft;
-    float mTop;
-    float mRight;
-    float mBottom;
+/**
+ * Draw a Rectangle
+ */
+public class DrawRect extends DrawBase4 {
+    public static final Companion COMPANION =
+            new Companion(Operations.DRAW_RECT) {
+                @Override
+                public Operation construct(float x1,
+                                           float y1,
+                                           float x2,
+                                           float y2) {
+                    return new DrawRect(x1, y1, x2, y2);
+                }
+            };
 
     public DrawRect(
             float left,
             float top,
             float right,
             float bottom) {
-        mLeft = left;
-        mTop = top;
-        mRight = right;
-        mBottom = bottom;
-    }
-
-    @Override
-    public void write(WireBuffer buffer) {
-        COMPANION.apply(buffer, mLeft, mTop, mRight, mBottom);
-    }
-
-    @Override
-    public String toString() {
-        return "DrawRect " + mLeft + " " + mTop
-                + " " + mRight + " " + mBottom + ";";
-    }
-
-    public static class Companion implements CompanionOperation {
-        private Companion() {
-        }
-
-        @Override
-        public void read(WireBuffer buffer, List<Operation> operations) {
-            float sLeft = buffer.readFloat();
-            float srcTop = buffer.readFloat();
-            float srcRight = buffer.readFloat();
-            float srcBottom = buffer.readFloat();
-
-            DrawRect op = new DrawRect(sLeft, srcTop, srcRight, srcBottom);
-            operations.add(op);
-        }
-
-        @Override
-        public String name() {
-            return "DrawRect";
-        }
-
-        @Override
-        public int id() {
-            return Operations.DRAW_RECT;
-        }
-
-        public void apply(WireBuffer buffer,
-                          float left,
-                          float top,
-                          float right,
-                          float bottom) {
-            buffer.start(Operations.DRAW_RECT);
-            buffer.writeFloat(left);
-            buffer.writeFloat(top);
-            buffer.writeFloat(right);
-            buffer.writeFloat(bottom);
-        }
+        super(left, top, right, bottom);
+        mName = "DrawRect";
     }
 
     @Override
     public void paint(PaintContext context) {
-        context.drawRect(mLeft,
-                mTop,
-                mRight,
-                mBottom);
+        context.drawRect(mX1, mY1, mX2, mY2);
     }
 
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java
index 8da16e7..b9d0a67 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java
@@ -15,104 +15,40 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
-import com.android.internal.widget.remotecompose.core.CompanionOperation;
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
-import com.android.internal.widget.remotecompose.core.PaintOperation;
-import com.android.internal.widget.remotecompose.core.WireBuffer;
 
-import java.util.List;
+/**
+ * Draw a rounded rectangle
+ */
+public class DrawRoundRect extends DrawBase6 {
+    public static final Companion COMPANION =
+            new Companion(Operations.DRAW_ROUND_RECT) {
+                @Override
+                public Operation construct(float v1,
+                                           float v2,
+                                           float v3,
+                                           float v4,
+                                           float v5,
+                                           float v6) {
+                    return new DrawRoundRect(v1, v2, v3, v4, v5, v6);
+                }
+            };
 
-public class DrawRoundRect extends PaintOperation {
-    public static final Companion COMPANION = new Companion();
-    float mLeft;
-    float mTop;
-    float mRight;
-    float mBottom;
-    float mRadiusX;
-    float mRadiusY;
-
-    public DrawRoundRect(
-            float left,
-            float top,
-            float right,
-            float bottom,
-            float radiusX,
-            float radiusY) {
-        mLeft = left;
-        mTop = top;
-        mRight = right;
-        mBottom = bottom;
-        mRadiusX = radiusX;
-        mRadiusY = radiusY;
-    }
-
-    @Override
-    public void write(WireBuffer buffer) {
-        COMPANION.apply(buffer, mLeft, mTop, mRight, mBottom, mRadiusX, mRadiusY);
-    }
-
-    @Override
-    public String toString() {
-        return "DrawRoundRect " + mLeft + " " + mTop
-                + " " + mRight + " " + mBottom
-                + " (" + mRadiusX + " " + mRadiusY + ");";
-    }
-
-    public static class Companion implements CompanionOperation {
-        private Companion() {
-        }
-
-        @Override
-        public void read(WireBuffer buffer, List<Operation> operations) {
-            float sLeft = buffer.readFloat();
-            float srcTop = buffer.readFloat();
-            float srcRight = buffer.readFloat();
-            float srcBottom = buffer.readFloat();
-            float srcRadiusX = buffer.readFloat();
-            float srcRadiusY = buffer.readFloat();
-
-            DrawRoundRect op = new DrawRoundRect(sLeft, srcTop, srcRight,
-                    srcBottom, srcRadiusX, srcRadiusY);
-            operations.add(op);
-        }
-
-        @Override
-        public String name() {
-            return "DrawOval";
-        }
-
-        @Override
-        public int id() {
-            return Operations.DRAW_ROUND_RECT;
-        }
-
-        public void apply(WireBuffer buffer,
-                          float left,
-                          float top,
-                          float right,
-                          float bottom,
-                          float radiusX,
-                          float radiusY) {
-            buffer.start(Operations.DRAW_ROUND_RECT);
-            buffer.writeFloat(left);
-            buffer.writeFloat(top);
-            buffer.writeFloat(right);
-            buffer.writeFloat(bottom);
-            buffer.writeFloat(radiusX);
-            buffer.writeFloat(radiusY);
-        }
+    public DrawRoundRect(float v1,
+                         float v2,
+                         float v3,
+                         float v4,
+                         float v5,
+                         float v6) {
+        super(v1, v2, v3, v4, v5, v6);
+        mName = "ClipRect";
     }
 
     @Override
     public void paint(PaintContext context) {
-        context.drawRoundRect(mLeft,
-                mTop,
-                mRight,
-                mBottom,
-                mRadiusX,
-                mRadiusY
+        context.drawRoundRect(mV1, mV2, mV3, mV4, mV5, mV6
         );
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java
new file mode 100644
index 0000000..f8f8afd
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2024 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.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+/**
+ * Draw Text
+ */
+public class DrawText extends PaintOperation {
+    public static final Companion COMPANION = new Companion();
+    int mTextID;
+    int mStart = 0;
+    int mEnd = 0;
+    int mContextStart = 0;
+    int mContextEnd = 0;
+    float mX = 0f;
+    float mY = 0f;
+    boolean mRtl = false;
+
+    public DrawText(int textID,
+                    int start,
+                    int end,
+                    int contextStart,
+                    int contextEnd,
+                    float x,
+                    float y,
+                    boolean rtl) {
+        mTextID = textID;
+        mStart = start;
+        mEnd = end;
+        mContextStart = contextStart;
+        mContextEnd = contextEnd;
+        mX = x;
+        mY = y;
+        mRtl = rtl;
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        COMPANION.apply(buffer, mTextID, mStart, mEnd, mContextStart, mContextEnd, mX, mY, mRtl);
+
+    }
+
+    @Override
+    public String toString() {
+        return "DrawTextRun [" + mTextID + "] " + mStart + ", " + mEnd + ", " + mX + ", " + mY;
+    }
+
+    public static class Companion implements CompanionOperation {
+        private Companion() {
+        }
+
+        @Override
+        public void read(WireBuffer buffer, List<Operation> operations) {
+            int text = buffer.readInt();
+            int start = buffer.readInt();
+            int end = buffer.readInt();
+            int contextStart = buffer.readInt();
+            int contextEnd = buffer.readInt();
+            float x = buffer.readFloat();
+            float y = buffer.readFloat();
+            boolean rtl = buffer.readBoolean();
+            DrawText op = new DrawText(text, start, end, contextStart, contextEnd, x, y, rtl);
+
+            operations.add(op);
+        }
+
+        @Override
+        public String name() {
+            return "";
+        }
+
+        @Override
+        public int id() {
+            return 0;
+        }
+
+        /**
+         * Writes out the operation to the buffer
+         * @param buffer
+         * @param textID
+         * @param start
+         * @param end
+         * @param contextStart
+         * @param contextEnd
+         * @param x
+         * @param y
+         * @param rtl
+         */
+        public void apply(WireBuffer buffer,
+                          int textID,
+                          int start,
+                          int end,
+                          int contextStart,
+                          int contextEnd,
+                          float x,
+                          float y,
+                          boolean rtl) {
+            buffer.start(Operations.DRAW_TEXT_RUN);
+            buffer.writeInt(textID);
+            buffer.writeInt(start);
+            buffer.writeInt(end);
+            buffer.writeInt(contextStart);
+            buffer.writeInt(contextEnd);
+            buffer.writeFloat(x);
+            buffer.writeFloat(y);
+            buffer.writeBoolean(rtl);
+        }
+    }
+
+    @Override
+    public void paint(PaintContext context) {
+        context.drawTextRun(mTextID, mStart, mEnd, mContextStart, mContextEnd, mX, mY, mRtl);
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java
new file mode 100644
index 0000000..4f0641f
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2024 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.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+/**
+ * Draw Text in Anchored to a point
+ */
+public class DrawTextAnchored extends PaintOperation implements VariableSupport {
+    public static final Companion COMPANION = new Companion();
+    int mTextID;
+    float mX;
+    float mY;
+    float mPanX;
+    float mPanY;
+    int mFlags;
+    float mOutX;
+    float mOutY;
+    float mOutPanX;
+    float mOutPanY;
+
+    public static final int ANCHOR_TEXT_RTL = 1;
+    public static final int ANCHOR_MONOSPACE_MEASURE = 2;
+
+    public DrawTextAnchored(int textID,
+                            float x,
+                            float y,
+                            float panX,
+                            float panY,
+                            int flags) {
+        mTextID = textID;
+        mX = x;
+        mY = y;
+        mOutX = mX;
+        mOutY = mY;
+        mFlags = flags;
+        mOutPanX = mPanX = panX;
+        mOutPanY = mPanY = panY;
+    }
+
+    @Override
+    public void updateVariables(RemoteContext context) {
+        mOutX = (Float.isNaN(mX))
+                ? context.getFloat(Utils.idFromNan(mX)) : mX;
+        mOutY = (Float.isNaN(mY))
+                ? context.getFloat(Utils.idFromNan(mY)) : mY;
+        mOutPanX = (Float.isNaN(mPanX))
+                ? context.getFloat(Utils.idFromNan(mPanX)) : mPanX;
+        mOutPanY = (Float.isNaN(mPanY))
+                ? context.getFloat(Utils.idFromNan(mPanY)) : mPanY;
+    }
+
+    @Override
+    public void registerListening(RemoteContext context) {
+        if (Float.isNaN(mX)) {
+            context.listensTo(Utils.idFromNan(mX), this);
+        }
+        if (Float.isNaN(mY)) {
+            context.listensTo(Utils.idFromNan(mY), this);
+        }
+        if (Float.isNaN(mPanX)) {
+            context.listensTo(Utils.idFromNan(mPanX), this);
+        }
+        if (Float.isNaN(mPanY) && Utils.idFromNan(mPanY) > 0) {
+            context.listensTo(Utils.idFromNan(mPanY), this);
+        }
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        COMPANION.apply(buffer, mTextID, mX,
+                mY,
+                mPanX,
+                mPanY,
+                mFlags);
+    }
+
+    @Override
+    public String toString() {
+        return "DrawTextAnchored [" + mTextID + "] " + floatToStr(mX) + ", "
+                + floatToStr(mY) + ", "
+                + floatToStr(mPanX) + ", " + floatToStr(mPanY) + ", "
+                + Integer.toBinaryString(mFlags);
+    }
+
+    private static String floatToStr(float v) {
+        if (Float.isNaN(v)) {
+            return "[" + Utils.idFromNan(v) + "]";
+        }
+        return Float.toString(v);
+    }
+
+    public static class Companion implements CompanionOperation {
+        private Companion() {
+        }
+
+        @Override
+        public void read(WireBuffer buffer, List<Operation> operations) {
+            int textID = buffer.readInt();
+            float x = buffer.readFloat();
+            float y = buffer.readFloat();
+            float panX = buffer.readFloat();
+            float panY = buffer.readFloat();
+            int flags = buffer.readInt();
+
+            DrawTextAnchored op = new DrawTextAnchored(textID,
+                    x, y,
+                    panX, panY,
+                    flags);
+
+            operations.add(op);
+        }
+
+        @Override
+        public String name() {
+            return "";
+        }
+
+        @Override
+        public int id() {
+            return 0;
+        }
+
+        /**
+         * Writes out the operation to the buffer
+         * @param buffer
+         * @param textID
+         * @param x
+         * @param y
+         * @param panX
+         * @param panY
+         * @param flags
+         */
+        public void apply(WireBuffer buffer,
+                          int textID,
+                          float x,
+                          float y,
+                          float panX,
+                          float panY,
+                          int flags) {
+            buffer.start(Operations.DRAW_TEXT_ANCHOR);
+            buffer.writeInt(textID);
+            buffer.writeFloat(x);
+            buffer.writeFloat(y);
+            buffer.writeFloat(panX);
+            buffer.writeFloat(panY);
+            buffer.writeInt(flags);
+        }
+    }
+
+    float[] mBounds = new float[4];
+
+    private float getHorizontalOffset() {
+        // TODO scale  TextSize / BaseTextSize;
+        float scale = 1.0f;
+
+        float textWidth = scale * (mBounds[2] - mBounds[0]);
+        float boxWidth = 0;
+        return (boxWidth - textWidth) * (1 + mOutPanX) / 2.f
+                - (scale * mBounds[0]);
+    }
+
+    private float getVerticalOffset() {
+        // TODO scale TextSize / BaseTextSize;
+        float scale = 1.0f;
+        float boxHeight = 0;
+        float textHeight = scale * (mBounds[3] - mBounds[1]);
+        return (boxHeight - textHeight) * (1 - mOutPanY) / 2
+                - (scale * mBounds[1]);
+    }
+
+    @Override
+    public void paint(PaintContext context) {
+        context.getTextBounds(mTextID, 0, -1,
+                (mFlags & ANCHOR_MONOSPACE_MEASURE) != 0, mBounds);
+        float x = mOutX + getHorizontalOffset();
+        float y = (Float.isNaN(mOutPanY)) ? mOutY : mOutY + getVerticalOffset();
+        context.drawTextRun(mTextID, 0, -1, 0, 1, x, y,
+                (mFlags & ANCHOR_TEXT_RTL) == 1);
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java
index 1856e30..b1a0172 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java
@@ -24,6 +24,9 @@
 
 import java.util.List;
 
+/**
+ * Draw text along a path.
+ */
 public class DrawTextOnPath extends PaintOperation {
     public static final Companion COMPANION = new Companion();
     int mPathId;
@@ -45,7 +48,8 @@
 
     @Override
     public String toString() {
-        return "DrawTextOnPath " + " " + mPathId + ";";
+        return "DrawTextOnPath [" + mTextId + "] [" + mPathId + "] "
+                + mHOffset + ", " + mVOffset;
     }
 
     public static class Companion implements CompanionOperation {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java
index ef0a4ad..48fc94e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java
@@ -58,7 +58,7 @@
     public String toString() {
         return "DrawTweenPath " + mPath1Id + " " + mPath2Id
                 + " " + mTween + " " + mStart + " "
-                + "- " + mStop + ";";
+                + "- " + mStop;
     }
 
     public static class Companion implements CompanionOperation {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java
new file mode 100644
index 0000000..576b53f
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2023 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.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+/**
+ * Operation to deal with Text data
+ */
+public class FloatConstant implements Operation {
+    public int mTextId;
+    public float mValue;
+    public static final Companion COMPANION = new Companion();
+    public static final int MAX_STRING_SIZE = 4000;
+
+    public FloatConstant(int textId, float value) {
+        this.mTextId = textId;
+        this.mValue = value;
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        COMPANION.apply(buffer, mTextId, mValue);
+    }
+
+    @Override
+    public String toString() {
+        return "FloatConstant[" + mTextId + "] = " + mValue + "";
+    }
+
+    public static class Companion implements CompanionOperation {
+        private Companion() {}
+
+        @Override
+        public String name() {
+            return "FloatExpression";
+        }
+
+        @Override
+        public int id() {
+            return Operations.DATA_FLOAT;
+        }
+
+        /**
+         * Writes out the operation to the buffer
+         * @param buffer
+         * @param textId
+         * @param value
+         */
+        public void apply(WireBuffer buffer, int textId, float value) {
+            buffer.start(Operations.DATA_FLOAT);
+            buffer.writeInt(textId);
+            buffer.writeFloat(value);
+        }
+
+        @Override
+        public void read(WireBuffer buffer, List<Operation> operations) {
+            int textId = buffer.readInt();
+
+            float value = buffer.readFloat();
+            operations.add(new FloatConstant(textId, value));
+        }
+    }
+
+    @Override
+    public void apply(RemoteContext context) {
+        context.loadFloat(mTextId, mValue);
+    }
+
+    @Override
+    public String deepToString(String indent) {
+        return indent + toString();
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java
new file mode 100644
index 0000000..354f41b
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2023 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.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.operations.utilities.AnimatedFloatExpression;
+import com.android.internal.widget.remotecompose.core.operations.utilities.easing.FloatAnimation;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Operation to deal with AnimatedFloats
+ * This is designed to be an optimized calculation for things like
+ * injecting the width of the component int draw rect
+ * As well as supporting generalized animation floats.
+ * The floats represent a RPN style calculator
+ */
+public class FloatExpression implements Operation, VariableSupport {
+    public int mId;
+    public float[] mSrcValue;
+    public float[] mSrcAnimation;
+    public FloatAnimation mFloatAnimation;
+    public float[] mPreCalcValue;
+    private float mLastChange = Float.NaN;
+    AnimatedFloatExpression mExp = new AnimatedFloatExpression();
+    public static final Companion COMPANION = new Companion();
+    public static final int MAX_STRING_SIZE = 4000;
+
+    public FloatExpression(int id, float[] value, float[] animation) {
+        this.mId = id;
+        this.mSrcValue = value;
+        this.mSrcAnimation = animation;
+        if (mSrcAnimation != null) {
+            mFloatAnimation = new FloatAnimation(mSrcAnimation);
+        }
+    }
+
+    @Override
+    public void updateVariables(RemoteContext context) {
+        if (mPreCalcValue == null || mPreCalcValue.length != mSrcValue.length) {
+            mPreCalcValue = new float[mSrcValue.length];
+        }
+        //Utils.log("updateVariables ");
+        boolean value_changed = false;
+        for (int i = 0; i < mSrcValue.length; i++) {
+            float v = mSrcValue[i];
+            if (Float.isNaN(v) && !AnimatedFloatExpression.isMathOperator(v)) {
+                float newValue = context.getFloat(Utils.idFromNan(v));
+                if (mFloatAnimation != null) {
+                    if (mPreCalcValue[i] != newValue) {
+                        mLastChange = context.getAnimationTime();
+                        value_changed = true;
+                        mPreCalcValue[i] = newValue;
+                    }
+                } else {
+                    mPreCalcValue[i] = newValue;
+                }
+            } else {
+                mPreCalcValue[i] = mSrcValue[i];
+            }
+        }
+        if (value_changed && mFloatAnimation != null) {
+            float v = mExp.eval(Arrays.copyOf(mPreCalcValue, mPreCalcValue.length));
+            if (Float.isNaN(mFloatAnimation.getTargetValue())) {
+                mFloatAnimation.setInitialValue(v);
+            } else {
+                mFloatAnimation.setInitialValue(mFloatAnimation.getTargetValue());
+            }
+            mFloatAnimation.setTargetValue(v);
+        }
+    }
+
+    @Override
+    public void registerListening(RemoteContext context) {
+        for (int i = 0; i < mSrcValue.length; i++) {
+            float v = mSrcValue[i];
+            if (Float.isNaN(v) && !AnimatedFloatExpression.isMathOperator(v)) {
+                context.listensTo(Utils.idFromNan(v), this);
+            }
+        }
+    }
+
+    @Override
+    public void apply(RemoteContext context) {
+        updateVariables(context);
+        float t = context.getAnimationTime();
+        if (Float.isNaN(mLastChange)) {
+            mLastChange = t;
+        }
+        if (mFloatAnimation != null) {
+            float f = mFloatAnimation.get(t - mLastChange);
+            context.loadFloat(mId, f);
+        } else {
+            context.loadFloat(mId, mExp.eval(Arrays.copyOf(mPreCalcValue, mPreCalcValue.length)));
+        }
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        COMPANION.apply(buffer, mId, mSrcValue, mSrcAnimation);
+    }
+
+    @Override
+    public String toString() {
+        String[] labels = new String[mSrcValue.length];
+        for (int i = 0; i < mSrcValue.length; i++) {
+            if (Float.isNaN(mSrcValue[i])) {
+                labels[i] = "[" + Utils.idFromNan(mSrcValue[i]) + "]";
+            }
+
+        }
+        return "FloatExpression[" + mId + "] = ("
+                + AnimatedFloatExpression.toString(mPreCalcValue, labels) + ")";
+    }
+
+    public static class Companion implements CompanionOperation {
+        private Companion() {
+        }
+
+        @Override
+        public String name() {
+            return "FloatExpression";
+        }
+
+        @Override
+        public int id() {
+            return Operations.ANIMATED_FLOAT;
+        }
+
+        /**
+         * Writes out the operation to the buffer
+         * @param buffer
+         * @param id
+         * @param value
+         * @param animation
+         */
+        public void apply(WireBuffer buffer, int id, float[] value, float[] animation) {
+            buffer.start(Operations.ANIMATED_FLOAT);
+            buffer.writeInt(id);
+
+            int len = value.length;
+            if (animation != null) {
+                len |= (animation.length << 16);
+            }
+            buffer.writeInt(len);
+
+            for (int i = 0; i < value.length; i++) {
+                buffer.writeFloat(value[i]);
+            }
+            if (animation != null) {
+                for (int i = 0; i < animation.length; i++) {
+                    buffer.writeFloat(animation[i]);
+                }
+            }
+
+        }
+
+        @Override
+        public void read(WireBuffer buffer, List<Operation> operations) {
+            int id = buffer.readInt();
+            int len = buffer.readInt();
+            int valueLen = len & 0xFFFF;
+            int animLen = (len >> 16) & 0xFFFF;
+            float[] values = new float[valueLen];
+            for (int i = 0; i < values.length; i++) {
+                values[i] = buffer.readFloat();
+            }
+
+            float[] animation;
+            if (animLen != 0) {
+                animation = new float[animLen];
+                for (int i = 0; i < animation.length; i++) {
+                    animation[i] = buffer.readFloat();
+                }
+            } else {
+                animation = null;
+            }
+            operations.add(new FloatExpression(id, values, animation));
+        }
+    }
+
+    @Override
+    public String deepToString(String indent) {
+        return indent + toString();
+    }
+
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java
index 482e0e2..0dad45c 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java
@@ -37,7 +37,7 @@
 
     @Override
     public String toString() {
-        return "MatrixRestore;";
+        return "MatrixRestore";
     }
 
     public static class Companion implements CompanionOperation {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java
index d6c89e0..bbf4135 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java
@@ -15,68 +15,29 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
-import com.android.internal.widget.remotecompose.core.CompanionOperation;
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
-import com.android.internal.widget.remotecompose.core.PaintOperation;
-import com.android.internal.widget.remotecompose.core.WireBuffer;
 
-import java.util.List;
-
-public class MatrixRotate extends PaintOperation {
-    public static final Companion COMPANION = new Companion();
-    float mRotate, mPivotX, mPivotY;
+public class MatrixRotate extends DrawBase3 {
+    public static final Companion COMPANION =
+            new Companion(Operations.MATRIX_ROTATE) {
+                @Override
+                public Operation construct(float rotate,
+                                           float pivotX,
+                                           float pivotY
+                ) {
+                    return new MatrixRotate(rotate, pivotX, pivotY);
+                }
+            };
 
     public MatrixRotate(float rotate, float pivotX, float pivotY) {
-        mRotate = rotate;
-        mPivotX = pivotX;
-        mPivotY = pivotY;
-    }
-
-    @Override
-    public void write(WireBuffer buffer) {
-        COMPANION.apply(buffer, mRotate, mPivotX, mPivotY);
-    }
-
-    @Override
-    public String toString() {
-        return "DrawArc " + mRotate + ", " + mPivotX + ", " + mPivotY + ";";
-    }
-
-    public static class Companion implements CompanionOperation {
-        private Companion() {
-        }
-
-        @Override
-        public void read(WireBuffer buffer, List<Operation> operations) {
-            float rotate = buffer.readFloat();
-            float pivotX = buffer.readFloat();
-            float pivotY = buffer.readFloat();
-            MatrixRotate op = new MatrixRotate(rotate, pivotX, pivotY);
-            operations.add(op);
-        }
-
-        @Override
-        public String name() {
-            return "Matrix";
-        }
-
-        @Override
-        public int id() {
-            return Operations.MATRIX_ROTATE;
-        }
-
-        public void apply(WireBuffer buffer, float rotate, float pivotX, float pivotY) {
-            buffer.start(Operations.MATRIX_ROTATE);
-            buffer.writeFloat(rotate);
-            buffer.writeFloat(pivotX);
-            buffer.writeFloat(pivotY);
-        }
+        super(rotate, pivotX, pivotY);
+        mName = "MatrixRotate";
     }
 
     @Override
     public void paint(PaintContext context) {
-        context.matrixRotate(mRotate, mPivotX, mPivotY);
+        context.matrixRotate(mV1, mV2, mV3);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java
index 28aa68dd..04b940b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java
@@ -15,74 +15,30 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
-import com.android.internal.widget.remotecompose.core.CompanionOperation;
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
-import com.android.internal.widget.remotecompose.core.PaintOperation;
-import com.android.internal.widget.remotecompose.core.WireBuffer;
 
-import java.util.List;
-
-public class MatrixScale extends PaintOperation {
-    public static final Companion COMPANION = new Companion();
-    float mScaleX, mScaleY;
-    float mCenterX, mCenterY;
+public class MatrixScale extends DrawBase4 {
+    public static final Companion COMPANION =
+            new Companion(Operations.MATRIX_SCALE) {
+                @Override
+                public Operation construct(float scaleX,
+                                           float scaleY,
+                                           float centerX,
+                                           float centerY
+                ) {
+                    return new MatrixScale(scaleX, scaleY, centerX, centerY);
+                }
+            };
 
     public MatrixScale(float scaleX, float scaleY, float centerX, float centerY) {
-        mScaleX = scaleX;
-        mScaleY = scaleY;
-        mCenterX = centerX;
-        mCenterY = centerY;
-    }
-
-    @Override
-    public void write(WireBuffer buffer) {
-        COMPANION.apply(buffer, mScaleX, mScaleY, mCenterX, mCenterY);
-    }
-
-    @Override
-    public String toString() {
-        return "MatrixScale " + mScaleY + ", " + mScaleY + ";";
-    }
-
-    public static class Companion implements CompanionOperation {
-        private Companion() {
-        }
-
-        @Override
-        public void read(WireBuffer buffer, List<Operation> operations) {
-            float scaleX = buffer.readFloat();
-            float scaleY = buffer.readFloat();
-            float centerX = buffer.readFloat();
-            float centerY = buffer.readFloat();
-            MatrixScale op = new MatrixScale(scaleX, scaleY, centerX, centerY);
-            operations.add(op);
-        }
-
-        @Override
-        public String name() {
-            return "Matrix";
-        }
-
-        @Override
-        public int id() {
-            return Operations.MATRIX_SCALE;
-        }
-
-        public void apply(WireBuffer buffer, float scaleX, float scaleY,
-                float centerX, float centerY) {
-            buffer.start(Operations.MATRIX_SCALE);
-            buffer.writeFloat(scaleX);
-            buffer.writeFloat(scaleY);
-            buffer.writeFloat(centerX);
-            buffer.writeFloat(centerY);
-
-        }
+        super(scaleX, scaleY, centerX, centerY);
+        mName = "MatrixScale";
     }
 
     @Override
     public void paint(PaintContext context) {
-        context.mtrixScale(mScaleX, mScaleY, mCenterX, mCenterY);
+        context.matrixScale(mX1, mY1, mX2, mY2);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java
index 3298752..4f34e98 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java
@@ -15,65 +15,28 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
-import com.android.internal.widget.remotecompose.core.CompanionOperation;
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
-import com.android.internal.widget.remotecompose.core.PaintOperation;
-import com.android.internal.widget.remotecompose.core.WireBuffer;
 
-import java.util.List;
-
-public class MatrixTranslate extends PaintOperation {
-    public static final Companion COMPANION = new Companion();
-    float mTranslateX, mTranslateY;
+public class MatrixTranslate extends DrawBase2 {
+    public static final Companion COMPANION =
+            new Companion(Operations.MATRIX_TRANSLATE) {
+                @Override
+                public Operation construct(float x1,
+                                           float y1
+                ) {
+                    return new MatrixTranslate(x1, y1);
+                }
+            };
 
     public MatrixTranslate(float translateX, float translateY) {
-        mTranslateX = translateX;
-        mTranslateY = translateY;
-    }
-
-    @Override
-    public void write(WireBuffer buffer) {
-        COMPANION.apply(buffer, mTranslateX, mTranslateY);
-    }
-
-    @Override
-    public String toString() {
-        return "DrawArc " + mTranslateY + ", " + mTranslateY + ";";
-    }
-
-    public static class Companion implements CompanionOperation {
-        private Companion() {
-        }
-
-        @Override
-        public void read(WireBuffer buffer, List<Operation> operations) {
-            float translateX = buffer.readFloat();
-            float translateY = buffer.readFloat();
-            MatrixTranslate op = new MatrixTranslate(translateX, translateY);
-            operations.add(op);
-        }
-
-        @Override
-        public String name() {
-            return "Matrix";
-        }
-
-        @Override
-        public int id() {
-            return Operations.MATRIX_TRANSLATE;
-        }
-
-        public void apply(WireBuffer buffer, float translateX, float translateY) {
-            buffer.start(Operations.MATRIX_TRANSLATE);
-            buffer.writeFloat(translateX);
-            buffer.writeFloat(translateY);
-        }
+        super(translateX, translateY);
+        mName = "MatrixTranslate";
     }
 
     @Override
     public void paint(PaintContext context) {
-        context.matrixTranslate(mTranslateX, mTranslateY);
+        context.matrixTranslate(mV1, mV2);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java b/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java
new file mode 100644
index 0000000..0c5b286
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2023 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.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+/**
+ * Operation to deal with Text data
+ */
+public class NamedVariable implements Operation {
+    public int mVarId;
+    public String mVarName;
+    public int mVarType;
+    public static final Companion COMPANION = new Companion();
+    public static final int MAX_STRING_SIZE = 4000;
+
+    public NamedVariable(int varId, int varType, String name) {
+        this.mVarId = varId;
+        this.mVarType = varType;
+        this.mVarName = name;
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        COMPANION.apply(buffer, mVarId, mVarType, mVarName);
+    }
+
+    @Override
+    public String toString() {
+        return "VariableName[" + mVarId + "] = \""
+                + Utils.trimString(mVarName, 10) + "\" type=" + mVarType;
+    }
+
+    public static class Companion implements CompanionOperation {
+        private Companion() {
+        }
+
+        @Override
+        public String name() {
+            return "TextData";
+        }
+
+        @Override
+        public int id() {
+            return Operations.DATA_TEXT;
+        }
+
+        /**
+         * Writes out the operation to the buffer
+         * @param buffer
+         * @param varId
+         * @param varType
+         * @param text
+         */
+        public void apply(WireBuffer buffer, int varId, int varType, String text) {
+            buffer.start(Operations.DATA_TEXT);
+            buffer.writeInt(varId);
+            buffer.writeInt(varType);
+            buffer.writeUTF8(text);
+        }
+
+        @Override
+        public void read(WireBuffer buffer, List<Operation> operations) {
+            int varId = buffer.readInt();
+            int varType = buffer.readInt();
+            String text = buffer.readUTF8(MAX_STRING_SIZE);
+            operations.add(new NamedVariable(varId, varType, text));
+        }
+    }
+
+    @Override
+    public void apply(RemoteContext context) {
+        context.loadVariableName(mVarName, mVarId, mVarType);
+    }
+
+    @Override
+    public String deepToString(String indent) {
+        return indent + toString();
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java
index e5683ec..0807bcd 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java
@@ -20,12 +20,14 @@
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
 import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
 import com.android.internal.widget.remotecompose.core.WireBuffer;
 import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
 
 import java.util.List;
 
-public class PaintData extends PaintOperation {
+public class PaintData extends PaintOperation implements VariableSupport {
     public PaintBundle mPaintData = new PaintBundle();
     public static final Companion COMPANION = new Companion();
     public static final int MAX_STRING_SIZE = 4000;
@@ -34,6 +36,16 @@
     }
 
     @Override
+    public void updateVariables(RemoteContext context) {
+        mPaintData.updateVariables(context);
+    }
+
+    @Override
+    public void registerListening(RemoteContext context) {
+        mPaintData.registerVars(context, this);
+    }
+
+    @Override
     public void write(WireBuffer buffer) {
         COMPANION.apply(buffer, mPaintData);
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java
index 2646b27..e467e7b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java
@@ -18,27 +18,50 @@
 import com.android.internal.widget.remotecompose.core.CompanionOperation;
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
-import com.android.internal.widget.remotecompose.core.PaintContext;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
 import com.android.internal.widget.remotecompose.core.WireBuffer;
 
+import java.util.Arrays;
 import java.util.List;
 
-public class PathData implements Operation {
+public class PathData implements Operation, VariableSupport {
     public static final Companion COMPANION = new Companion();
     int mInstanceId;
-    float[] mRef;
     float[] mFloatPath;
-    float[] mRetFloats;
+    float[] mOutputPath;
 
     PathData(int instanceId, float[] floatPath) {
         mInstanceId = instanceId;
         mFloatPath = floatPath;
+        mOutputPath = Arrays.copyOf(mFloatPath, mFloatPath.length);
+    }
+
+    @Override
+    public void updateVariables(RemoteContext context) {
+        for (int i = 0; i < mFloatPath.length; i++) {
+            float v = mFloatPath[i];
+            if (Utils.isVariable(v)) {
+                mOutputPath[i] = (Float.isNaN(v))
+                        ? context.getFloat(Utils.idFromNan(v)) : v;
+            } else {
+                mOutputPath[i] = v;
+            }
+        }
+    }
+
+    @Override
+    public void registerListening(RemoteContext context) {
+        for (int i = 0; i < mFloatPath.length; i++) {
+            if (Float.isNaN(mFloatPath[i])) {
+                context.listensTo(Utils.idFromNan(mFloatPath[i]), this);
+            }
+        }
     }
 
     @Override
     public void write(WireBuffer buffer) {
-        COMPANION.apply(buffer, mInstanceId, mFloatPath);
+        COMPANION.apply(buffer, mInstanceId, mOutputPath);
     }
 
     @Override
@@ -46,29 +69,35 @@
         return pathString(mFloatPath);
     }
 
-    public float[] getFloatPath(PaintContext context) {
-        float[] ret = mRetFloats; // Assume retFloats is declared elsewhere
-        if (ret == null) {
-            return mFloatPath; // Assume floatPath is declared elsewhere
-        }
-        float[] localRef = mRef; // Assume ref is of type Float[]
-        if (localRef == null) {
-            for (int i = 0; i < mFloatPath.length; i++) {
-                ret[i] = mFloatPath[i];
-            }
-        } else {
-            for (int i = 0; i < mFloatPath.length; i++) {
-                float lr = localRef[i];
-                if (Float.isNaN(lr)) {
-                    ret[i] = Utils.getActualValue(lr);
-                } else {
-                    ret[i] = mFloatPath[i];
-                }
-            }
-        }
-        return ret;
+    @Override
+    public String toString() {
+        return "PathData[" + mInstanceId + "] = " + "\"" + deepToString(" ") + "\"";
     }
 
+    /**
+     * public float[] getFloatPath(PaintContext context) {
+     * float[] ret = mRetFloats; // Assume retFloats is declared elsewhere
+     * if (ret == null) {
+     * return mFloatPath; // Assume floatPath is declared elsewhere
+     * }
+     * float[] localRef = mRef; // Assume ref is of type Float[]
+     * if (localRef == null) {
+     * for (int i = 0; i < mFloatPath.length; i++) {
+     * ret[i] = mFloatPath[i];
+     * }
+     * } else {
+     * for (int i = 0; i < mFloatPath.length; i++) {
+     * float lr = localRef[i];
+     * if (Float.isNaN(lr)) {
+     * ret[i] = Utils.getActualValue(lr);
+     * } else {
+     * ret[i] = mFloatPath[i];
+     * }
+     * }
+     * }
+     * return ret;
+     * }
+     */
     public static final int MOVE = 10;
     public static final int LINE = 11;
     public static final int QUADRATIC = 12;
@@ -155,7 +184,7 @@
                             str.append(".");
                             break;
                         default:
-                            str.append("X");
+                            str.append("[" + id + "]");
                             break;
                     }
                 } else {
@@ -170,7 +199,7 @@
 
     @Override
     public void apply(RemoteContext context) {
-        context.loadPathData(mInstanceId, mFloatPath);
+        context.loadPathData(mInstanceId, mOutputPath);
     }
 
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java
index 6d924eb..997e8dc 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java
@@ -94,7 +94,6 @@
     public static final int SCALE_CROP = 5;
     public static final int SCALE_FILL_BOUNDS = 6;
 
-
     public static final Companion COMPANION = new Companion();
 
     /**
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java
index 64c7f3e..076b28e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java
@@ -48,7 +48,7 @@
 
     @Override
     public String toString() {
-        return "ROOT_CONTENT_DESCRIPTION " + mContentDescription;
+        return "RootContentDescription " + mContentDescription;
     }
 
     @Override
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java
new file mode 100644
index 0000000..8463ac5
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2023 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.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Operation to deal with bitmap data
+ * On getting an Image during a draw call the bitmap is compressed and saved
+ * in playback the image is decompressed
+ */
+public class ShaderData implements Operation, VariableSupport {
+    int mShaderTextId; // the actual text of a shader
+    int mShaderID; // allows shaders to be referenced by number
+    HashMap<String, float[]> mUniformRawFloatMap = null;
+    HashMap<String, float[]> mUniformFloatMap = null;
+    HashMap<String, int[]> mUniformIntMap = null;
+    HashMap<String, Integer> mUniformBitmapMap = null;
+
+    public static final int MAX_IMAGE_DIMENSION = 8000;
+
+    public static final Companion COMPANION = new Companion();
+
+    public ShaderData(int shaderID,
+                      int shaderTextId,
+                      HashMap<String, float[]> floatMap,
+                      HashMap<String, int[]> intMap,
+                      HashMap<String, Integer> bitmapMap) {
+        mShaderID = shaderID;
+        mShaderTextId = shaderTextId;
+        if (floatMap != null) {
+            mUniformFloatMap = new HashMap<>();
+            mUniformRawFloatMap = new HashMap<>();
+
+            for (String name : floatMap.keySet()) {
+                mUniformRawFloatMap.put(name, floatMap.get(name));
+                mUniformFloatMap.put(name, floatMap.get(name));
+            }
+        }
+
+        if (intMap != null) {
+            mUniformIntMap = new HashMap<>();
+            for (String name : intMap.keySet()) {
+                mUniformIntMap.put(name, intMap.get(name));
+            }
+        }
+        if (bitmapMap != null) {
+            mUniformBitmapMap = new HashMap<>();
+            for (String name : bitmapMap.keySet()) {
+                mUniformBitmapMap.put(name, bitmapMap.get(name));
+            }
+        }
+
+    }
+
+    public int getShaderTextId() {
+        return mShaderTextId;
+    }
+
+    /**
+     * get names of all known floats
+     * @return
+     */
+    public String[] getUniformFloatNames() {
+        if (mUniformFloatMap == null) return new String[0];
+        return mUniformFloatMap.keySet().toArray(new String[0]);
+    }
+
+    /**
+     * Get float values associated with the name
+     * @param name
+     * @return
+     */
+    public float[] getUniformFloats(String name) {
+        return mUniformFloatMap.get(name);
+    }
+
+    /**
+     * get the name of all know uniform integers
+     * @return
+     */
+    public String[] getUniformIntegerNames() {
+        if (mUniformIntMap == null) return new String[0];
+        return mUniformIntMap.keySet().toArray(new String[0]);
+    }
+
+    /**
+     * Get Int value associated with the name
+     * @param name
+     * @return
+     */
+    public int[] getUniformInts(String name) {
+        return mUniformIntMap.get(name);
+    }
+
+    /**
+     * get list of uniform Bitmaps
+     * @return
+     */
+    public String[] getUniformBitmapNames() {
+        if (mUniformBitmapMap == null) return new String[0];
+        return mUniformBitmapMap.keySet().toArray(new String[0]);
+    }
+
+    /**
+     * Get a bitmap stored under that name
+     * @param name
+     * @return
+     */
+    public int getUniformBitmapId(String name) {
+        return mUniformBitmapMap.get(name);
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        COMPANION.apply(buffer, mShaderID, mShaderTextId,
+                mUniformFloatMap, mUniformIntMap, mUniformBitmapMap);
+    }
+
+    @Override
+    public String toString() {
+        return "SHADER DATA " + mShaderID;
+    }
+
+    @Override
+    public void updateVariables(RemoteContext context) {
+        for (String name : mUniformRawFloatMap.keySet()) {
+            float[] value = mUniformRawFloatMap.get(name);
+            float[] out = null;
+            for (int i = 0; i < value.length; i++) {
+                if (Float.isNaN(value[i])) {
+                    if (out == null) { // need to copy
+                        out = Arrays.copyOf(value, value.length);
+                    }
+                    out[i] = context.getFloat(Utils.idFromNan(value[i]));
+                }
+            }
+            mUniformFloatMap.put(name, out == null ? value : out);
+        }
+    }
+
+    @Override
+    public void registerListening(RemoteContext context) {
+        for (String name : mUniformRawFloatMap.keySet()) {
+            float[] value = mUniformRawFloatMap.get(name);
+            for (int i = 0; i < value.length; i++) {
+                if (Float.isNaN(value[i])) {
+                    context.listensTo(Utils.idFromNan(value[i]), this);
+                }
+            }
+        }
+    }
+
+    public static class Companion implements CompanionOperation {
+        private Companion() {
+        }
+
+        @Override
+        public String name() {
+            return "BitmapData";
+        }
+
+        @Override
+        public int id() {
+            return Operations.DATA_SHADER;
+        }
+
+        /**
+         * Writes out the operation to the buffer
+         * @param buffer
+         * @param shaderID
+         * @param shaderTextId
+         * @param floatMap
+         * @param intMap
+         * @param bitmapMap
+         */
+        public void apply(WireBuffer buffer, int shaderID, int shaderTextId,
+                          HashMap<String, float[]> floatMap,
+                          HashMap<String, int[]> intMap,
+                          HashMap<String, Integer> bitmapMap) {
+            buffer.start(Operations.DATA_SHADER);
+            buffer.writeInt(shaderID);
+
+            buffer.writeInt(shaderTextId);
+            int floatSize = (floatMap == null) ? 0 : floatMap.size();
+            int intSize = (intMap == null) ? 0 : intMap.size();
+            int bitmapSize = (bitmapMap == null) ? 0 : bitmapMap.size();
+            int sizes = floatSize | (intSize << 8) | (bitmapSize << 16);
+            buffer.writeInt(sizes);
+
+            if (floatSize > 0) {
+
+                for (String name : floatMap.keySet()) {
+                    buffer.writeUTF8(name);
+                    float[] values = floatMap.get(name);
+                    buffer.writeInt(values.length);
+
+                    for (int i = 0; i < values.length; i++) {
+                        buffer.writeFloat(values[i]);
+                    }
+                }
+            }
+
+            if (intSize > 0) {
+                for (String name : intMap.keySet()) {
+                    buffer.writeUTF8(name);
+                    int[] values = intMap.get(name);
+                    buffer.writeInt(values.length);
+                    for (int i = 0; i < values.length; i++) {
+                        buffer.writeInt(values[i]);
+                    }
+                }
+            }
+            if (bitmapSize > 0) {
+                for (String name : bitmapMap.keySet()) {
+                    buffer.writeUTF8(name);
+                    int value = bitmapMap.get(name);
+                    buffer.writeInt(value);
+                }
+            }
+        }
+
+        @Override
+        public void read(WireBuffer buffer, List<Operation> operations) {
+            int shaderID = buffer.readInt();
+            int shaderTextId = buffer.readInt();
+            HashMap<String, float[]> floatMap = null;
+            HashMap<String, int[]> intMap = null;
+            HashMap<String, Integer> bitmapMap = null;
+
+            int sizes = buffer.readInt();
+
+            int floatMapSize = sizes & 0xFF;
+            if (floatMapSize > 0) {
+                floatMap = new HashMap<>();
+                for (int i = 0; i < floatMapSize; i++) {
+                    String name = buffer.readUTF8();
+                    int len = buffer.readInt();
+                    float[] val = new float[len];
+
+                    for (int j = 0; j < len; j++) {
+                        val[j] = buffer.readFloat();
+                    }
+
+                    floatMap.put(name, val);
+                }
+            }
+            int intMapSize = (sizes >> 8) & 0xFF;
+
+            if (intMapSize > 0) {
+
+                intMap = new HashMap<>();
+                for (int i = 0; i < intMapSize; i++) {
+                    String name = buffer.readUTF8();
+                    int len = buffer.readInt();
+                    int[] val = new int[len];
+                    for (int j = 0; j < len; j++) {
+                        val[j] = buffer.readInt();
+                    }
+                    intMap.put(name, val);
+                }
+            }
+            int bitmapMapSize = (sizes >> 16) & 0xFF;
+
+            if (bitmapMapSize > 0) {
+                bitmapMap = new HashMap<>();
+                for (int i = 0; i < bitmapMapSize; i++) {
+                    String name = buffer.readUTF8();
+                    int val = buffer.readInt();
+                    bitmapMap.put(name, val);
+                }
+            }
+            operations.add(new ShaderData(shaderID, shaderTextId,
+                    floatMap, intMap, bitmapMap));
+        }
+    }
+
+    @Override
+    public void apply(RemoteContext context) {
+        context.loadShader(mShaderID, this);
+    }
+
+    @Override
+    public String deepToString(String indent) {
+        return indent + toString();
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java
index 5b622ae..ed13449 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java
@@ -44,11 +44,13 @@
 
     @Override
     public String toString() {
-        return "TEXT DATA " + mTextId + "\"" + mText + "\"";
+        return "TextData[" + mTextId + "] = \""
+                + Utils.trimString(mText, 10) + "\"";
     }
 
     public static class Companion implements CompanionOperation {
-        private Companion() {}
+        private Companion() {
+        }
 
         @Override
         public String name() {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java
new file mode 100644
index 0000000..65a39a1e
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2023 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.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringUtils;
+
+import java.util.List;
+
+/**
+ * Operation convert floats to text
+ * This command is structured [command][textID][before,after][flags]
+ * before and after define number of digits before and after the decimal point
+ */
+public class TextFromFloat implements Operation, VariableSupport {
+    public int mTextId;
+    public float mValue;
+    public float mOutValue;
+    public short mDigitsBefore;
+    public short mDigitsAfter;
+    public int mFlags;
+    public static final Companion COMPANION = new Companion();
+    public static final int MAX_STRING_SIZE = 4000;
+    char mPre = ' ';
+    char mAfter = ' ';
+    // Theses flags define what how to/if  fill the space
+    public static final int PAD_AFTER_SPACE = 0; // pad past point with space
+    public static final int PAD_AFTER_NONE = 1; // do not pad past last digit
+    public static final int PAD_AFTER_ZERO = 3; // pad with 0 past last digit
+    public static final int PAD_PRE_SPACE = 0;  // pad before number with spaces
+    public static final int PAD_PRE_NONE = 4;   // pad before number with 0s
+    public static final int PAD_PRE_ZERO = 12;  // do not pad before number
+
+    public TextFromFloat(int textId, float value, short digitsBefore,
+                         short digitsAfter, int flags) {
+        this.mTextId = textId;
+        this.mValue = value;
+        this.mDigitsAfter = digitsAfter;
+        this.mDigitsBefore = digitsBefore;
+        this.mFlags = flags;
+        mOutValue = mValue;
+        switch (mFlags & 3) {
+            case PAD_AFTER_SPACE:
+                mAfter = ' ';
+                break;
+            case PAD_AFTER_NONE:
+                mAfter = 0;
+                break;
+            case PAD_AFTER_ZERO:
+                mAfter = '0';
+                break;
+        }
+        switch (mFlags & 12) {
+            case PAD_PRE_SPACE:
+                mPre = ' ';
+                break;
+            case PAD_PRE_NONE:
+                mPre = 0;
+                break;
+            case PAD_PRE_ZERO:
+                mPre = '0';
+                break;
+        }
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        COMPANION.apply(buffer, mTextId, mValue, mDigitsAfter, mDigitsBefore, mFlags);
+    }
+
+    @Override
+    public String toString() {
+        return "TextFromFloat[" + mTextId + "] = "
+                + Utils.floatToString(mValue) + " " + mDigitsBefore
+                + "." + mDigitsAfter + " " + mFlags;
+    }
+
+
+    @Override
+    public void updateVariables(RemoteContext context) {
+        if (Float.isNaN(mValue)) {
+            mOutValue = context.getFloat(Utils.idFromNan(mValue));
+        }
+
+    }
+
+
+    @Override
+    public void registerListening(RemoteContext context) {
+        if (Float.isNaN(mValue)) {
+            context.listensTo(Utils.idFromNan(mValue), this);
+        }
+    }
+
+    public static class Companion implements CompanionOperation {
+        private Companion() {
+        }
+
+        @Override
+        public String name() {
+            return "TextData";
+        }
+
+        @Override
+        public int id() {
+            return Operations.TEXT_FROM_FLOAT;
+        }
+
+        /**
+         * Writes out the operation to the buffer
+         * @param buffer
+         * @param textId
+         * @param value
+         * @param digitsBefore
+         * @param digitsAfter
+         * @param flags
+         */
+        public void apply(WireBuffer buffer, int textId,
+                          float value, short digitsBefore,
+                          short digitsAfter, int flags) {
+            buffer.start(Operations.TEXT_FROM_FLOAT);
+            buffer.writeInt(textId);
+            buffer.writeFloat(value);
+            buffer.writeInt((digitsBefore << 16) | digitsAfter);
+            buffer.writeInt(flags);
+
+        }
+
+        @Override
+        public void read(WireBuffer buffer, List<Operation> operations) {
+            int textId = buffer.readInt();
+            float value = buffer.readFloat();
+            int tmp = buffer.readInt();
+            short post = (short) (tmp & 0xFFFF);
+            short pre = (short) ((tmp >> 16) & 0xFFFF);
+
+            int flags = buffer.readInt();
+            operations.add(new TextFromFloat(textId, value, pre, post, flags));
+        }
+    }
+
+    @Override
+    public void apply(RemoteContext context) {
+        float v = mOutValue;
+        String s = StringUtils.floatToString(v, mDigitsBefore,
+                mDigitsAfter, mPre, mAfter);
+        context.loadText(mTextId, s);
+    }
+
+    @Override
+    public String deepToString(String indent) {
+        return indent + toString();
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java
new file mode 100644
index 0000000..a0fc854
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2023 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.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+/**
+ * Operation to deal with Text data
+ */
+public class TextMerge implements Operation {
+    public int mTextId;
+    public int mSrcId1;
+    public int mSrcId2;
+    public static final Companion COMPANION = new Companion();
+    public static final int MAX_STRING_SIZE = 4000;
+
+    public TextMerge(int textId, int srcId1, int srcId2) {
+        this.mTextId = textId;
+        this.mSrcId1 = srcId1;
+        this.mSrcId2 = srcId2;
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        COMPANION.apply(buffer, mTextId, mSrcId1, mSrcId2);
+    }
+
+    @Override
+    public String toString() {
+        return "TextMerge[" + mTextId + "] = [" + mSrcId1 + " ] + [ " + mSrcId2 + "]";
+    }
+
+    public static class Companion implements CompanionOperation {
+        private Companion() {
+        }
+
+        @Override
+        public String name() {
+            return "TextData";
+        }
+
+        @Override
+        public int id() {
+            return Operations.TEXT_MERGE;
+        }
+
+        /**
+         * Writes out the operation to the buffer
+         * @param buffer
+         * @param textId
+         * @param srcId1
+         * @param srcId2
+         */
+        public void apply(WireBuffer buffer, int textId, int srcId1, int srcId2) {
+            buffer.start(Operations.TEXT_MERGE);
+            buffer.writeInt(textId);
+            buffer.writeInt(srcId1);
+            buffer.writeInt(srcId2);
+        }
+
+        @Override
+        public void read(WireBuffer buffer, List<Operation> operations) {
+            int textId = buffer.readInt();
+            int srcId1 = buffer.readInt();
+            int srcId2 = buffer.readInt();
+
+            operations.add(new TextMerge(textId, srcId1, srcId2));
+        }
+    }
+
+    @Override
+    public void apply(RemoteContext context) {
+        String str1 = context.getText(mSrcId1);
+        String str2 = context.getText(mSrcId2);
+        context.loadText(mTextId, str1 + str2);
+    }
+
+    @Override
+    public String deepToString(String indent) {
+        return indent + toString();
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java
index 00e2f20..fdc6860 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java
@@ -15,13 +15,16 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+/**
+ * Utilities to be used across all core operations
+ */
 public class Utils {
     public static float asNan(int v) {
         return Float.intBitsToFloat(v | -0x800000);
     }
 
     public static int idFromNan(float value) {
-        int b =  Float.floatToRawIntBits(value);
+        int b = Float.floatToRawIntBits(value);
         return b & 0xFFFFF;
     }
 
@@ -29,13 +32,194 @@
         return 0;
     }
 
-    String getFloatString(float value) {
-        if (Float.isNaN(value)) {
-            int id = idFromNan(value);
-            if (id > 0) {
-                return "NaN(" + id + ")";
-            }
+    /**
+     * trim a string to n characters if needing to trim
+     * end in "..."
+     *
+     * @param str
+     * @param n
+     * @return
+     */
+    static String trimString(String str, int n) {
+        if (str.length() > n) {
+            str = str.substring(0, n - 3) + "...";
         }
-        return "" + value;
+        return str;
     }
+
+    /**
+     * print the id and the value of a float
+     * @param idvalue
+     * @param value
+     * @return
+     */
+    public static String floatToString(float idvalue, float value) {
+        if (Float.isNaN(idvalue)) {
+            return "[" + idFromNan(idvalue) + "]" + floatToString(value);
+        }
+        return floatToString(value);
+    }
+
+    /**
+     * Convert float to string but render nan id in brackets [n]
+     * @param value
+     * @return
+     */
+    public static String floatToString(float value) {
+        if (Float.isNaN(value)) {
+            return "[" + idFromNan(value) + "]";
+        }
+        return Float.toString(value);
+    }
+
+    /**
+     * Debugging util to print a message and include the file/line it came from
+     * @param str
+     */
+    public static void log(String str) {
+        StackTraceElement s = new Throwable().getStackTrace()[1];
+        System.out.println("(" + s.getFileName() + ":" + s.getLineNumber() + ")." + str);
+    }
+
+    /**
+     * Debugging util to print the stack
+     * @param str
+     * @param n
+     */
+    public static void logStack(String str, int n) {
+        StackTraceElement[] st = new Throwable().getStackTrace();
+        for (int i = 1; i < n + 1; i++) {
+            StackTraceElement s = st[i];
+            String space = new String(new char[i]).replace('\0', ' ');
+            System.out.println(space + "(" + s.getFileName()
+                    + ":" + s.getLineNumber() + ")." + str);
+        }
+    }
+
+    /**
+     * Is a variable Allowed int calculation and references.
+     *
+     * @param v
+     * @return
+     */
+    public static boolean isVariable(float v) {
+        if (Float.isNaN(v)) {
+            int id = idFromNan(v);
+            return id > 40 || id < 10;
+        }
+        return false;
+    }
+
+    /**
+     * print a color in the familiar 0xAARRGGBB pattern
+     *
+     * @param color
+     * @return
+     */
+    public static String colorInt(int color) {
+        String str = "000000000000" + Integer.toHexString(color);
+        return "0x" + str.substring(str.length() - 8);
+    }
+
+    /**
+     * Interpolate two colors.
+     * gamma corrected colors are interpolated in the form c1 * (1-t) + c2 * t
+     *
+     * @param c1
+     * @param c2
+     * @param t
+     * @return
+     */
+    public static int interpolateColor(int c1, int c2, float t) {
+        if (Float.isNaN(t) || t == 0.0f) {
+            return c1;
+        } else if (t == 1.0f) {
+            return c2;
+        }
+        int a = 0xFF & (c1 >> 24);
+        int r = 0xFF & (c1 >> 16);
+        int g = 0xFF & (c1 >> 8);
+        int b = 0xFF & c1;
+        float f_r = (float) Math.pow(r / 255.0f, 2.2);
+        float f_g = (float) Math.pow(g / 255.0f, 2.2);
+        float f_b = (float) Math.pow(b / 255.0f, 2.2);
+        float c1fr = f_r;
+        float c1fg = f_g;
+        float c1fb = f_b;
+        float c1fa = a / 255f;
+
+        a = 0xFF & (c2 >> 24);
+        r = 0xFF & (c2 >> 16);
+        g = 0xFF & (c2 >> 8);
+        b = 0xFF & c2;
+        f_r = (float) Math.pow(r / 255.0f, 2.2);
+        f_g = (float) Math.pow(g / 255.0f, 2.2);
+        f_b = (float) Math.pow(b / 255.0f, 2.2);
+        float c2fr = f_r;
+        float c2fg = f_g;
+        float c2fb = f_b;
+        float c2fa = a / 255f;
+        f_r = c1fr + t * (c2fr - c1fr);
+        f_g = c1fg + t * (c2fg - c1fg);
+        f_b = c1fb + t * (c2fb - c1fb);
+        float f_a = c1fa + t * (c2fa - c1fa);
+
+        int outr = clamp((int) ((float) Math.pow(f_r, 1.0 / 2.2) * 255.0f));
+        int outg = clamp((int) ((float) Math.pow(f_g, 1.0 / 2.2) * 255.0f));
+        int outb = clamp((int) ((float) Math.pow(f_b, 1.0 / 2.2) * 255.0f));
+        int outa = clamp((int) (f_a * 255.0f));
+
+
+        return (outa << 24 | outr << 16 | outg << 8 | outb);
+    }
+
+    /**
+     * Efficient clamping function
+     *
+     * @param c
+     * @return number between 0 and 255
+     */
+    public static int clamp(int c) {
+        int n = 255;
+        c &= ~(c >> 31);
+        c -= n;
+        c &= (c >> 31);
+        c += n;
+        return c;
+    }
+
+    /**
+     * convert hue saturation and value to RGB
+     *
+     * @param hue        0..1
+     * @param saturation 0..1 0=on the gray scale
+     * @param value      0..1 0=black
+     * @return
+     */
+    public static int hsvToRgb(float hue, float saturation, float value) {
+        int h = (int) (hue * 6);
+        float f = hue * 6 - h;
+        int p = (int) (0.5f + 255 * value * (1 - saturation));
+        int q = (int) (0.5f + 255 * value * (1 - f * saturation));
+        int t = (int) (0.5f + 255 * value * (1 - (1 - f) * saturation));
+        int v = (int) (0.5f + 255 * value);
+        switch (h) {
+            case 0:
+                return 0XFF000000 | (v << 16) + (t << 8) + p;
+            case 1:
+                return 0XFF000000 | (q << 16) + (v << 8) + p;
+            case 2:
+                return 0XFF000000 | (p << 16) + (v << 8) + t;
+            case 3:
+                return 0XFF000000 | (p << 16) + (q << 8) + v;
+            case 4:
+                return 0XFF000000 | (t << 16) + (p << 8) + v;
+            case 5:
+                return 0XFF000000 | (v << 16) + (p << 8) + q;
+
+        }
+        return 0;
+    }
+
+
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java
index 8abb0bf..a7d0ac6 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java
@@ -15,43 +15,60 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.paint;
 
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
 import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.operations.Utils;
 
 import java.util.Arrays;
 
+/**
+ * Paint Bundle represents a delta of changes to a paint object
+ */
 public class PaintBundle {
     int[] mArray = new int[200];
+    int[] mOutArray = null;
     int mPos = 0;
 
-    public void applyPaintChange(PaintChanges p) {
+    /**
+     * Apply changes to a PaintChanges interface
+     * @param paintContext
+     * @param p
+     */
+    public void applyPaintChange(PaintContext paintContext, PaintChanges p) {
         int i = 0;
         int mask = 0;
+        if (mOutArray == null) {
+            mOutArray = mArray;
+        }
         while (i < mPos) {
-            int cmd = mArray[i++];
+            int cmd = mOutArray[i++];
             mask = mask | (1 << (cmd - 1));
             switch (cmd & 0xFFFF) {
                 case TEXT_SIZE: {
-                    p.setTextSize(Float.intBitsToFloat(mArray[i++]));
+                    p.setTextSize(Float.intBitsToFloat(mOutArray[i++]));
                     break;
                 }
                 case TYPEFACE:
                     int style = (cmd >> 16);
                     int weight = style & 0x3ff;
                     boolean italic = (style >> 10) > 0;
-                    int font_type = mArray[i++];
+                    int font_type = mOutArray[i++];
 
                     p.setTypeFace(font_type, weight, italic);
                     break;
+                case COLOR_ID: // mOutArray should have already decoded it
                 case COLOR: {
-                    p.setColor(mArray[i++]);
+                    p.setColor(mOutArray[i++]);
                     break;
                 }
                 case STROKE_WIDTH: {
-                    p.setStrokeWidth(Float.intBitsToFloat(mArray[i++]));
+                    p.setStrokeWidth(Float.intBitsToFloat(mOutArray[i++]));
                     break;
                 }
                 case STROKE_MITER: {
-                    p.setStrokeMiter(Float.intBitsToFloat(mArray[i++]));
+                    p.setStrokeMiter(Float.intBitsToFloat(mOutArray[i++]));
                     break;
                 }
                 case STROKE_CAP: {
@@ -63,6 +80,7 @@
                     break;
                 }
                 case SHADER: {
+                    p.setShader(mOutArray[i++]);
                     break;
                 }
                 case STROKE_JOIN: {
@@ -81,17 +99,16 @@
                     p.setFilterBitmap(!((cmd >> 16) == 0));
                     break;
                 }
-
                 case GRADIENT: {
-                    i = callSetGradient(cmd, mArray, i, p);
+                    i = callSetGradient(cmd, mOutArray, i, p);
                     break;
                 }
                 case COLOR_FILTER: {
-                    p.setColorFilter(mArray[i++], cmd >> 16);
+                    p.setColorFilter(mOutArray[i++], cmd >> 16);
                     break;
                 }
                 case ALPHA: {
-                    p.setAlpha(Float.intBitsToFloat(mArray[i++]));
+                    p.setAlpha(Float.intBitsToFloat(mOutArray[i++]));
                     break;
                 }
             }
@@ -106,7 +123,6 @@
         switch (id) {
             case TEXT_SIZE:
                 return "TEXT_SIZE";
-
             case COLOR:
                 return "COLOR";
             case STROKE_WIDTH:
@@ -133,7 +149,6 @@
                 return "ALPHA";
             case COLOR_FILTER:
                 return "COLOR_FILTER";
-
         }
         return "????" + id + "????";
     }
@@ -154,6 +169,14 @@
         return str + "]";
     }
 
+    private static String asFloatStr(int value) {
+        float fValue = Float.intBitsToFloat(value);
+        if (Float.isNaN(fValue)) {
+            return "[" + Utils.idFromNan(fValue) + "]";
+        }
+        return Float.toString(fValue);
+    }
+
     @Override
     public String toString() {
         StringBuilder ret = new StringBuilder("\n");
@@ -164,7 +187,8 @@
             switch (type) {
 
                 case TEXT_SIZE: {
-                    ret.append("    TextSize(" + Float.intBitsToFloat(mArray[i++]));
+                    ret.append("    TextSize("
+                            + asFloatStr(mArray[i++]));
                 }
 
                 break;
@@ -181,14 +205,18 @@
                     ret.append("    Color(" + colorInt(mArray[i++]));
                 }
                 break;
+                case COLOR_ID: {
+                    ret.append("    ColorId([" + mArray[i++] + "]");
+                }
+                break;
                 case STROKE_WIDTH: {
                     ret.append("    StrokeWidth("
-                            + (Float.intBitsToFloat(mArray[i++])));
+                            + (asFloatStr(mArray[i++])));
                 }
                 break;
                 case STROKE_MITER: {
                     ret.append("    StrokeMiter("
-                            + (Float.intBitsToFloat(mArray[i++])));
+                            + (asFloatStr(mArray[i++])));
                 }
                 break;
                 case STROKE_CAP: {
@@ -207,11 +235,12 @@
                 }
                 break;
                 case SHADER: {
+                    ret.append("    Shader(" + mArray[i++]);
                 }
                 break;
                 case ALPHA: {
                     ret.append("    Alpha("
-                            + (Float.intBitsToFloat(mArray[i++])));
+                            + (asFloatStr(mArray[i++])));
                 }
                 break;
                 case IMAGE_FILTER_QUALITY: {
@@ -244,7 +273,6 @@
         return ret.toString();
     }
 
-
     int callPrintGradient(int cmd, int[] array, int i, StringBuilder p) {
         int ret = i;
         int type = (cmd >> 16);
@@ -258,26 +286,25 @@
                     colors = new int[len];
                     for (int j = 0; j < colors.length; j++) {
                         colors[j] = array[ret++];
-
                     }
                 }
                 len = array[ret++];
-                float[] stops = null;
+                String[] stops = null;
                 if (len > 0) {
-                    stops = new float[len];
+                    stops = new String[len];
                     for (int j = 0; j < stops.length; j++) {
-                        stops[j] = Float.intBitsToFloat(array[ret++]);
+                        stops[j] = asFloatStr(array[ret++]);
                     }
                 }
 
                 p.append("      colors = " + colorInt(colors) + ",\n");
                 p.append("      stops = " + Arrays.toString(stops) + ",\n");
                 p.append("      start = ");
-                p.append("[" + Float.intBitsToFloat(array[ret++]));
-                p.append(", " + Float.intBitsToFloat(array[ret++]) + "],\n");
+                p.append("[" + asFloatStr(array[ret++]));
+                p.append(", " + asFloatStr(array[ret++]) + "],\n");
                 p.append("      end = ");
-                p.append("[" + Float.intBitsToFloat(array[ret++]));
-                p.append(", " + Float.intBitsToFloat(array[ret++]) + "],\n");
+                p.append("[" + asFloatStr(array[ret++]));
+                p.append(", " + asFloatStr(array[ret++]) + "],\n");
                 int tileMode = array[ret++];
                 p.append("      tileMode = " + tileMode + "\n    ");
             }
@@ -295,21 +322,21 @@
                     }
                 }
                 len = array[ret++];
-                float[] stops = null;
+                String[] stops = null;
                 if (len > 0) {
-                    stops = new float[len];
+                    stops = new String[len];
                     for (int j = 0; j < stops.length; j++) {
-                        stops[j] = Float.intBitsToFloat(array[ret++]);
+                        stops[j] = asFloatStr(array[ret++]);
                     }
                 }
 
                 p.append("      colors = " + colorInt(colors) + ",\n");
                 p.append("      stops = " + Arrays.toString(stops) + ",\n");
                 p.append("      center = ");
-                p.append("[" + Float.intBitsToFloat(array[ret++]));
-                p.append(", " + Float.intBitsToFloat(array[ret++]) + "],\n");
+                p.append("[" + asFloatStr(array[ret++]));
+                p.append(", " + asFloatStr(array[ret++]) + "],\n");
                 p.append("      radius =");
-                p.append(" " + Float.intBitsToFloat(array[ret++]) + ",\n");
+                p.append(" " + asFloatStr(array[ret++]) + ",\n");
                 int tileMode = array[ret++];
                 p.append("      tileMode = " + tileMode + "\n    ");
             }
@@ -327,20 +354,19 @@
                     }
                 }
                 len = array[ret++];
-                float[] stops = null;
+                String[] stops = null;
                 if (len > 0) {
-                    stops = new float[len];
+                    stops = new String[len];
                     for (int j = 0; j < stops.length; j++) {
-                        stops[j] = Float.intBitsToFloat(array[ret++]);
+                        stops[j] = asFloatStr(array[ret++]);
                     }
                 }
-
                 p.append("      colors = " + colorInt(colors) + ",\n");
                 p.append("      stops = " + Arrays.toString(stops) + ",\n");
                 p.append("      center = ");
-                p.append("[" + Float.intBitsToFloat(array[ret++]));
-                p.append(", " + Float.intBitsToFloat(array[ret++]) + "],\n    ");
-
+                p.append("[" + asFloatStr(array[ret++]));
+                p.append(", "
+                        + asFloatStr(array[ret++]) + "],\n    ");
             }
             break;
             default: {
@@ -376,7 +402,6 @@
             return ret;
         }
 
-
         switch (gradientType) {
 
             case LINEAR_GRADIENT: {
@@ -433,7 +458,7 @@
     public static final int COLOR = 4;  // int
     public static final int STROKE_WIDTH = 5; // float
     public static final int STROKE_MITER = 6;
-    public static final int STROKE_CAP = 7; // int
+    public static final int STROKE_CAP = 7; //  int
     public static final int STYLE = 8; // int
     public static final int SHADER = 9; // int
     public static final int IMAGE_FILTER_QUALITY = 10; // int
@@ -445,7 +470,7 @@
     public static final int TYPEFACE = 16;
     public static final int FILTER_BITMAP = 17;
     public static final int BLEND_MODE = 18;
-
+    public static final int COLOR_ID = 19;  // int
 
     public static final int BLEND_MODE_CLEAR = 0;
     public static final int BLEND_MODE_SRC = 1;
@@ -634,8 +659,8 @@
 
     /**
      * @param fontType 0 = default 1 = sans serif 2 = serif 3 = monospace
-     * @param weight    100-1000
-     * @param italic    tur
+     * @param weight   100-1000
+     * @param italic   tur
      */
     public void setTextStyle(int fontType, int weight, boolean italic) {
         int style = (weight & 0x3FF) | (italic ? 2048 : 0);  // pack the weight and italic
@@ -658,6 +683,10 @@
         mPos++;
     }
 
+    /**
+     * Set the Color based on Color
+     * @param color
+     */
     public void setColor(int color) {
         mArray[mPos] = COLOR;
         mPos++;
@@ -666,6 +695,18 @@
     }
 
     /**
+     * Set the Color based on ID
+     * @param color
+     */
+    public void setColorId(int color) {
+        mArray[mPos] = COLOR_ID;
+        mPos++;
+        mArray[mPos] = color;
+        mPos++;
+    }
+
+
+    /**
      * Set the paint's Cap.
      *
      * @param cap set the paint's line cap style, used whenever the paint's
@@ -676,16 +717,29 @@
         mPos++;
     }
 
+    /**
+     * Set the style STROKE and/or FILL
+     * @param style
+     */
     public void setStyle(int style) {
         mArray[mPos] = STYLE | (style << 16);
         mPos++;
     }
 
-    public void setShader(int shader, String shaderString) {
-        mArray[mPos] = SHADER | (shader << 16);
+    /**
+     * Set the shader id to use
+     * @param shaderId
+     */
+    public void setShader(int shaderId) {
+        mArray[mPos] = SHADER;
+        mPos++;
+        mArray[mPos] = shaderId;
         mPos++;
     }
 
+    /**
+     * Set the Alpha value
+     */
     public void setAlpha(float alpha) {
         mArray[mPos] = ALPHA;
         mPos++;
@@ -729,7 +783,6 @@
      * destination pixels
      * (content of the render target).
      *
-     *
      * @param blendmode The blend mode to be installed in the paint
      */
     public void setBlendMode(int blendmode) {
@@ -825,5 +878,216 @@
         return "null";
     }
 
-}
+    /**
+     * Check all the floats for Nan(id) floats and call listenTo
+     * @param context
+     * @param support
+     */
+    public void registerVars(RemoteContext context, VariableSupport support) {
+        int i = 0;
+        while (i < mPos) {
+            int cmd = mArray[i++];
+            int type = cmd & 0xFFFF;
+            switch (type) {
+                case STROKE_MITER:
+                case STROKE_WIDTH:
+                case ALPHA:
+                case TEXT_SIZE:
+                    float v = Float.intBitsToFloat(mArray[i++]);
+                    if (Float.isNaN(v)) {
+                        context.listensTo(Utils.idFromNan(v), support);
+                    }
+                    break;
+                case COLOR_ID:
+                    context.listensTo(mArray[i++], support);
+                    break;
+                case COLOR:
 
+                case TYPEFACE:
+                case SHADER:
+                case COLOR_FILTER:
+                    i++;
+                    break;
+                case STROKE_JOIN:
+                case FILTER_BITMAP:
+                case STROKE_CAP:
+                case STYLE:
+                case IMAGE_FILTER_QUALITY:
+                case BLEND_MODE:
+                case ANTI_ALIAS:
+                    break;
+
+                case GRADIENT: {
+                    // TODO gradients should be handled correctly
+                    i = callPrintGradient(cmd, mArray, i, new StringBuilder());
+                }
+            }
+        }
+    }
+
+    /**
+     * Update variables if any are float ids
+     * @param context
+     */
+    public void updateVariables(RemoteContext context) {
+        if (mOutArray == null) {
+            mOutArray = Arrays.copyOf(mArray, mArray.length);
+        } else {
+            System.arraycopy(mArray, 0, mOutArray, 0, mArray.length);
+        }
+        int i = 0;
+        while (i < mPos) {
+            int cmd = mArray[i++];
+            int type = cmd & 0xFFFF;
+            switch (type) {
+                case STROKE_MITER:
+                case STROKE_WIDTH:
+                case ALPHA:
+                case TEXT_SIZE:
+                    mOutArray[i] = fixFloatVar(mArray[i], context);
+                    i++;
+                    break;
+                case COLOR_ID:
+                    mOutArray[i] = fixColor(mArray[i], context);
+                    i++;
+                    break;
+                case COLOR:
+                case TYPEFACE:
+                case SHADER:
+                case COLOR_FILTER:
+                    i++;
+                    break;
+                case STROKE_JOIN:
+                case FILTER_BITMAP:
+                case STROKE_CAP:
+                case STYLE:
+                case IMAGE_FILTER_QUALITY:
+                case BLEND_MODE:
+                case ANTI_ALIAS:
+                    break;
+
+                case GRADIENT: {
+                    // TODO gradients should be handled correctly
+                    i = updateFloatsInGradient(cmd, mOutArray, mArray, i, context);
+                }
+            }
+        }
+    }
+
+    private int fixFloatVar(int val, RemoteContext context) {
+        float v = Float.intBitsToFloat(val);
+        if (Float.isNaN(v)) {
+            int id = Utils.idFromNan(v);
+            return Float.floatToRawIntBits(context.getFloat(id));
+        }
+        return val;
+    }
+
+    private int fixColor(int colorId, RemoteContext context) {
+        int n = context.getColor(colorId);
+        return n;
+    }
+
+    int updateFloatsInGradient(int cmd, int[] out, int[] array,
+                               int i,
+                               RemoteContext context) {
+        int ret = i;
+        int type = (cmd >> 16);
+        switch (type) {
+            case 0: {
+                int len = array[ret++];
+                if (len > 0) {
+                    for (int j = 0; j < len; j++) {
+                        ret++;
+                    }
+                }
+                len = array[ret++];
+
+                if (len > 0) {
+                    for (int j = 0; j < len; j++) {
+                        out[ret] = fixFloatVar(array[ret], context);
+                        ret++;
+                    }
+                }
+
+                out[ret] = fixFloatVar(array[ret], context);
+                ret++;
+                out[ret] = fixFloatVar(array[ret], context);
+                ret++;
+
+                //      end
+                out[ret] = fixFloatVar(array[ret], context);
+                ret++;
+                out[ret] = fixFloatVar(array[ret], context);
+                ret++;
+                ret++; // tileMode
+            }
+
+            break;
+            case 1: {
+                //   RadialGradient
+                int len = array[ret++];
+                if (len > 0) {
+                    for (int j = 0; j < len; j++) {
+                        ret++;
+                    }
+                }
+                len = array[ret++];
+                if (len > 0) {
+                    for (int j = 0; j < len; j++) {
+                        out[ret] = fixFloatVar(array[ret], context);
+                        ret++;
+                    }
+                }
+
+
+                //    center
+                out[ret] = fixFloatVar(array[ret], context);
+                ret++;
+                out[ret] = fixFloatVar(array[ret], context);
+                ret++;
+                //     radius
+                out[ret] = fixFloatVar(array[ret], context);
+                ret++;
+                ret++; // tileMode
+
+            }
+
+            break;
+            case 2: {
+                //   SweepGradient
+                int len = array[ret++];
+                int[] colors = null;
+                if (len > 0) {
+                    colors = new int[len];
+                    for (int j = 0; j < colors.length; j++) {
+                        colors[j] = array[ret++];
+
+                    }
+                }
+                len = array[ret++];
+                float[] stops = null;
+                if (len > 0) {
+                    stops = new float[len];
+                    for (int j = 0; j < stops.length; j++) {
+                        out[ret] = fixFloatVar(array[ret], context);
+                        ret++;
+                    }
+                }
+
+                //      center
+                out[ret] = fixFloatVar(array[ret], context);
+                ret++;
+                out[ret] = fixFloatVar(array[ret], context);
+                ret++;
+            }
+            break;
+            default: {
+                System.err.println("gradient type unknown");
+            }
+        }
+
+        return ret;
+    }
+
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintChangeAdapter.java b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintChangeAdapter.java
index 994bf6d..28fe63a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintChangeAdapter.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintChangeAdapter.java
@@ -27,7 +27,6 @@
 
     }
 
-
     @Override
     public void setStrokeWidth(float width) {
 
@@ -49,7 +48,7 @@
     }
 
     @Override
-    public void setShader(int shader, String shaderString) {
+    public void setShader(int shader) {
 
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintChanges.java b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintChanges.java
index 87e58ac..d5dc388 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintChanges.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintChanges.java
@@ -15,9 +15,14 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.paint;
 
+/**
+ * Interface to a paint object
+ * For more details see Android Paint
+ */
 public interface PaintChanges {
 
-
+    // MASK to be set/cleared
+    int CLEAR_TEXT_SIZE = 1 << (PaintBundle.TEXT_SIZE - 1);
     int CLEAR_TEXT_STYLE = 1 << (PaintBundle.TYPEFACE - 1);
     int CLEAR_COLOR = 1 << (PaintBundle.COLOR - 1);
     int CLEAR_STROKE_WIDTH = 1 << (PaintBundle.STROKE_WIDTH - 1);
@@ -32,21 +37,101 @@
     int CLEAR_COLOR_FILTER = 1 << (PaintBundle.COLOR_FILTER - 1);
     int VALID_BITS = 0x1FFF; // only the first 13 bit are valid now
 
-
+    /**
+     * Set the size of text
+     * @param size
+     */
     void setTextSize(float size);
+
+    /**
+     * Set the width of lines
+     * @param width
+     */
     void setStrokeWidth(float width);
+
+    /**
+     * Set the color to use
+     * @param color
+     */
     void setColor(int color);
+
+    /**
+     * Set the Stroke Cap
+     * @param cap
+     */
     void setStrokeCap(int cap);
+
+    /**
+     * Set the Stroke style FILL and/or STROKE
+     * @param style
+     */
     void setStyle(int style);
-    void setShader(int shader, String shaderString);
+
+    /**
+     * Set the id of the shader to use
+     * @param shader
+     */
+    void setShader(int shader);
+
+    /**
+     * Set the way image is interpolated
+     * @param quality
+     */
     void setImageFilterQuality(int quality);
+
+    /**
+     * Set the alpha to draw under
+     * @param a
+     */
     void setAlpha(float a);
+
+    /**
+     * Set the Stroke Miter
+     * @param miter
+     */
     void setStrokeMiter(float miter);
+
+    /**
+     * Set the Stroke Join
+     * @param join
+     */
     void setStrokeJoin(int join);
+
+    /**
+     * Should bitmaps be interpolated
+     * @param filter
+     */
     void setFilterBitmap(boolean filter);
+
+    /**
+     * Set the blend mode can be porterduff + others
+     * @param mode
+     */
     void setBlendMode(int mode);
+
+    /**
+     * Set the AntiAlias. Typically true
+     * Set to off when you need pixilated look (e.g. QR codes)
+     * @param aa
+     */
     void setAntiAlias(boolean aa);
+
+    /**
+     * Clear some sub set of the settings
+     * @param mask
+     */
     void clear(long mask);
+
+    /**
+     * Set a linear gradient fill
+     * @param colorsArray
+     * @param stopsArray
+     * @param startX
+     * @param startY
+     * @param endX
+     * @param endY
+     * @param tileMode
+     */
     void setLinearGradient(
             int[] colorsArray,
             float[] stopsArray,
@@ -57,6 +142,15 @@
             int tileMode
     );
 
+    /**
+     * Set a radial gradient fill
+     * @param colorsArray
+     * @param stopsArray
+     * @param centerX
+     * @param centerY
+     * @param radius
+     * @param tileMode
+     */
     void setRadialGradient(
             int[] colorsArray,
             float[] stopsArray,
@@ -66,6 +160,13 @@
             int tileMode
     );
 
+    /**
+     * Set a sweep gradient fill
+     * @param colorsArray
+     * @param stopsArray
+     * @param centerX
+     * @param centerY
+     */
     void setSweepGradient(
             int[] colorsArray,
             float[] stopsArray,
@@ -73,9 +174,19 @@
             float centerY
     );
 
-
+    /**
+     * Set Color filter mod
+     * @param color
+     * @param mode
+     */
     void setColorFilter(int color, int mode);
 
+    /**
+     * Set TypeFace 0,1,2
+     * TODO above should point to a string to be decoded
+     * @param fontType
+     * @param weight
+     * @param italic
+     */
     void setTypeFace(int fontType, int weight, boolean italic);
-}
-
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java
new file mode 100644
index 0000000..616048d
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java
@@ -0,0 +1,452 @@
+/*
+ * Copyright (C) 2023 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.internal.widget.remotecompose.core.operations.utilities;
+
+/**
+ * high performance floating point expression evaluator used in animation
+ */
+public class AnimatedFloatExpression {
+    static IntMap<String> sNames = new IntMap<>();
+    public static final int OFFSET = 0x100;
+    public static final float ADD = asNan(OFFSET + 1);
+    public static final float SUB = asNan(OFFSET + 2);
+    public static final float MUL = asNan(OFFSET + 3);
+    public static final float DIV = asNan(OFFSET + 4);
+    public static final float MOD = asNan(OFFSET + 5);
+    public static final float MIN = asNan(OFFSET + 6);
+    public static final float MAX = asNan(OFFSET + 7);
+    public static final float POW = asNan(OFFSET + 8);
+    public static final float SQRT = asNan(OFFSET + 9);
+    public static final float ABS = asNan(OFFSET + 10);
+    public static final float SIGN = asNan(OFFSET + 11);
+    public static final float COPY_SIGN = asNan(OFFSET + 12);
+    public static final float EXP = asNan(OFFSET + 13);
+    public static final float FLOOR = asNan(OFFSET + 14);
+    public static final float LOG = asNan(OFFSET + 15);
+    public static final float LN = asNan(OFFSET + 16);
+    public static final float ROUND = asNan(OFFSET + 17);
+    public static final float SIN = asNan(OFFSET + 18);
+    public static final float COS = asNan(OFFSET + 19);
+    public static final float TAN = asNan(OFFSET + 20);
+    public static final float ASIN = asNan(OFFSET + 21);
+    public static final float ACOS = asNan(OFFSET + 22);
+
+    public static final float ATAN = asNan(OFFSET + 23);
+
+    public static final float ATAN2 = asNan(OFFSET + 24);
+    public static final float MAD = asNan(OFFSET + 25);
+    public static final float IFELSE = asNan(OFFSET + 26);
+
+    public static final float CLAMP = asNan(OFFSET + 27);
+    public static final float CBRT = asNan(OFFSET + 28);
+    public static final float DEG = asNan(OFFSET + 29);
+    public static final float RAD = asNan(OFFSET + 30);
+    public static final float CEIL = asNan(OFFSET + 31);
+
+
+    public static final float LAST_OP = 31;
+
+
+    public static final float VAR1 = asNan(OFFSET + 27);
+    public static final float VAR2 = asNan(OFFSET + 28);
+
+    // TODO CLAMP, CBRT, DEG, RAD, EXPM1, CEIL, FLOOR
+    private static final float FP_PI = (float) Math.PI;
+    private static final float FP_TO_RAD = 57.29577951f; // 180/PI
+    private static final float FP_TO_DEG = 0.01745329252f; // 180/PI
+
+    float[] mStack;
+    float[] mLocalStack = new float[128];
+    float[] mVar;
+
+    /**
+     * is float a math operator
+     * @param v
+     * @return
+     */
+    public static boolean isMathOperator(float v) {
+        if (Float.isNaN(v)) {
+            int pos = fromNaN(v);
+            return pos > OFFSET && pos <= OFFSET + LAST_OP;
+        }
+        return false;
+    }
+
+    interface Op {
+        int eval(int sp);
+    }
+
+    /**
+     * Evaluate a float expression
+     * @param exp
+     * @param var
+     * @return
+     */
+    public float eval(float[] exp, float... var) {
+        mStack = exp;
+        mVar = var;
+        int sp = -1;
+        for (int i = 0; i < mStack.length; i++) {
+            float v = mStack[i];
+            if (Float.isNaN(v)) {
+                sp = mOps[fromNaN(v) - OFFSET].eval(sp);
+            } else {
+                mStack[++sp] = v;
+            }
+        }
+        return mStack[sp];
+    }
+
+    /**
+     * Evaluate a float expression
+     * @param exp
+     * @param len
+     * @param var
+     * @return
+     */
+    public float eval(float[] exp, int len, float... var) {
+        System.arraycopy(exp, 0, mLocalStack, 0, len);
+        mStack = mLocalStack;
+        mVar = var;
+        int sp = -1;
+        for (int i = 0; i < len; i++) {
+            float v = mStack[i];
+            if (Float.isNaN(v)) {
+                sp = mOps[fromNaN(v) - OFFSET].eval(sp);
+            } else {
+                mStack[++sp] = v;
+            }
+        }
+        return mStack[sp];
+    }
+
+    /**
+     * Evaluate a float expression
+     * @param exp
+     * @param var
+     * @return
+     */
+    public float evalDB(float[] exp, float... var) {
+        mStack = exp;
+        mVar = var;
+        int sp = -1;
+        for (float v : exp) {
+            if (Float.isNaN(v)) {
+                System.out.print(" " + sNames.get((fromNaN(v) - OFFSET)));
+                sp = mOps[fromNaN(v) - OFFSET].eval(sp);
+            } else {
+                System.out.print(" " + v);
+                mStack[++sp] = v;
+            }
+        }
+        return mStack[sp];
+    }
+
+    Op[] mOps = {
+            null,
+            (sp) -> { // ADD
+                mStack[sp - 1] = mStack[sp - 1] + mStack[sp];
+                return sp - 1;
+            },
+            (sp) -> { // SUB
+                mStack[sp - 1] = mStack[sp - 1] - mStack[sp];
+                return sp - 1;
+            },
+            (sp) -> { // MUL
+                mStack[sp - 1] = mStack[sp - 1] * mStack[sp];
+                return sp - 1;
+            },
+            (sp) -> {  // DIV
+                mStack[sp - 1] = mStack[sp - 1] / mStack[sp];
+                return sp - 1;
+            },
+            (sp) -> {  // MOD
+                mStack[sp - 1] = mStack[sp - 1] % mStack[sp];
+                return sp - 1;
+            },
+            (sp) -> { // MIN
+                mStack[sp - 1] = (float) Math.min(mStack[sp - 1], mStack[sp]);
+                return sp - 1;
+            },
+            (sp) -> { // MAX
+                mStack[sp - 1] = (float) Math.max(mStack[sp - 1], mStack[sp]);
+                return sp - 1;
+            },
+            (sp) -> { // POW
+                mStack[sp - 1] = (float) Math.pow(mStack[sp - 1], mStack[sp]);
+                return sp - 1;
+            },
+            (sp) -> { // SQRT
+                mStack[sp] = (float) Math.sqrt(mStack[sp]);
+                return sp;
+            },
+            (sp) -> { // ABS
+                mStack[sp] = (float) Math.abs(mStack[sp]);
+                return sp;
+            },
+            (sp) -> { // SIGN
+                mStack[sp] = (float) Math.signum(mStack[sp]);
+                return sp;
+            },
+            (sp) -> { // copySign
+                mStack[sp - 1] = (float) Math.copySign(mStack[sp - 1], mStack[sp]);
+                return sp - 1;
+            },
+            (sp) -> { // EXP
+                mStack[sp] = (float) Math.exp(mStack[sp]);
+                return sp;
+            },
+            (sp) -> { // FLOOR
+                mStack[sp] = (float) Math.floor(mStack[sp]);
+                return sp;
+            },
+            (sp) -> { // LOG
+                mStack[sp] = (float) Math.log10(mStack[sp]);
+                return sp;
+            },
+            (sp) -> { // LN
+                mStack[sp] = (float) Math.log(mStack[sp]);
+                return sp;
+            },
+            (sp) -> { // ROUND
+                mStack[sp] = (float) Math.round(mStack[sp]);
+                return sp;
+            },
+            (sp) -> { // SIN
+                mStack[sp] = (float) Math.sin(mStack[sp]);
+                return sp;
+            },
+            (sp) -> { // COS
+                mStack[sp] = (float) Math.cos(mStack[sp]);
+                return sp;
+            },
+            (sp) -> { // TAN
+                mStack[sp] = (float) Math.tan(mStack[sp]);
+                return sp;
+            },
+            (sp) -> { // ASIN
+                mStack[sp] = (float) Math.asin(mStack[sp]);
+                return sp;
+            },
+            (sp) -> { // ACOS
+                mStack[sp] = (float) Math.acos(mStack[sp]);
+                return sp;
+            },
+            (sp) -> { // ATAN
+                mStack[sp] = (float) Math.atan(mStack[sp]);
+                return sp;
+            },
+            (sp) -> { // ATAN2
+                mStack[sp - 1] = (float) Math.atan2(mStack[sp - 1], mStack[sp]);
+                return sp - 1;
+            },
+            (sp) -> { // MAD
+                mStack[sp - 2] = mStack[sp] + mStack[sp - 1] * mStack[sp - 2];
+                return sp - 2;
+            },
+            (sp) -> { // Ternary conditional
+                mStack[sp - 2] = (mStack[sp] > 0)
+                        ? mStack[sp - 1] : mStack[sp - 2];
+                return sp - 2;
+            },
+            (sp) -> { // CLAMP(min,max, val)
+                mStack[sp - 2] = Math.min(Math.max(mStack[sp - 2], mStack[sp]),
+                        mStack[sp - 1]);
+                return sp - 2;
+            },
+            (sp) -> { // CBRT cuberoot
+                mStack[sp] = (float) Math.pow(mStack[sp], 1 / 3.);
+                return sp;
+            },
+            (sp) -> { // DEG
+                mStack[sp] = mStack[sp] * FP_TO_RAD;
+                return sp;
+            },
+            (sp) -> { // RAD
+                mStack[sp] = mStack[sp] * FP_TO_DEG;
+                return sp;
+            },
+            (sp) -> { // CEIL
+                mStack[sp] = (float) Math.ceil(mStack[sp]);
+                return sp;
+            },
+            (sp) -> { // first var =
+                mStack[sp] = mVar[0];
+                return sp;
+            },
+            (sp) -> { // second var y?
+                mStack[sp] = mVar[1];
+                return sp;
+            },
+            (sp) -> { // 3rd var z?
+                mStack[sp] = mVar[2];
+                return sp;
+            },
+    };
+
+    static {
+        int k = 0;
+        sNames.put(k++, "NOP");
+        sNames.put(k++, "+");
+        sNames.put(k++, "-");
+        sNames.put(k++, "*");
+        sNames.put(k++, "/");
+        sNames.put(k++, "%");
+        sNames.put(k++, "min");
+        sNames.put(k++, "max");
+        sNames.put(k++, "pow");
+        sNames.put(k++, "sqrt");
+        sNames.put(k++, "abs");
+        sNames.put(k++, "sign");
+        sNames.put(k++, "copySign");
+        sNames.put(k++, "exp");
+        sNames.put(k++, "floor");
+        sNames.put(k++, "log");
+        sNames.put(k++, "ln");
+        sNames.put(k++, "round");
+        sNames.put(k++, "sin");
+        sNames.put(k++, "cos");
+        sNames.put(k++, "tan");
+        sNames.put(k++, "asin");
+        sNames.put(k++, "acos");
+        sNames.put(k++, "atan");
+        sNames.put(k++, "atan2");
+        sNames.put(k++, "mad");
+        sNames.put(k++, "ifElse");
+        sNames.put(k++, "clamp");
+        sNames.put(k++, "cbrt");
+        sNames.put(k++, "deg");
+        sNames.put(k++, "rad");
+        sNames.put(k++, "ceil");
+        sNames.put(k++, "a[0]");
+        sNames.put(k++, "a[1]");
+        sNames.put(k++, "a[2]");
+    }
+
+    /**
+     * given a float command return its math name (e.g sin, cos etc.)
+     * @param f
+     * @return
+     */
+    public static String toMathName(float f) {
+        int id = fromNaN(f) - OFFSET;
+        return sNames.get(id);
+    }
+
+    /**
+     * Convert an expression encoded as an array of floats int ot a string
+     * @param exp
+     * @param labels
+     * @return
+     */
+    public static String toString(float[] exp, String[] labels) {
+        StringBuilder s = new StringBuilder();
+        for (int i = 0; i < exp.length; i++) {
+            float v = exp[i];
+            if (Float.isNaN(v)) {
+                if (isMathOperator(v)) {
+                    s.append(toMathName(v));
+                } else {
+                    s.append("[");
+                    s.append(fromNaN(v));
+                    s.append("]");
+                }
+            } else {
+                if (labels[i] != null) {
+                    s.append(labels[i]);
+                }
+                s.append(v);
+            }
+            s.append(" ");
+        }
+        return s.toString();
+    }
+
+    static String toString(float[] exp, int sp) {
+        String[] str = new String[exp.length];
+        if (Float.isNaN(exp[sp])) {
+            int id = fromNaN(exp[sp]) - OFFSET;
+            switch (NO_OF_OPS[id]) {
+                case -1:
+                    return "nop";
+                case 1:
+                    return sNames.get(id) + "(" + toString(exp, sp + 1) + ") ";
+                case 2:
+                    if (infix(id)) {
+                        return "(" + toString(exp, sp + 1)
+                                + sNames.get(id) + " "
+                                + toString(exp, sp + 2) + ") ";
+                    } else {
+                        return sNames.get(id) + "("
+                                + toString(exp, sp + 1) + ", "
+                                + toString(exp, sp + 2) + ")";
+                    }
+                case 3:
+                    if (infix(id)) {
+                        return "((" + toString(exp, sp + 1) + ") ? "
+                                + toString(exp, sp + 2) + ":"
+                                + toString(exp, sp + 3) + ")";
+                    } else {
+                        return sNames.get(id)
+                                + "(" + toString(exp, sp + 1)
+                                + ", " + toString(exp, sp + 2)
+                                + ", " + toString(exp, sp + 3) + ")";
+                    }
+            }
+        }
+        return Float.toString(exp[sp]);
+    }
+
+    static final int[] NO_OF_OPS = {
+            -1, // no op
+            2, 2, 2, 2, 2, // + - * / %
+            2, 2, 2,  // min max, power
+            1, 1, 1, 1, 1, 1, 1, 1,  //sqrt,abs,CopySign,exp,floor,log,ln
+            1, 1, 1, 1, 1, 1, 1, 2,  // round,sin,cos,tan,asin,acos,atan,atan2
+            3, 3, 3, 1, 1, 1, 1,
+            0, 0, 0 // mad, ?:,
+            // a[0],a[1],a[2]
+    };
+
+    /**
+     * to be used by parser to determine if command is infix
+     * @param n
+     * @return
+     */
+    static boolean infix(int n) {
+        return ((n < 6) || (n == 25) || (n == 26));
+    }
+
+    /**
+     * Convert an id into a NaN object
+     * @param v
+     * @return
+     */
+    public static float asNan(int v) {
+        return Float.intBitsToFloat(v | -0x800000);
+    }
+
+    /**
+     * Get ID from a NaN float
+     * @param v
+     * @return
+     */
+    public static int fromNaN(float v) {
+        int b = Float.floatToRawIntBits(v);
+        return b & 0xFFFFF;
+    }
+
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ColorUtils.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ColorUtils.java
new file mode 100644
index 0000000..0ea28a8
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ColorUtils.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2024 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.internal.widget.remotecompose.core.operations.utilities;
+
+/**
+ * These are tools to use long Color as variables
+ * long colors are stored a 0xXXXXXXXX XXXXXX??
+ * in SRGB the colors are stored 0xAARRGGBB,00000000
+ * SRGB color sapce is color space 0
+ * Our Color will use color float with a
+ * Current android supports
+ * SRGB, LINEAR_SRGB, EXTENDED_SRGB, LINEAR_EXTENDED_SRGB, BT709, BT2020,
+ * DCI_P3, DISPLAY_P3, NTSC_1953, SMPTE_C, ADOBE_RGB, PRO_PHOTO_RGB, ACES,
+ * ACESCG, CIE_XYZ, CIE_LAB, BT2020_HLG, BT2020_PQ 0..17 respectively
+ *
+ * Our color space will be 62 (MAX_ID-1). (0x3E)
+ * Storing the default value in SRGB format and having the
+ * id of the color between the ARGB values and the 62 i.e.
+ * 0xAARRGGBB 00 00 00 3E
+ *
+ */
+public class ColorUtils {
+    public static int RC_COLOR = 62;
+
+    long packRCColor(int defaultARGB, int id) {
+        long l = defaultARGB;
+        return (l << 32) | id << 8 | RC_COLOR;
+    }
+
+    boolean isRCColor(long color) {
+        return ((color & 0x3F) == 62);
+    }
+
+    int getID(long color) {
+        if (isRCColor(color)) {
+            return (int) ((color & 0xFFFFFF00) >> 8);
+        }
+        return -1;
+    }
+
+    /**
+     * get default color from long color
+     * @param color
+     * @return
+     */
+    public int getDefaultColor(long color) {
+        if (isRCColor(color)) {
+            return (int) (color >> 32);
+        }
+        if (((color & 0xFF) == 0)) {
+            return (int) (color >> 32);
+        }
+        return 0;
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntMap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntMap.java
index 8051ef1..0512fa6 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntMap.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntMap.java
@@ -50,7 +50,6 @@
         return insert(key, value);
     }
 
-
     public  T get(int key) {
         int index = findKey(key);
         if (index == -1) {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/NanMap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/NanMap.java
new file mode 100644
index 0000000..f4cd504
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/NanMap.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 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.internal.widget.remotecompose.core.operations.utilities;
+
+import com.android.internal.widget.remotecompose.core.operations.Utils;
+
+/**
+ * This defines the major id maps and ranges used by remote compose
+ * Generally ids ranging from 0 ... FFF (4095) are for ids
+ * 0x1000-0x1100 are used for path operations in PathData
+ * 0x1100-0x1200 are used for math operations in Animated float
+ * 0x
+ */
+public class NanMap {
+
+    public static final int MOVE = 0x1000;
+    public static final int LINE = 0x1001;
+    public static final int QUADRATIC = 0x1002;
+    public static final int CONIC = 0x1003;
+    public static final int CUBIC = 0x1004;
+    public static final int CLOSE = 0x1005;
+    public static final int DONE = 0x1006;
+    public static final float MOVE_NAN = Utils.asNan(MOVE);
+    public static final float LINE_NAN = Utils.asNan(LINE);
+    public static final float QUADRATIC_NAN = Utils.asNan(QUADRATIC);
+    public static final float CONIC_NAN = Utils.asNan(CONIC);
+    public static final float CUBIC_NAN = Utils.asNan(CUBIC);
+    public static final float CLOSE_NAN = Utils.asNan(CLOSE);
+    public static final float DONE_NAN = Utils.asNan(DONE);
+
+    /**
+     *
+     */
+    public static final float ADD = asNan(0x1100);
+    public static final float SUB = asNan(0x1101);
+    public static final float MUL = asNan(0x1102);
+    public static final float DIV = asNan(0x1103);
+    public static final float MOD = asNan(0x1104);
+    public static final float MIN = asNan(0x1105);
+    public static final float MAX = asNan(0x1106);
+    public static final float POW = asNan(0x1107);
+
+
+    /**
+     * Get ID from Nan float
+     * @param v
+     * @return
+     */
+    public static int fromNaN(float v) {
+        int b = Float.floatToRawIntBits(v);
+        return b & 0xFFFFF;
+    }
+
+    /**
+     * Given id return as a Nan float
+     * @param v
+     * @return
+     */
+    public static float asNan(int v) {
+        return Float.intBitsToFloat(v | 0xFF800000);
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringUtils.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringUtils.java
new file mode 100644
index 0000000..8dd5405
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringUtils.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2024 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.internal.widget.remotecompose.core.operations.utilities;
+
+import java.util.Arrays;
+
+/**
+ * Utilities for string manipulation
+ */
+public class StringUtils {
+    /**
+     * Converts a float into a string.
+     * Providing a defined number of characters before and after the
+     * decimal point.
+     *
+     * @param value              The value to convert to string
+     * @param beforeDecimalPoint digits before the decimal point
+     * @param afterDecimalPoint  digits after the decimal point
+     * @param pre                character to pad width 0 = no pad typically ' ' or '0'
+     * @param post               character to pad width 0 = no pad typically ' ' or '0'
+     * @return
+     */
+    public static String floatToString(float value,
+                                       int beforeDecimalPoint,
+                                       int afterDecimalPoint,
+                                       char pre, char post) {
+
+        int integerPart = (int) value;
+        float fractionalPart = value % 1;
+
+        // Convert integer part to string and pad with spaces
+        String integerPartString = String.valueOf(integerPart);
+        int iLen = integerPartString.length();
+        if (iLen < beforeDecimalPoint) {
+            int spacesToPad = beforeDecimalPoint - iLen;
+            if (pre != 0) {
+                char[] pad = new char[spacesToPad];
+                Arrays.fill(pad, pre);
+                integerPartString = new String(pad) + integerPartString;
+            }
+
+
+        } else if (iLen > beforeDecimalPoint) {
+            integerPartString = integerPartString.substring(iLen - beforeDecimalPoint);
+        }
+        if (afterDecimalPoint == 0) {
+            return integerPartString;
+        }
+        // Convert fractional part to string and pad with zeros
+
+        for (int i = 0; i < afterDecimalPoint; i++) {
+            fractionalPart *= 10;
+        }
+
+        fractionalPart = Math.round(fractionalPart);
+
+        for (int i = 0; i < afterDecimalPoint; i++) {
+            fractionalPart *= .1;
+        }
+
+        String fact = Float.toString(fractionalPart);
+        fact = fact.substring(2, Math.min(fact.length(), afterDecimalPoint + 2));
+        int trim = fact.length();
+        for (int i = fact.length() - 1; i >= 0; i--) {
+            if (fact.charAt(i) != '0') {
+                break;
+            }
+            trim--;
+        }
+        if (trim != fact.length()) {
+            fact = fact.substring(0, trim);
+        }
+        int len = fact.length();
+        if (post != 0 && len < afterDecimalPoint) {
+            char[] c = new char[afterDecimalPoint - len];
+            Arrays.fill(c, post);
+            fact = fact + new String(c);
+        }
+
+        return integerPartString + "." + fact;
+    }
+
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/BounceCurve.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/BounceCurve.java
new file mode 100644
index 0000000..c3cd5ae9
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/BounceCurve.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 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.internal.widget.remotecompose.core.operations.utilities.easing;
+
+/**
+ * Provide a specific bouncing easing function
+ */
+public class BounceCurve extends Easing {
+    private static final float N1 = 7.5625f;
+    private static final float D1 = 2.75f;
+
+    BounceCurve(int type) {
+        mType = type;
+    }
+
+    @Override
+    public float get(float x) {
+        float t = x;
+        if (t < 0) {
+            return 0f;
+        }
+        if (t < 1 / D1) {
+            return 1 / (1 + 1 / D1) * (N1 * t * t + t);
+        } else if (t < 2 / D1) {
+            t -= 1.5f / D1;
+            return N1 * t * t + 0.75f;
+        } else if (t < 2.5 / D1) {
+            t -= 2.25f / D1;
+            return N1 * t * t + 0.9375f;
+        } else if (t <= 1) {
+            t -= 2.625f / D1;
+            return N1 * t * t + 0.984375f;
+        }
+        return 1f;
+    }
+
+    @Override
+    public float getDiff(float x) {
+        if (x < 0) {
+            return 0f;
+        }
+        if (x < 1 / D1) {
+            return 2 * N1 * x / (1 + 1 / D1) + 1 / (1 + 1 / D1);
+        } else if (x < 2 / D1) {
+            return 2 * N1 * (x - 1.5f / D1);
+        } else if (x < 2.5 / D1) {
+            return 2 * N1 * (x - 2.25f / D1);
+        } else if (x <= 1) {
+            return 2 * N1 * (x - 2.625f / D1);
+        }
+        return 0f;
+    }
+
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/CubicEasing.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/CubicEasing.java
new file mode 100644
index 0000000..fd1ee03
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/CubicEasing.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2024 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.internal.widget.remotecompose.core.operations.utilities.easing;
+
+class CubicEasing extends Easing {
+    float mType = 0;
+    float mX1 = 0f;
+    float mY1 = 0f;
+    float mX2 = 0f;
+    float mY2 = 0f;
+
+    private static final float[] STANDARD = {0.4f, 0.0f, 0.2f, 1f};
+    private static final float[] ACCELERATE = {0.4f, 0.05f, 0.8f, 0.7f};
+    private static final float[] DECELERATE = {0.0f, 0.0f, 0.2f, 0.95f};
+    private static final float[] LINEAR = {1f, 1f, 0f, 0f};
+    private static final float[] ANTICIPATE = {0.36f, 0f, 0.66f, -0.56f};
+    private static final float[] OVERSHOOT = {0.34f, 1.56f, 0.64f, 1f};
+
+    CubicEasing(int type) {
+        mType = type;
+        config(type);
+    }
+
+    CubicEasing(float x1, float y1, float x2, float y2) {
+        setup(x1, y1, x2, y2);
+    }
+
+    public void config(int type) {
+
+        switch (type) {
+            case CUBIC_STANDARD:
+                setup(STANDARD);
+                break;
+            case CUBIC_ACCELERATE:
+                setup(ACCELERATE);
+                break;
+            case CUBIC_DECELERATE:
+                setup(DECELERATE);
+                break;
+            case CUBIC_LINEAR:
+                setup(LINEAR);
+                break;
+            case CUBIC_ANTICIPATE:
+                setup(ANTICIPATE);
+                break;
+            case CUBIC_OVERSHOOT:
+                setup(OVERSHOOT);
+                break;
+        }
+        mType = type;
+    }
+
+    void setup(float[] values) {
+        setup(values[0], values[1], values[2], values[3]);
+    }
+
+    void setup(float x1, float y1, float x2, float y2) {
+        mX1 = x1;
+        mY1 = y1;
+        mX2 = x2;
+        mY2 = y2;
+    }
+
+    private float getX(float t) {
+        float t1 = 1 - t;
+        // no need for because start at 0,0 float f0 = (1 - t) * (1 - t) * (1 - t)
+        float f1 = 3 * t1 * t1 * t;
+        float f2 = 3 * t1 * t * t;
+        float f3 = t * t * t;
+        return mX1 * f1 + mX2 * f2 + f3;
+    }
+
+    private float getY(float t) {
+        float t1 = 1 - t;
+        // no need for testing because start at 0,0 float f0 = (1 - t) * (1 - t) * (1 - t)
+        float f1 = 3 * t1 * t1 * t;
+        float f2 = 3 * t1 * t * t;
+        float f3 = t * t * t;
+        return mY1 * f1 + mY2 * f2 + f3;
+    }
+
+    private float getDiffX(float t) {
+        float t1 = 1 - t;
+        return 3 * t1 * t1 * mX1 + 6 * t1 * t * (mX2 - mX1) + 3 * t * t * (1 - mX2);
+    }
+
+    private float getDiffY(float t) {
+        float t1 = 1 - t;
+        return 3 * t1 * t1 * mY1 + 6 * t1 * t * (mY2 - mY1) + 3 * t * t * (1 - mY2);
+    }
+
+    /**
+     * binary search for the region and linear interpolate the answer
+     */
+    public float getDiff(float x) {
+        float t = 0.5f;
+        float range = 0.5f;
+        while (range > D_ERROR) {
+            float tx = getX(t);
+            range *= 0.5;
+            if (tx < x) {
+                t += range;
+            } else {
+                t -= range;
+            }
+        }
+        float x1 = getX(t - range);
+        float x2 = getX(t + range);
+        float y1 = getY(t - range);
+        float y2 = getY(t + range);
+        return (y2 - y1) / (x2 - x1);
+    }
+
+    /**
+     * binary search for the region and linear interpolate the answer
+     */
+    public float get(float x) {
+        if (x <= 0.0f) {
+            return 0f;
+        }
+        if (x >= 1.0f) {
+            return 1.0f;
+        }
+        float t = 0.5f;
+        float range = 0.5f;
+        while (range > ERROR) {
+            float tx = getX(t);
+            range *= 0.5f;
+            if (tx < x) {
+                t += range;
+            } else {
+                t -= range;
+            }
+        }
+        float x1 = getX(t - range);
+        float x2 = getX(t + range);
+        float y1 = getY(t - range);
+        float y2 = getY(t + range);
+        return (y2 - y1) * (x - x1) / (x2 - x1) + y1;
+    }
+
+    private static final float ERROR = 0.01f;
+    private static final float D_ERROR = 0.0001f;
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/Easing.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/Easing.java
new file mode 100644
index 0000000..4ed9550
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/Easing.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 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.internal.widget.remotecompose.core.operations.utilities.easing;
+
+/**
+ * The standard interface to Easing functions
+ */
+public abstract class Easing {
+    int mType;
+    /**
+     * get the value at point x
+     */
+    public abstract float get(float x);
+
+    /**
+     * get the slope of the easing function at at x
+     */
+    public abstract float getDiff(float x);
+
+    public int getType() {
+        return mType;
+    }
+
+    public static final int CUBIC_STANDARD = 1;
+    public static final int CUBIC_ACCELERATE = 2;
+    public static final int CUBIC_DECELERATE = 3;
+    public static final int CUBIC_LINEAR = 4;
+    public static final int CUBIC_ANTICIPATE = 5;
+    public static final int CUBIC_OVERSHOOT = 6;
+    public static final int CUBIC_CUSTOM = 11;
+    public static final int SPLINE_CUSTOM = 12;
+    public static final int EASE_OUT_BOUNCE = 13;
+    public static final int EASE_OUT_ELASTIC = 14;
+
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/ElasticOutCurve.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/ElasticOutCurve.java
new file mode 100644
index 0000000..e269583
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/ElasticOutCurve.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 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.internal.widget.remotecompose.core.operations.utilities.easing;
+
+/**
+ * Provide a bouncing Easing function
+ */
+public class ElasticOutCurve extends Easing {
+    private static final float F_PI = (float) Math.PI;
+    private static final float C4 = 2 * F_PI / 3;
+    private static final float TWENTY_PI = 20 * F_PI;
+    private static final float LOG_8 = (float) Math.log(8.0f);
+
+    @Override
+    public float get(float x) {
+        if (x <= 0) {
+            return 0.0f;
+        }
+        if (x >= 1) {
+            return 1.0f;
+        } else
+            return (float) (Math.pow(2.0f, -10 * x)
+                    * Math.sin((x * 10 - 0.75f) * C4) + 1);
+    }
+
+    @Override
+    public float getDiff(float x) {
+        if (x < 0 || x > 1) {
+            return 0.0f;
+        } else
+            return (float) ((5 * Math.pow(2.0f, (1 - 10 * x))
+                    * (LOG_8 * Math.cos(TWENTY_PI * x / 3) + 2
+                    * F_PI * Math.sin(TWENTY_PI * x / 3))
+                    / 3));
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java
new file mode 100644
index 0000000..4f484de
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2024 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.internal.widget.remotecompose.core.operations.utilities.easing;
+
+/**
+ * Support Animation of the FloatExpression
+ */
+public class FloatAnimation extends Easing {
+    float[] mSpec;
+    // mSpec[0] = duration
+    // int(mSpec[1]) = num_of_param << 16 | type
+    // mSpec[2..1+num_of_param] params
+    // mSpec[2+num_of_param] starting Value
+    Easing mEasingCurve;
+    private int mType = CUBIC_STANDARD;
+    private float mDuration = 1;
+    private float mWrap = Float.NaN;
+    private float mInitialValue = Float.NaN;
+    private float mTargetValue = Float.NaN;
+    private float mScale = 1;
+    float mOffset = 0;
+
+    @Override
+    public String toString() {
+
+        String str = "type " + mType;
+        if (!Float.isNaN(mInitialValue)) {
+            str += " " + mInitialValue;
+        }
+        if (!Float.isNaN(mTargetValue)) {
+            str += " -> " + mTargetValue;
+        }
+        if (!Float.isNaN(mWrap)) {
+            str += "  % " + mWrap;
+        }
+
+        return str;
+    }
+
+    public FloatAnimation() {
+    }
+
+    public FloatAnimation(float... description) {
+        setAnimationDescription(description);
+    }
+
+    public FloatAnimation(int type,
+                          float duration,
+                          float[] description,
+                          float initialValue,
+                          float wrap) {
+        setAnimationDescription(packToFloatArray(duration,
+                type, description, initialValue, wrap));
+    }
+
+    /**
+     * packs spec into a float array
+     *
+     * @param duration
+     * @param type
+     * @param spec
+     * @param initialValue
+     * @return
+     */
+    public static float[] packToFloatArray(float duration,
+                                           int type,
+                                           float[] spec,
+                                           float initialValue,
+                                           float wrap) {
+        int count = 0;
+
+        if (!Float.isNaN(initialValue)) {
+            count++;
+        }
+        if (spec != null) {
+            count++;
+        }
+        if (spec != null || type != CUBIC_STANDARD) {
+            count++;
+            count += (spec == null) ? 0 : spec.length;
+        }
+        if (duration != 1 || count > 0) {
+            count++;
+        }
+        if (!Float.isNaN(initialValue)) {
+            count++;
+        }
+        if (!Float.isNaN(wrap)) {
+            count++;
+        }
+        float[] ret = new float[count];
+        int pos = 0;
+        int specLen = (spec == null) ? 0 : spec.length;
+
+        if (ret.length > 0) {
+            ret[pos++] = duration;
+
+        }
+        if (ret.length > 1) {
+            int wrapBit = (Float.isNaN(wrap)) ? 0 : 1;
+            int initBit = (Float.isNaN(initialValue)) ? 0 : 2;
+            int bits = type | ((wrapBit | initBit) << 8);
+            ret[pos++] = Float.intBitsToFloat(specLen << 16 | bits);
+        }
+
+        if (specLen > 0) {
+            System.arraycopy(spec, 0, ret, pos, spec.length);
+            pos += spec.length;
+        }
+        if (!Float.isNaN(initialValue)) {
+            ret[pos++] = initialValue;
+        }
+        if (!Float.isNaN(wrap)) {
+            ret[pos] = wrap;
+        }
+        return ret;
+    }
+
+    /**
+     * Create an animation based on a float encoding of the animation
+     * @param description
+     */
+    public void setAnimationDescription(float[] description) {
+        mSpec = description;
+        mDuration = (mSpec.length == 0) ? 1 : mSpec[0];
+        int len = 0;
+        if (mSpec.length > 1) {
+            int num_type = Float.floatToRawIntBits(mSpec[1]);
+            mType = num_type & 0xFF;
+            boolean wrap = ((num_type >> 8) & 0x1) > 0;
+            boolean init = ((num_type >> 8) & 0x2) > 0;
+            len = (num_type >> 16) & 0xFFFF;
+            int off = 2 + len;
+            if (init) {
+                mInitialValue = mSpec[off++];
+            }
+            if (wrap) {
+                mWrap = mSpec[off];
+            }
+        }
+        create(mType, description, 2, len);
+    }
+
+    private void create(int type, float[] params, int offset, int len) {
+        switch (type) {
+            case CUBIC_STANDARD:
+            case CUBIC_ACCELERATE:
+            case CUBIC_DECELERATE:
+            case CUBIC_LINEAR:
+            case CUBIC_ANTICIPATE:
+            case CUBIC_OVERSHOOT:
+                mEasingCurve = new CubicEasing(type);
+                break;
+            case CUBIC_CUSTOM:
+                mEasingCurve = new CubicEasing(params[offset + 0],
+                        params[offset + 1],
+                        params[offset + 2],
+                        params[offset + 3]
+                );
+                break;
+            case EASE_OUT_BOUNCE:
+                mEasingCurve = new BounceCurve(type);
+                break;
+            case EASE_OUT_ELASTIC:
+                mEasingCurve = new ElasticOutCurve();
+                break;
+            case SPLINE_CUSTOM:
+                mEasingCurve = new StepCurve(params, offset, len);
+                break;
+        }
+    }
+
+    /**
+     * Get the duration the interpolate is to take
+     * @return duration in seconds
+     */
+    public float getDuration() {
+        return mDuration;
+    }
+
+    /**
+     * Set the initial Value
+     * @param value
+     */
+    public void setInitialValue(float value) {
+
+        if (Float.isNaN(mWrap)) {
+            mInitialValue = value;
+        } else {
+            mInitialValue = value % mWrap;
+        }
+        setScaleOffset();
+    }
+
+    /**
+     * Set the target value to interpolate to
+     * @param value
+     */
+    public void setTargetValue(float value) {
+        if (Float.isNaN(mWrap)) {
+            mTargetValue = value;
+        } else {
+            if (Math.abs((value % mWrap) + mWrap - mInitialValue)
+                    < Math.abs((value % mWrap) - mInitialValue)) {
+                mTargetValue = (value % mWrap) + mWrap;
+
+            } else {
+                mTargetValue = value % mWrap;
+            }
+        }
+        setScaleOffset();
+    }
+
+    public float getTargetValue() {
+        return mTargetValue;
+    }
+
+    private void setScaleOffset() {
+        if (!Float.isNaN(mInitialValue) && !Float.isNaN(mTargetValue)) {
+            mScale = (mTargetValue - mInitialValue);
+            mOffset = mInitialValue;
+        } else {
+            mScale = 1;
+            mOffset = 0;
+        }
+    }
+
+    /**
+     * get the value at time t in seconds since start
+     */
+    public float get(float t) {
+        return mEasingCurve.get(t / mDuration)
+                * (mTargetValue - mInitialValue) + mInitialValue;
+    }
+
+    /**
+     * get the slope of the easing function at at x
+     */
+    public float getDiff(float t) {
+        return mEasingCurve.getDiff(t / mDuration) * (mTargetValue - mInitialValue);
+    }
+
+    public float getInitialValue() {
+        return mInitialValue;
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java
new file mode 100644
index 0000000..693deaf
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 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.internal.widget.remotecompose.core.operations.utilities.easing;
+
+/**
+ * Provides and interface to create easing functions
+ */
+public class GeneralEasing extends  Easing{
+    float[] mEasingData = new float[0];
+    Easing mEasingCurve = new CubicEasing(CUBIC_STANDARD);
+
+    /**
+     * Set the curve based on the float encoding of it
+     * @param data
+     */
+    public void setCurveSpecification(float[] data) {
+        mEasingData = data;
+        createEngine();
+    }
+
+    public float[] getCurveSpecification() {
+        return mEasingData;
+    }
+
+    void createEngine() {
+        int type = Float.floatToRawIntBits(mEasingData[0]);
+        switch (type) {
+            case CUBIC_STANDARD:
+            case CUBIC_ACCELERATE:
+            case CUBIC_DECELERATE:
+            case CUBIC_LINEAR:
+            case CUBIC_ANTICIPATE:
+            case CUBIC_OVERSHOOT:
+                mEasingCurve = new CubicEasing(type);
+                break;
+            case CUBIC_CUSTOM:
+                mEasingCurve = new CubicEasing(mEasingData[1],
+                        mEasingData[2],
+                        mEasingData[3],
+                        mEasingData[5]
+                );
+                break;
+            case EASE_OUT_BOUNCE:
+                mEasingCurve = new BounceCurve(type);
+                break;
+        }
+    }
+
+    /**
+     * get the value at point x
+     */
+    public float get(float x) {
+        return mEasingCurve.get(x);
+    }
+
+    /**
+     * get the slope of the easing function at at x
+     */
+    public float getDiff(float x) {
+        return mEasingCurve.getDiff(x);
+    }
+
+    public int getType() {
+        return mEasingCurve.getType();
+    }
+
+
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java
new file mode 100644
index 0000000..23930b9
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2024 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.internal.widget.remotecompose.core.operations.utilities.easing;
+
+import java.util.Arrays;
+
+/**
+ * This performs a spline interpolation in multiple dimensions
+ *
+ *
+ */
+public class MonotonicCurveFit  {
+    private static final String TAG = "MonotonicCurveFit";
+    private double[] mT;
+    private double[][] mY;
+    private double[][] mTangent;
+    private boolean mExtrapolate = true;
+    double[] mSlopeTemp;
+
+    /**
+     * create a collection of curves
+     * @param time the point along the curve
+     * @param y the parameter at those points
+     */
+    public MonotonicCurveFit(double[] time, double[][] y) {
+        final int n = time.length;
+        final int dim = y[0].length;
+        mSlopeTemp = new double[dim];
+        double[][] slope = new double[n - 1][dim]; // could optimize this out
+        double[][] tangent = new double[n][dim];
+        for (int j = 0; j < dim; j++) {
+            for (int i = 0; i < n - 1; i++) {
+                double dt = time[i + 1] - time[i];
+                slope[i][j] = (y[i + 1][j] - y[i][j]) / dt;
+                if (i == 0) {
+                    tangent[i][j] = slope[i][j];
+                } else {
+                    tangent[i][j] = (slope[i - 1][j] + slope[i][j]) * 0.5f;
+                }
+            }
+            tangent[n - 1][j] = slope[n - 2][j];
+        }
+
+        for (int i = 0; i < n - 1; i++) {
+            for (int j = 0; j < dim; j++) {
+                if (slope[i][j] == 0.) {
+                    tangent[i][j] = 0.;
+                    tangent[i + 1][j] = 0.;
+                } else {
+                    double a = tangent[i][j] / slope[i][j];
+                    double b = tangent[i + 1][j] / slope[i][j];
+                    double h = Math.hypot(a, b);
+                    if (h > 9.0) {
+                        double t = 3. / h;
+                        tangent[i][j] = t * a * slope[i][j];
+                        tangent[i + 1][j] = t * b * slope[i][j];
+                    }
+                }
+            }
+        }
+        mT = time;
+        mY = y;
+        mTangent = tangent;
+    }
+
+    /**
+     * Get the position of all curves at time t
+     * @param t
+     * @param v
+     */
+    public void getPos(double t, double[] v) {
+        final int n = mT.length;
+        final int dim = mY[0].length;
+        if (mExtrapolate) {
+            if (t <= mT[0]) {
+                getSlope(mT[0], mSlopeTemp);
+                for (int j = 0; j < dim; j++) {
+                    v[j] = mY[0][j] + (t - mT[0]) * mSlopeTemp[j];
+                }
+                return;
+            }
+            if (t >= mT[n - 1]) {
+                getSlope(mT[n - 1], mSlopeTemp);
+                for (int j = 0; j < dim; j++) {
+                    v[j] = mY[n - 1][j] + (t - mT[n - 1]) * mSlopeTemp[j];
+                }
+                return;
+            }
+        } else {
+            if (t <= mT[0]) {
+                for (int j = 0; j < dim; j++) {
+                    v[j] = mY[0][j];
+                }
+                return;
+            }
+            if (t >= mT[n - 1]) {
+                for (int j = 0; j < dim; j++) {
+                    v[j] = mY[n - 1][j];
+                }
+                return;
+            }
+        }
+
+        for (int i = 0; i < n - 1; i++) {
+            if (t == mT[i]) {
+                for (int j = 0; j < dim; j++) {
+                    v[j] = mY[i][j];
+                }
+            }
+            if (t < mT[i + 1]) {
+                double h = mT[i + 1] - mT[i];
+                double x = (t - mT[i]) / h;
+                for (int j = 0; j < dim; j++) {
+                    double y1 = mY[i][j];
+                    double y2 = mY[i + 1][j];
+                    double t1 = mTangent[i][j];
+                    double t2 = mTangent[i + 1][j];
+                    v[j] = interpolate(h, x, y1, y2, t1, t2);
+                }
+                return;
+            }
+        }
+    }
+
+    /**
+     * Get the position of all curves at time t
+     * @param t
+     * @param v
+     */
+    public void getPos(double t, float[] v) {
+        final int n = mT.length;
+        final int dim = mY[0].length;
+        if (mExtrapolate) {
+            if (t <= mT[0]) {
+                getSlope(mT[0], mSlopeTemp);
+                for (int j = 0; j < dim; j++) {
+                    v[j] = (float) (mY[0][j] + (t - mT[0]) * mSlopeTemp[j]);
+                }
+                return;
+            }
+            if (t >= mT[n - 1]) {
+                getSlope(mT[n - 1], mSlopeTemp);
+                for (int j = 0; j < dim; j++) {
+                    v[j] = (float) (mY[n - 1][j] + (t - mT[n - 1]) * mSlopeTemp[j]);
+                }
+                return;
+            }
+        } else {
+            if (t <= mT[0]) {
+                for (int j = 0; j < dim; j++) {
+                    v[j] = (float) mY[0][j];
+                }
+                return;
+            }
+            if (t >= mT[n - 1]) {
+                for (int j = 0; j < dim; j++) {
+                    v[j] = (float) mY[n - 1][j];
+                }
+                return;
+            }
+        }
+
+        for (int i = 0; i < n - 1; i++) {
+            if (t == mT[i]) {
+                for (int j = 0; j < dim; j++) {
+                    v[j] = (float) mY[i][j];
+                }
+            }
+            if (t < mT[i + 1]) {
+                double h = mT[i + 1] - mT[i];
+                double x = (t - mT[i]) / h;
+                for (int j = 0; j < dim; j++) {
+                    double y1 = mY[i][j];
+                    double y2 = mY[i + 1][j];
+                    double t1 = mTangent[i][j];
+                    double t2 = mTangent[i + 1][j];
+                    v[j] = (float) interpolate(h, x, y1, y2, t1, t2);
+                }
+                return;
+            }
+        }
+    }
+
+    /**
+     * Get the position of the jth curve at time t
+     * @param t
+     * @param j
+     * @return
+     */
+    public double getPos(double t, int j) {
+        final int n = mT.length;
+        if (mExtrapolate) {
+            if (t <= mT[0]) {
+                return mY[0][j] + (t - mT[0]) * getSlope(mT[0], j);
+            }
+            if (t >= mT[n - 1]) {
+                return mY[n - 1][j] + (t - mT[n - 1]) * getSlope(mT[n - 1], j);
+            }
+        } else {
+            if (t <= mT[0]) {
+                return mY[0][j];
+            }
+            if (t >= mT[n - 1]) {
+                return mY[n - 1][j];
+            }
+        }
+
+        for (int i = 0; i < n - 1; i++) {
+            if (t == mT[i]) {
+                return mY[i][j];
+            }
+            if (t < mT[i + 1]) {
+                double h = mT[i + 1] - mT[i];
+                double x = (t - mT[i]) / h;
+                double y1 = mY[i][j];
+                double y2 = mY[i + 1][j];
+                double t1 = mTangent[i][j];
+                double t2 = mTangent[i + 1][j];
+                return interpolate(h, x, y1, y2, t1, t2);
+
+            }
+        }
+        return 0; // should never reach here
+    }
+
+    /**
+     * Get the slope of all the curves at position t
+     * @param t
+     * @param v
+     */
+    public void getSlope(double t, double[] v) {
+        final int n = mT.length;
+        int dim = mY[0].length;
+        if (t <= mT[0]) {
+            t = mT[0];
+        } else if (t >= mT[n - 1]) {
+            t = mT[n - 1];
+        }
+
+        for (int i = 0; i < n - 1; i++) {
+            if (t <= mT[i + 1]) {
+                double h = mT[i + 1] - mT[i];
+                double x = (t - mT[i]) / h;
+                for (int j = 0; j < dim; j++) {
+                    double y1 = mY[i][j];
+                    double y2 = mY[i + 1][j];
+                    double t1 = mTangent[i][j];
+                    double t2 = mTangent[i + 1][j];
+                    v[j] = diff(h, x, y1, y2, t1, t2) / h;
+                }
+                break;
+            }
+        }
+        return;
+    }
+
+    /**
+     * Get the slope of the j curve at position t
+     * @param t
+     * @param j
+     * @return
+     */
+    public double getSlope(double t, int j) {
+        final int n = mT.length;
+
+        if (t < mT[0]) {
+            t = mT[0];
+        } else if (t >= mT[n - 1]) {
+            t = mT[n - 1];
+        }
+        for (int i = 0; i < n - 1; i++) {
+            if (t <= mT[i + 1]) {
+                double h = mT[i + 1] - mT[i];
+                double x = (t - mT[i]) / h;
+                double y1 = mY[i][j];
+                double y2 = mY[i + 1][j];
+                double t1 = mTangent[i][j];
+                double t2 = mTangent[i + 1][j];
+                return diff(h, x, y1, y2, t1, t2) / h;
+            }
+        }
+        return 0; // should never reach here
+    }
+
+    public double[] getTimePoints() {
+        return mT;
+    }
+
+    /**
+     * Cubic Hermite spline
+     */
+    private static double interpolate(double h,
+                                      double x,
+                                      double y1,
+                                      double y2,
+                                      double t1,
+                                      double t2) {
+        double x2 = x * x;
+        double x3 = x2 * x;
+        return -2 * x3 * y2 + 3 * x2 * y2 + 2 * x3 * y1 - 3 * x2 * y1 + y1
+                + h * t2 * x3 + h * t1 * x3 - h * t2 * x2 - 2 * h * t1 * x2
+                + h * t1 * x;
+    }
+
+    /**
+     * Cubic Hermite spline slope differentiated
+     */
+    private static double diff(double h, double x, double y1, double y2, double t1, double t2) {
+        double x2 = x * x;
+        return -6 * x2 * y2 + 6 * x * y2 + 6 * x2 * y1 - 6 * x * y1 + 3 * h * t2 * x2
+                + 3 * h * t1 * x2 - 2 * h * t2 * x - 4 * h * t1 * x + h * t1;
+    }
+
+    /**
+     * This builds a monotonic spline to be used as a wave function
+     */
+    public static MonotonicCurveFit buildWave(String configString) {
+        // done this way for efficiency
+        String str = configString;
+        double[] values = new double[str.length() / 2];
+        int start = configString.indexOf('(') + 1;
+        int off1 = configString.indexOf(',', start);
+        int count = 0;
+        while (off1 != -1) {
+            String tmp = configString.substring(start, off1).trim();
+            values[count++] = Double.parseDouble(tmp);
+            off1 = configString.indexOf(',', start = off1 + 1);
+        }
+        off1 = configString.indexOf(')', start);
+        String tmp = configString.substring(start, off1).trim();
+        values[count++] = Double.parseDouble(tmp);
+
+        return buildWave(Arrays.copyOf(values, count));
+    }
+
+    private static MonotonicCurveFit buildWave(double[] values) {
+        int length = values.length * 3 - 2;
+        int len = values.length - 1;
+        double gap = 1.0 / len;
+        double[][] points = new double[length][1];
+        double[] time = new double[length];
+        for (int i = 0; i < values.length; i++) {
+            double v = values[i];
+            points[i + len][0] = v;
+            time[i + len] = i * gap;
+            if (i > 0) {
+                points[i + len * 2][0] = v + 1;
+                time[i + len * 2] = i * gap + 1;
+
+                points[i - 1][0] = v - 1 - gap;
+                time[i - 1] = i * gap + -1 - gap;
+            }
+        }
+
+        return new MonotonicCurveFit(time, points);
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java
new file mode 100644
index 0000000..6ed6548
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 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.internal.widget.remotecompose.core.operations.utilities.easing;
+
+
+/**
+ * This class translates a series of floating point values into a continuous
+ * curve for use in an easing function including quantize functions
+ * it is used with the "spline(0,0.3,0.3,0.5,...0.9,1)" it should start at 0 and end at one 1
+ */
+public class StepCurve extends Easing {
+    private static final boolean DEBUG = false;
+    MonotonicCurveFit mCurveFit;
+
+    public StepCurve(float[] params, int offset, int len) {
+        mCurveFit = genSpline(params, offset, len);
+    }
+
+    private static MonotonicCurveFit genSpline(float[] values, int off, int arrayLen) {
+        int length = arrayLen * 3 - 2;
+        int len = arrayLen - 1;
+        double gap = 1.0 / len;
+        double[][] points = new double[length][1];
+        double[] time = new double[length];
+        for (int i = 0; i < arrayLen; i++) {
+            double v = values[i + off];
+            points[i + len][0] = v;
+            time[i + len] = i * gap;
+            if (i > 0) {
+                points[i + len * 2][0] = v + 1;
+                time[i + len * 2] = i * gap + 1;
+
+                points[i - 1][0] = v - 1 - gap;
+                time[i - 1] = i * gap + -1 - gap;
+            }
+        }
+
+        MonotonicCurveFit ms = new MonotonicCurveFit(time, points);
+
+        return ms;
+    }
+
+    @Override
+    public float getDiff(float x) {
+        return (float) mCurveFit.getSlope(x, 0);
+    }
+
+
+    @Override
+    public float get(float x) {
+        return (float) mCurveFit.getPos(x, 0);
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java
index bcda27a..d1c4d46 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java
@@ -79,6 +79,15 @@
     }
 
     /**
+     * The delay in milliseconds to next repaint -1 = not needed 0 = asap
+     *
+     * @return delay in milliseconds to next repaint or -1
+     */
+    public int needsRepaint() {
+        return mDocument.needsRepaint();
+    }
+
+    /**
      * Returns true if the document can be displayed given this version of the player
      *
      * @param majorVersion the max major version supported by the player
@@ -89,5 +98,10 @@
         return mDocument.canBeDisplayed(majorVersion, minorVersion, capabilities);
     }
 
+    @Override
+    public String toString() {
+        return "Document{\n"
+                + mDocument + '}';
+    }
 }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java
index d0d6e69..ecb68bb 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java
@@ -26,6 +26,7 @@
 import android.graphics.RadialGradient;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.graphics.RuntimeShader;
 import android.graphics.Shader;
 import android.graphics.SweepGradient;
 import android.graphics.Typeface;
@@ -33,6 +34,8 @@
 import com.android.internal.widget.remotecompose.core.PaintContext;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
 import com.android.internal.widget.remotecompose.core.operations.ClipPath;
+import com.android.internal.widget.remotecompose.core.operations.ShaderData;
+import com.android.internal.widget.remotecompose.core.operations.Utils;
 import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
 import com.android.internal.widget.remotecompose.core.operations.paint.PaintChanges;
 
@@ -43,6 +46,7 @@
 public class AndroidPaintContext extends PaintContext {
     Paint mPaint = new Paint();
     Canvas mCanvas;
+    Rect mTmpRect = new Rect(); // use in calculation of bounds
 
     public AndroidPaintContext(RemoteContext context, Canvas canvas) {
         super(context);
@@ -177,6 +181,22 @@
     }
 
     @Override
+    public void getTextBounds(int textId, int start, int end, boolean monospace, float[] bounds) {
+        String str = getText(textId);
+        if (end == -1) {
+            end = str.length();
+        }
+
+        mPaint.getTextBounds(str, start, end, mTmpRect);
+
+        bounds[0] = mTmpRect.left;
+        bounds[1] = mTmpRect.top;
+        bounds[2] = monospace ? (mPaint.measureText(str, start, end) - mTmpRect.left)
+                : mTmpRect.right;
+        bounds[3] = mTmpRect.bottom;
+    }
+
+    @Override
     public void drawTextRun(int textID,
                             int start,
                             int end,
@@ -185,7 +205,16 @@
                             float x,
                             float y,
                             boolean rtl) {
-        String textToPaint = getText(textID).substring(start, end);
+
+        String textToPaint = getText(textID);
+        if (end == -1) {
+            if (start != 0) {
+                textToPaint = textToPaint.substring(start);
+            }
+        } else {
+            textToPaint = textToPaint.substring(start, end);
+        }
+
         mCanvas.drawText(textToPaint, x, y, mPaint);
     }
 
@@ -308,7 +337,7 @@
 
     @Override
     public void applyPaint(PaintBundle mPaintData) {
-        mPaintData.applyPaintChange(new PaintChanges() {
+        mPaintData.applyPaintChange((PaintContext) this, new PaintChanges() {
             @Override
             public void setTextSize(float size) {
                 mPaint.setTextSize(size);
@@ -361,10 +390,8 @@
                     }
                 }
 
-
             }
 
-
             @Override
             public void setStrokeWidth(float width) {
                 mPaint.setStrokeWidth(width);
@@ -386,13 +413,37 @@
             }
 
             @Override
-            public void setShader(int shader, String shaderString) {
-
+            public void setShader(int shaderId) {
+                // TODO this stuff should check the shader creation
+                if (shaderId == 0) {
+                    mPaint.setShader(null);
+                    return;
+                }
+                ShaderData data = getShaderData(shaderId);
+                RuntimeShader shader = new RuntimeShader(getText(data.getShaderTextId()));
+                String[] names = data.getUniformFloatNames();
+                for (int i = 0; i < names.length; i++) {
+                    String name = names[i];
+                    float[] val = data.getUniformFloats(name);
+                    shader.setFloatUniform(name, val);
+                }
+                names = data.getUniformIntegerNames();
+                for (int i = 0; i < names.length; i++) {
+                    String name = names[i];
+                    int[] val = data.getUniformInts(name);
+                    shader.setIntUniform(name, val);
+                }
+                names = data.getUniformBitmapNames();
+                for (int i = 0; i < names.length; i++) {
+                    String name = names[i];
+                    int val = data.getUniformBitmapId(name);
+                }
+                mPaint.setShader(shader);
             }
 
             @Override
             public void setImageFilterQuality(int quality) {
-                System.out.println(">>>>>>>>>>>> ");
+                Utils.log(" quality =" + quality);
             }
 
             @Override
@@ -420,7 +471,6 @@
                 mPaint.setFilterBitmap(filter);
             }
 
-
             @Override
             public void setAntiAlias(boolean aa) {
                 mPaint.setAntiAlias(aa);
@@ -437,7 +487,6 @@
 
                             case PaintBundle.COLOR_FILTER:
                                 mPaint.setColorFilter(null);
-                                System.out.println(">>>>>>>>>>>>> CLEAR!!!!");
                                 break;
                         }
                     }
@@ -446,12 +495,11 @@
                 }
             }
 
-            Shader.TileMode[] mTilesModes = new Shader.TileMode[]{
+            Shader.TileMode[] mTileModes = new Shader.TileMode[]{
                     Shader.TileMode.CLAMP,
                     Shader.TileMode.REPEAT,
                     Shader.TileMode.MIRROR};
 
-
             @Override
             public void setLinearGradient(int[] colors,
                                           float[] stops,
@@ -463,7 +511,7 @@
                 mPaint.setShader(new LinearGradient(startX,
                         startY,
                         endX,
-                        endY, colors, stops, mTilesModes[tileMode]));
+                        endY, colors, stops, mTileModes[tileMode]));
 
             }
 
@@ -475,7 +523,7 @@
                                           float radius,
                                           int tileMode) {
                 mPaint.setShader(new RadialGradient(centerX, centerY, radius,
-                        colors, stops, mTilesModes[tileMode]));
+                        colors, stops, mTileModes[tileMode]));
             }
 
             @Override
@@ -490,7 +538,6 @@
             @Override
             public void setColorFilter(int color, int mode) {
                 PorterDuff.Mode pmode = origamiToPorterDuffMode(mode);
-                System.out.println("setting color filter to " + pmode.name());
                 if (pmode != null) {
                     mPaint.setColorFilter(
                             new PorterDuffColorFilter(color, pmode));
@@ -500,10 +547,10 @@
     }
 
     @Override
-    public void mtrixScale(float scaleX,
-                           float scaleY,
-                           float centerX,
-                           float centerY) {
+    public void matrixScale(float scaleX,
+                            float scaleY,
+                            float centerX,
+                            float centerY) {
         if (Float.isNaN(centerX)) {
             mCanvas.scale(scaleX, scaleY);
         } else {
@@ -556,6 +603,11 @@
         }
     }
 
+    @Override
+    public void reset() {
+        mPaint.reset();
+    }
+
     private Path getPath(int path1Id,
                          int path2Id,
                          float tween,
@@ -599,5 +651,9 @@
     private String getText(int id) {
         return (String) mContext.mRemoteComposeState.getFromId(id);
     }
+
+    private ShaderData getShaderData(int id) {
+        return (ShaderData) mContext.mRemoteComposeState.getFromId(id);
+    }
 }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java
index 270e96f..6e4893b 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java
@@ -20,10 +20,15 @@
 import android.graphics.Canvas;
 
 import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
+import com.android.internal.widget.remotecompose.core.operations.FloatExpression;
+import com.android.internal.widget.remotecompose.core.operations.ShaderData;
+
+import java.util.HashMap;
 
 /**
  * An implementation of Context for Android.
- *
+ * <p>
  * This is used to play the RemoteCompose operations on Android.
  */
 class AndroidRemoteContext extends RemoteContext {
@@ -33,6 +38,7 @@
             mPaintContext = new AndroidPaintContext(this, canvas);
         } else {
             // need to make sure to update the canvas for the current one
+            mPaintContext.reset();
             ((AndroidPaintContext) mPaintContext).setCanvas(canvas);
         }
         mWidth = canvas.getWidth();
@@ -50,13 +56,32 @@
         }
     }
 
+    static class VarName {
+        String mName;
+        int mId;
+        int mType;
+
+        VarName(String name, int id, int type) {
+            mName = name;
+            mId = id;
+            mType = type;
+        }
+    }
+
+    HashMap<String, VarName> mVarNameHashMap = new HashMap<>();
+
+    @Override
+    public void loadVariableName(String varName, int varId, int varType) {
+        mVarNameHashMap.put(varName, new VarName(varName, varId, varType));
+    }
+
     /**
      * Decode a byte array into an image and cache it using the given imageId
      *
-     * @oaram imageId the id of the image
-     * @param width with of image to be loaded
+     * @param width  with of image to be loaded
      * @param height height of image to be loaded
      * @param bitmap a byte array containing the image information
+     * @oaram imageId the id of the image
      */
     @Override
     public void loadBitmap(int imageId, int width, int height, byte[] bitmap) {
@@ -70,14 +95,66 @@
     public void loadText(int id, String text) {
         if (!mRemoteComposeState.containsId(id)) {
             mRemoteComposeState.cache(id, text);
+        } else {
+            mRemoteComposeState.update(id, text);
         }
     }
 
+    @Override
+    public String getText(int id) {
+        return (String) mRemoteComposeState.getFromId(id);
+    }
+
+    @Override
+    public void loadFloat(int id, float value) {
+        mRemoteComposeState.updateFloat(id, value);
+    }
+
+
+    @Override
+    public void loadColor(int id, int color) {
+        mRemoteComposeState.updateColor(id, color);
+    }
+
+    @Override
+    public void loadAnimatedFloat(int id, FloatExpression animatedFloat) {
+        mRemoteComposeState.cache(id, animatedFloat);
+    }
+
+    @Override
+    public void loadShader(int id, ShaderData value) {
+        mRemoteComposeState.cache(id, value);
+    }
+
+    @Override
+    public float getFloat(int id) {
+        return (float) mRemoteComposeState.getFloat(id);
+    }
+
+    @Override
+    public int getColor(int id) {
+        return mRemoteComposeState.getColor(id);
+    }
+
+    @Override
+    public void listensTo(int id, VariableSupport variableSupport) {
+        mRemoteComposeState.listenToVar(id, variableSupport);
+    }
+
+    @Override
+    public int updateOps() {
+        return mRemoteComposeState.getOpsToUpdate(this);
+    }
+
+    @Override
+    public ShaderData getShader(int id) {
+        return (ShaderData) mRemoteComposeState.getFromId(id);
+    }
+
     ///////////////////////////////////////////////////////////////////////////////////////////////
     // Click handling
     ///////////////////////////////////////////////////////////////////////////////////////////////
 
-
     @Override
     public void addClickArea(int id,
                              int contentDescriptionId,
@@ -87,7 +164,7 @@
                              float bottom,
                              int metadataId) {
         String contentDescription = (String) mRemoteComposeState.getFromId(contentDescriptionId);
-        String  metadata = (String) mRemoteComposeState.getFromId(metadataId);
+        String metadata = (String) mRemoteComposeState.getFromId(metadataId);
         mDocument.addClickArea(id, contentDescription, left, top, right, bottom, metadata);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/ClickAreaView.java b/core/java/com/android/internal/widget/remotecompose/player/platform/ClickAreaView.java
index 672dae3..329178a 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/ClickAreaView.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/ClickAreaView.java
@@ -20,7 +20,6 @@
 import android.graphics.Paint;
 import android.view.View;
 
-
 /**
  * Implementation for the click handling
  */
@@ -40,7 +39,6 @@
         setContentDescription(contentDescription);
     }
 
-
     public void setDebug(boolean value) {
         if (mDebug != value) {
             mDebug = value;
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
index a3bb73e..97d23c8 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
@@ -85,6 +85,7 @@
         mDocument.initializeContext(mARContext);
         setContentDescription(mDocument.getDocument().getContentDescription());
         requestLayout();
+        invalidate();
     }
 
     AndroidRemoteContext mARContext = new AndroidRemoteContext();
@@ -119,8 +120,7 @@
         removeAllViews();
     }
 
-
-    public  interface ClickCallbacks {
+    public interface ClickCallbacks {
         void click(int id, String metadata);
     }
 
@@ -213,6 +213,9 @@
         setMeasuredDimension(w, h);
     }
 
+    private int mCount;
+    private long mTime = System.nanoTime();
+
     @Override
     protected void onDraw(Canvas canvas) {
         super.onDraw(canvas);
@@ -224,6 +227,17 @@
         mARContext.mWidth = getWidth();
         mARContext.mHeight = getHeight();
         mDocument.paint(mARContext, mTheme);
+        if (mDebug) {
+            mCount++;
+            if (System.nanoTime() - mTime > 1000000000L) {
+                System.out.println(" count " + mCount + " fps");
+                mCount = 0;
+                mTime = System.nanoTime();
+            }
+        }
+        if (mDocument.needsRepaint() > 0) {
+            invalidate();
+        }
     }
 
 }