Merge "add suport for integer operations" into main
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 fc8668e..4b8dbf6 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Operations.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
@@ -37,6 +37,7 @@
 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.IntegerExpression;
 import com.android.internal.widget.remotecompose.core.operations.MatrixRestore;
 import com.android.internal.widget.remotecompose.core.operations.MatrixRotate;
 import com.android.internal.widget.remotecompose.core.operations.MatrixSave;
@@ -54,6 +55,8 @@
 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;
+import com.android.internal.widget.remotecompose.core.types.BooleanConstant;
+import com.android.internal.widget.remotecompose.core.types.IntegerConstant;
 
 /**
  * List of operations supported in a RemoteCompose document
@@ -109,6 +112,9 @@
     public static final int TEXT_MERGE = 136;
     public static final int NAMED_VARIABLE = 137;
     public static final int COLOR_CONSTANT = 138;
+    public static final int DATA_INT = 140;
+    public static final int DATA_BOOLEAN = 143;
+    public static final int INTEGER_EXPRESSION = 144;
 
     /////////////////////////////////////////======================
     public static IntMap<CompanionOperation> map = new IntMap<>();
@@ -153,6 +159,9 @@
         map.put(TEXT_MERGE, TextMerge.COMPANION);
         map.put(NAMED_VARIABLE, NamedVariable.COMPANION);
         map.put(COLOR_CONSTANT, ColorConstant.COMPANION);
+        map.put(DATA_INT, IntegerConstant.COMPANION);
+        map.put(INTEGER_EXPRESSION, IntegerExpression.COMPANION);
+        map.put(DATA_BOOLEAN, BooleanConstant.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 ecd0efc..6d8a442 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java
@@ -59,6 +59,16 @@
 
     public abstract void drawRect(float left, float top, float right, float bottom);
 
+    /**
+     * this caches the paint to a paint stack
+     */
+    public abstract void  savePaint();
+
+    /**
+     * This restores the paint form the paint stack
+     */
+    public abstract void  restorePaint();
+
     public abstract void drawRoundRect(float left,
                                        float top,
                                        float right,
@@ -119,6 +129,10 @@
                                        float start,
                                        float stop);
 
+    /**
+     * This applies changes to the current paint
+     * @param mPaintData the list of changes
+     */
     public abstract void applyPaint(PaintBundle mPaintData);
 
     /**
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 d462c7d..f5f155e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
@@ -37,6 +37,7 @@
 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.IntegerExpression;
 import com.android.internal.widget.remotecompose.core.operations.MatrixRestore;
 import com.android.internal.widget.remotecompose.core.operations.MatrixRotate;
 import com.android.internal.widget.remotecompose.core.operations.MatrixSave;
@@ -55,6 +56,7 @@
 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 com.android.internal.widget.remotecompose.core.types.IntegerConstant;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -876,6 +878,27 @@
         return Utils.asNan(id);
     }
 
+
+    /**
+     * Add a Integer return an id number pointing to that float.
+     * @param value
+     * @return
+     */
+    public int addInteger(int value) {
+        int id = mRemoteComposeState.cacheInteger(value);
+        IntegerConstant.COMPANION.apply(mBuffer, id, value);
+        return id;
+    }
+
+    /**
+     * Add a IntegerId as float ID.
+     * @param id id to be converted
+     * @return
+     */
+    public float asFloatId(int id) {
+        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
@@ -901,6 +924,18 @@
     }
 
     /**
+     * Add and integer expression
+     * @param mask defines which elements are operators or variables
+     * @param value array of values to calculate maximum 32
+     * @return
+     */
+    public int addIntegerExpression(int mask, int[] value) {
+        int id = mRemoteComposeState.cache(value);
+        IntegerExpression.COMPANION.apply(mBuffer, id, mask, value);
+        return  id;
+    }
+
+    /**
      * Add a simple color
      * @param color
      * @return id that represents that color
@@ -1038,5 +1073,6 @@
         NamedVariable.COMPANION.apply(mBuffer, id,
                 NamedVariable.COLOR_TYPE, name);
     }
+
 }
 
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 bfe67c8..6b06a54 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
@@ -21,6 +21,8 @@
 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.IntFloatMap;
+import com.android.internal.widget.remotecompose.core.operations.utilities.IntIntMap;
 import com.android.internal.widget.remotecompose.core.operations.utilities.IntMap;
 
 import java.util.ArrayList;
@@ -37,16 +39,12 @@
     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_COLORS]; // efficient cache
+    private final IntFloatMap mFloatMap = new IntFloatMap(); // efficient cache
+    private final IntIntMap mIntegerMap = new IntIntMap(); // efficient cache
+    private final IntIntMap mColorMap = new IntIntMap(); // efficient cache
     private final boolean[] mColorOverride = new boolean[MAX_COLORS];
     private int mNextId = START_ID;
 
-    {
-        for (int i = 0; i < mFloatMap.length; i++) {
-            mFloatMap[i] = Float.NaN;
-        }
-    }
 
     /**
      * Get Object based on id. The system will cache things like bitmaps
@@ -113,7 +111,18 @@
      */
     public int cacheFloat(float item) {
         int id = nextId();
-        mFloatMap[id] = item;
+        mFloatMap.put(id, item);
+        mIntegerMap.put(id, (int) item);
+        return id;
+    }
+
+    /**
+     * Insert an item in the cache
+     */
+    public int cacheInteger(int item) {
+        int id = nextId();
+        mIntegerMap.put(id, item);
+        mFloatMap.put(id, item);
         return id;
     }
 
@@ -121,21 +130,43 @@
      * Insert an item in the cache
      */
     public void cacheFloat(int id, float item) {
-        mFloatMap[id] = item;
+        mFloatMap.put(id, item);
     }
 
     /**
-     * Insert an item in the cache
+     * Insert an float item in the cache
      */
     public void updateFloat(int id, float item) {
-        mFloatMap[id] = item;
+        mFloatMap.put(id, item);
+        mIntegerMap.put(id, (int) item);
     }
 
     /**
-     * get float
+     * Insert an integer item in the cache
+     */
+    public void updateInteger(int id, int item) {
+        mFloatMap.put(id, item);
+        mIntegerMap.put(id, item);
+    }
+
+    /**
+     * get a float from the float cache
+     *
+     * @param id of the float value
+     * @return the float value
      */
     public float getFloat(int id) {
-        return mFloatMap[id];
+        return mFloatMap.get(id);
+    }
+
+    /**
+     * get an integer from the cache
+     *
+     * @param id of the integer value
+     * @return the integer
+     */
+    public int getInteger(int id) {
+        return mIntegerMap.get(id);
     }
 
     /**
@@ -145,11 +176,12 @@
      * @return
      */
     public int getColor(int id) {
-        return mColorMap[id];
+        return mColorMap.get(id);
     }
 
     /**
      * Modify the color at id.
+     *
      * @param id
      * @param color
      */
@@ -157,7 +189,7 @@
         if (mColorOverride[id]) {
             return;
         }
-        mColorMap[id] = color;
+        mColorMap.put(id, color);
     }
 
     /**
@@ -169,7 +201,7 @@
      */
     public void overrideColor(int id, int color) {
         mColorOverride[id] = true;
-        mColorMap[id] = color;
+        mColorMap.put(id, color);
     }
 
     /**
@@ -205,6 +237,7 @@
 
     /**
      * Get the next available id
+     *
      * @return
      */
     public int nextId() {
@@ -213,6 +246,7 @@
 
     /**
      * Set the next id
+     *
      * @param id
      */
     public void setNextId(int id) {
@@ -234,6 +268,7 @@
 
     /**
      * Commands that listen to variables add themselves.
+     *
      * @param id
      * @param variableSupport
      */
@@ -243,6 +278,7 @@
 
     /**
      * List of Commands that need to be updated
+     *
      * @param context
      * @return
      */
@@ -264,6 +300,7 @@
 
     /**
      * Set the width of the overall document on screen.
+     *
      * @param width
      */
     public void setWindowWidth(float width) {
@@ -272,6 +309,7 @@
 
     /**
      * Set the width of the overall document on screen.
+     *
      * @param height
      */
     public void setWindowHeight(float 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 32027d8..41eeb5b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
@@ -39,6 +39,7 @@
 
     public float mWidth = 0f;
     public float mHeight = 0f;
+    private float mAnimationTime;
 
     /**
      * Load a path under an id.
@@ -65,11 +66,20 @@
     public abstract void loadColor(int id, int color);
 
     /**
+     * Set the animation time allowing the creator to control animation rates
+     * @param time
+     */
+    public void setAnimationTime(float time) {
+        mAnimationTime = time;
+    }
+
+    /**
      * 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;
+        mAnimationTime = (System.nanoTime() - mStart) * 1E-9f; // Eliminate
+        return mAnimationTime;
     }
 
     /**
@@ -213,6 +223,13 @@
     public abstract void loadFloat(int id, float value);
 
     /**
+     * Load a float
+     * @param id
+     * @param value
+     */
+    public abstract void loadInteger(int id, int value);
+
+    /**
      * Load an animated float associated with an id
      * Todo: Remove?
      * @param id
@@ -235,6 +252,13 @@
     public abstract float getFloat(int id);
 
     /**
+     * Get a float given an id
+     * @param id
+     * @return
+     */
+    public abstract int getInteger(int id);
+
+    /**
      * Get the color given and ID
      * @param id
      * @return
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
index 56b2f1f..5a4a9f3 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java
@@ -63,23 +63,23 @@
 
     @Override
     public void updateVariables(RemoteContext context) {
-        mV1 = (Float.isNaN(mValue1))
+        mV1 = (Utils.isVariable(mValue1))
                 ? context.getFloat(Utils.idFromNan(mValue1)) : mValue1;
-        mV2 = (Float.isNaN(mValue2))
+        mV2 = (Utils.isVariable(mValue2))
                 ? context.getFloat(Utils.idFromNan(mValue2)) : mValue2;
-        mV3 = (Float.isNaN(mValue3))
+        mV3 = (Utils.isVariable(mValue3))
                 ? context.getFloat(Utils.idFromNan(mValue3)) : mValue3;
     }
 
     @Override
     public void registerListening(RemoteContext context) {
-        if (Float.isNaN(mValue1)) {
+        if (Utils.isVariable(mValue1)) {
             context.listensTo(Utils.idFromNan(mValue1), this);
         }
-        if (Float.isNaN(mValue2)) {
+        if (Utils.isVariable(mValue2)) {
             context.listensTo(Utils.idFromNan(mValue2), this);
         }
-        if (Float.isNaN(mValue3)) {
+        if (Utils.isVariable(mValue3)) {
             context.listensTo(Utils.idFromNan(mValue3), this);
         }
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextRun.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextRun.java
deleted file mode 100644
index a099252..0000000
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextRun.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * 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 DrawTextRun 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 DrawTextRun(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 "";
-    }
-
-    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();
-            DrawTextRun op = new DrawTextRun(text, start, end, contextStart, contextEnd, x, y, rtl);
-
-            operations.add(op);
-        }
-
-        @Override
-        public String name() {
-            return "";
-        }
-
-        @Override
-        public int id() {
-            return 0;
-        }
-
-        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/IntegerExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java
new file mode 100644
index 0000000..d52df5d
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java
@@ -0,0 +1,177 @@
+/*
+ * 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.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.IntegerExpressionEvaluator;
+
+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 IntegerExpression implements Operation, VariableSupport {
+    public int mId;
+    private int mMask;
+    private int mPreMask;
+    public int[] mSrcValue;
+    public int[] mPreCalcValue;
+    private float mLastChange = Float.NaN;
+    public static final Companion COMPANION = new Companion();
+    public static final int MAX_STRING_SIZE = 4000;
+    IntegerExpressionEvaluator mExp = new IntegerExpressionEvaluator();
+
+    public IntegerExpression(int id, int mask, int[] value) {
+        this.mId = id;
+        this.mMask = mask;
+        this.mSrcValue = value;
+    }
+
+    @Override
+    public void updateVariables(RemoteContext context) {
+        if (mPreCalcValue == null || mPreCalcValue.length != mSrcValue.length) {
+            mPreCalcValue = new int[mSrcValue.length];
+        }
+        mPreMask = mMask;
+        for (int i = 0; i < mSrcValue.length; i++) {
+            if (isId(mMask, i, mSrcValue[i])) {
+                mPreMask &= ~(0x1 << i);
+                mPreCalcValue[i] = context.getInteger(mSrcValue[i]);
+            } else {
+                mPreCalcValue[i] = mSrcValue[i];
+            }
+        }
+    }
+
+
+    @Override
+    public void registerListening(RemoteContext context) {
+        for (int i = 0; i < mSrcValue.length; i++) {
+            if (isId(mMask, i, mSrcValue[i])) {
+                context.listensTo(mSrcValue[i], this);
+            }
+        }
+    }
+
+    @Override
+    public void apply(RemoteContext context) {
+        updateVariables(context);
+        float t = context.getAnimationTime();
+        if (Float.isNaN(mLastChange)) {
+            mLastChange = t;
+        }
+        int v = mExp.eval(mPreMask, Arrays.copyOf(mPreCalcValue, mPreCalcValue.length));
+        context.loadInteger(mId, v);
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        COMPANION.apply(buffer, mId, mMask, mSrcValue);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder s = new StringBuilder();
+        for (int i = 0; i < mPreCalcValue.length; i++) {
+            if (i != 0) {
+                s.append(" ");
+            }
+            if (IntegerExpressionEvaluator.isOperation(mMask, i)) {
+                if (isId(mMask, i, mSrcValue[i])) {
+                    s.append("[" + mSrcValue[i] + "]");
+                } else {
+                    s.append(IntegerExpressionEvaluator.toMathName(mPreCalcValue[i]));
+                }
+            } else {
+                s.append(mSrcValue[i]);
+            }
+        }
+        return "IntegerExpression[" + mId + "] = (" + s + ")";
+    }
+
+    public static class Companion implements CompanionOperation {
+        private Companion() {
+        }
+
+        @Override
+        public String name() {
+            return "FloatExpression";
+        }
+
+        @Override
+        public int id() {
+            return Operations.INTEGER_EXPRESSION;
+        }
+
+        /**
+         * Writes out the operation to the buffer
+         *
+         * @param buffer
+         * @param id
+         * @param mask
+         * @param value
+         */
+        public void apply(WireBuffer buffer, int id, int mask, int[] value) {
+            buffer.start(Operations.INTEGER_EXPRESSION);
+            buffer.writeInt(id);
+            buffer.writeInt(mask);
+            buffer.writeInt(value.length);
+            for (int i = 0; i < value.length; i++) {
+                buffer.writeInt(value[i]);
+            }
+        }
+
+        @Override
+        public void read(WireBuffer buffer, List<Operation> operations) {
+            int id = buffer.readInt();
+            int mask = buffer.readInt();
+            int len = buffer.readInt();
+
+            int[] values = new int[len];
+            for (int i = 0; i < values.length; i++) {
+                values[i] = buffer.readInt();
+            }
+
+            operations.add(new IntegerExpression(id, mask, values));
+        }
+    }
+
+    @Override
+    public String deepToString(String indent) {
+        return indent + toString();
+    }
+
+    /**
+     * given the "i" position in the mask is this an ID
+     * @param mask 32 bit mask used for defining numbers vs other
+     * @param i the bit in question
+     * @param value the value
+     * @return true if this is an ID
+     */
+    public static boolean isId(int mask, int i, int value) {
+        return ((1 << i) & mask) != 0 && value < IntegerExpressionEvaluator.OFFSET;
+    }
+}
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 fcb3bfa..e9b0c3b 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
@@ -40,7 +40,7 @@
      * @param n
      * @return
      */
-    static String trimString(String str, int n) {
+    public static String trimString(String str, int n) {
         if (str.length() > n) {
             str = str.substring(0, n - 3) + "...";
         }
@@ -55,6 +55,9 @@
      */
     public static String floatToString(float idvalue, float value) {
         if (Float.isNaN(idvalue)) {
+            if (idFromNan(value) == 0) {
+                return "NaN";
+            }
             return "[" + idFromNan(idvalue) + "]" + floatToString(value);
         }
         return floatToString(value);
@@ -67,6 +70,9 @@
      */
     public static String floatToString(float value) {
         if (Float.isNaN(value)) {
+            if (idFromNan(value) == 0) {
+                return "NaN";
+            }
             return "[" + idFromNan(value) + "]";
         }
         return Float.toString(value);
@@ -107,6 +113,7 @@
     public static boolean isVariable(float v) {
         if (Float.isNaN(v)) {
             int id = idFromNan(v);
+            if (id == 0) return false;
             return id > 40 || id < 10;
         }
         return false;
@@ -222,6 +229,4 @@
         }
         return 0;
     }
-
-
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/Painter.java b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/Painter.java
new file mode 100644
index 0000000..ada3757
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/Painter.java
@@ -0,0 +1,260 @@
+/*
+ * 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.paint;
+
+
+/**
+ * Provides a Builder pattern for a PaintBundle
+ */
+class Painter {
+    PaintBundle mPaint;
+
+    /**
+     * Write the paint to the buffer
+     */
+    public PaintBundle commit() {
+        return mPaint;
+    }
+
+    public Painter setAntiAlias(boolean aa) {
+        mPaint.setAntiAlias(aa);
+        return this;
+    }
+
+    public Painter setColor(int color) {
+        mPaint.setColor(color);
+        return this;
+    }
+
+    public Painter setColorId(int colorId) {
+        mPaint.setColorId(colorId);
+        return this;
+    }
+
+    /**
+     * Set the paint's Join.
+     *
+     * @param join set the paint's Join, used whenever the paint's style is
+     *             Stroke or StrokeAndFill.
+     */
+    public Painter setStrokeJoin(int join) {
+        mPaint.setStrokeJoin(join);
+        return this;
+    }
+
+    /**
+     * Set the width for stroking. Pass 0 to stroke in hairline mode.
+     * Hairlines always draws a single
+     * pixel independent of the canvas's matrix.
+     *
+     * @param width set the paint's stroke width, used whenever the paint's
+     *             style is Stroke or StrokeAndFill.
+     */
+    public Painter setStrokeWidth(float width) {
+        mPaint.setStrokeWidth(width);
+        return this;
+    }
+
+    /**
+     * Set the paint's style, used for controlling how primitives' geometries
+     * are interpreted (except for drawBitmap, which always assumes Fill).
+     *
+     * @param style The new style to set in the paint
+     */
+    public Painter setStyle(int style) {
+        mPaint.setStyle(style);
+        return this;
+    }
+
+    /**
+     * Set the paint's Cap.
+     *
+     * @param cap set the paint's line cap style, used whenever the paint's
+     *           style is Stroke or StrokeAndFill.
+     */
+    public Painter setStrokeCap(int cap) {
+        mPaint.setStrokeCap(cap);
+        return this;
+    }
+
+    /**
+     * Set the paint's stroke miter value. This is used to control the behavior
+     * of miter joins when the joins angle is sharp. This value must be >= 0.
+     *
+     * @param miter set the miter limit on the paint, used whenever the paint's
+     *             style is Stroke or StrokeAndFill.
+     */
+    public Painter setStrokeMiter(float miter) {
+        mPaint.setStrokeMiter(miter);
+        return this;
+    }
+
+    /**
+     * Helper to setColor(), that only assigns the color's alpha value,
+     * leaving its r,g,b values unchanged. Results are undefined if the alpha
+     * value is outside of the range [0..1.0]
+     *
+     * @param alpha set the alpha component [0..1.0] of the paint's color.
+     */
+    public Painter setAlpha(float alpha) {
+        mPaint.setAlpha((alpha > 2) ? alpha / 255f : alpha);
+        return this;
+    }
+
+    /**
+     * Create a color filter that uses the specified color and Porter-Duff mode.
+     *
+     * @param color The ARGB source color used with the specified Porter-Duff
+     *             mode
+     * @param mode  The porter-duff mode that is applied
+     */
+    public Painter setPorterDuffColorFilter(int color, int mode) {
+        mPaint.setColorFilter(color, mode);
+        return this;
+    }
+
+    /**
+     * sets a shader that draws a linear gradient along a line.
+     *
+     * @param startX    The x-coordinate for the start of the gradient line
+     * @param startY    The y-coordinate for the start of the gradient line
+     * @param endX      The x-coordinate for the end of the gradient line
+     * @param endY      The y-coordinate for the end of the gradient line
+     * @param colors    The sRGB colors to be distributed along the gradient
+     *                  line
+     * @param positions May be null. The relative positions [0..1] of each
+     *                 corresponding color in the colors array. If this is null,
+     *                 the colors are distributed evenly along the gradient
+     *                 line.
+     * @param tileMode  The Shader tiling mode
+     */
+    public Painter setLinearGradient(
+            float startX,
+            float startY,
+            float endX,
+            float endY,
+            int[] colors,
+            float[] positions,
+            int tileMode
+    ) {
+        mPaint.setLinearGradient(colors, positions, startX,
+                startY, endX, endY, tileMode);
+        return this;
+    }
+
+    /**
+     * Sets a shader that draws a radial gradient given the center and radius.
+     *
+     * @param centerX   The x-coordinate of the center of the radius
+     * @param centerY   The y-coordinate of the center of the radius
+     * @param radius    Must be positive. The radius of the circle for this
+     *                  gradient.
+     * @param colors    The sRGB colors to be distributed between the center
+     *                  and edge of the circle
+     * @param positions May be <code>null</code>. Valid values are between
+     *                  <code>0.0f</code> and
+     *                  <code>1.0f</code>. The relative position of each
+     *                  corresponding color in the colors array. If
+     *                  <code>null</code>, colors are distributed evenly
+     *                  between the center and edge of the circle.
+     * @param tileMode  The Shader tiling mode
+     */
+    public Painter setRadialGradient(
+            float centerX,
+            float centerY,
+            float radius,
+            int[] colors,
+            float[] positions,
+            int tileMode
+    ) {
+        mPaint.setRadialGradient(colors, positions, centerX,
+                centerY, radius, tileMode);
+        return this;
+    }
+
+    /**
+     * Set a shader that draws a sweep gradient around a center point.
+     *
+     * @param centerX   The x-coordinate of the center
+     * @param centerY   The y-coordinate of the center
+     * @param colors    The sRGB colors to be distributed between around the
+     *                  center. There must be at least 2 colors in the array.
+     * @param positions May be NULL. The relative position of each corresponding
+     *                 color in the colors array, beginning with 0 and ending
+     *                 with 1.0. If the values are not monotonic, the drawing
+     *                  may produce unexpected results. If positions is NULL,
+     *                  then the colors are automatically spaced evenly.
+     */
+    public Painter setSweepGradient(
+            float centerX,
+            float centerY,
+            int[] colors,
+            float[] positions
+    ) {
+        mPaint.setSweepGradient(colors, positions, centerX, centerY);
+        return this;
+    }
+
+    /**
+     * Set the paint's text size. This value must be > 0
+     *
+     * @param size set the paint's text size in pixel units.
+     */
+    public Painter setTextSize(float size) {
+        mPaint.setTextSize(size);
+        return this;
+    }
+
+    /**
+     * sets a typeface object that best matches the specified existing
+     * typeface and the specified weight and italic style
+     *
+     * <p>Below are numerical values and corresponding common weight names.</p>
+     * <table> <thead>
+     * <tr><th>Value</th><th>Common weight name</th></tr> </thead> <tbody>
+     * <tr><td>100</td><td>Thin</td></tr>
+     * <tr><td>200</td><td>Extra Light</td></tr>
+     * <tr><td>300</td><td>Light</td></tr>
+     * <tr><td>400</td><td>Normal</td></tr>
+     * <tr><td>500</td><td>Medium</td></tr>
+     * <tr><td>600</td><td>Semi Bold</td></tr>
+     * <tr><td>700</td><td>Bold</td></tr>
+     * <tr><td>800</td><td>Extra Bold</td></tr>
+     * <tr><td>900</td><td>Black</td></tr> </tbody> </table>
+     *
+     * @param fontType 0 = default 1 = sans serif 2 = serif 3 = monospace
+     * @param weight   The desired weight to be drawn.
+     * @param italic   {@code true} if italic style is desired to be drawn.
+     *                            Otherwise, {@code false}
+     */
+    public Painter setTypeface(int fontType, int weight, boolean italic) {
+        mPaint.setTextStyle(fontType, weight, italic);
+        return this;
+    }
+
+
+    public Painter setFilterBitmap(boolean filter) {
+        mPaint.setFilterBitmap(filter);
+        return this;
+    }
+
+
+    public Painter setShader(int id) {
+        mPaint.setShader(id);
+        return this;
+    }
+
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntFloatMap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntFloatMap.java
new file mode 100644
index 0000000..23c3ec5
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntFloatMap.java
@@ -0,0 +1,140 @@
+/*
+ * 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 java.util.Arrays;
+
+public class IntFloatMap {
+
+    private static final int DEFAULT_CAPACITY = 16;
+    private static final float LOAD_FACTOR = 0.75f;
+    private static final int NOT_PRESENT = Integer.MIN_VALUE;
+    private int[] mKeys;
+    private float[] mValues;
+    int mSize;
+
+    public IntFloatMap() {
+        mKeys = new int[DEFAULT_CAPACITY];
+        Arrays.fill(mKeys, NOT_PRESENT);
+        mValues = new float[DEFAULT_CAPACITY];
+    }
+
+    /**
+     * clear the map
+     */
+    public void clear() {
+        Arrays.fill(mKeys, NOT_PRESENT);
+        Arrays.fill(mValues, Float.NaN); // not strictly necessary but defensive
+        mSize = 0;
+    }
+
+    /**
+     * is the key contained in map
+     *
+     * @param key the key to check
+     * @return true if the map contains the key
+     */
+    public boolean contains(int key) {
+        return findKey(key) != -1;
+    }
+
+    /**
+     * Put a item in the map
+     *
+     * @param key   item'values key
+     * @param value item's value
+     * @return old value if exist
+     */
+    public float put(int key, float value) {
+        if (key == NOT_PRESENT) {
+            throw new IllegalArgumentException("Key cannot be NOT_PRESENT");
+        }
+        if (mSize > mKeys.length * LOAD_FACTOR) {
+            resize();
+        }
+        return insert(key, value);
+    }
+
+    /**
+     * get an element given the key
+     *
+     * @param key the key to fetch
+     * @return the value
+     */
+    public float get(int key) {
+        int index = findKey(key);
+        if (index == -1) {
+            return 0;
+        } else
+            return mValues[index];
+    }
+
+    /**
+     * how many elements in the map
+     *
+     * @return number of elements
+     */
+    public int size() {
+        return mSize;
+    }
+
+    private float insert(int key, float value) {
+        int index = hash(key) % mKeys.length;
+        while (mKeys[index] != NOT_PRESENT && mKeys[index] != key) {
+            index = (index + 1) % mKeys.length;
+        }
+        float oldValue = 0;
+        if (mKeys[index] == NOT_PRESENT) {
+            mSize++;
+        } else {
+            oldValue = mValues[index];
+        }
+        mKeys[index] = key;
+        mValues[index] = value;
+        return oldValue;
+    }
+
+    private int findKey(int key) {
+        int index = hash(key) % mKeys.length;
+        while (mKeys[index] != NOT_PRESENT) {
+            if (mKeys[index] == key) {
+                return index;
+            }
+            index = (index + 1) % mKeys.length;
+        }
+        return -1;
+    }
+
+    private int hash(int key) {
+        return key;
+    }
+
+    private void resize() {
+        int[] oldKeys = mKeys;
+        float[] oldValues = mValues;
+        mKeys = new int[(oldKeys.length * 2)];
+        for (int i = 0; i < mKeys.length; i++) {
+            mKeys[i] = NOT_PRESENT;
+        }
+        mValues = new float[oldKeys.length * 2];
+        mSize = 0;
+        for (int i = 0; i < oldKeys.length; i++) {
+            if (oldKeys[i] != NOT_PRESENT) {
+                put(oldKeys[i], oldValues[i]);
+            }
+        }
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntIntMap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntIntMap.java
new file mode 100644
index 0000000..221014c
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntIntMap.java
@@ -0,0 +1,139 @@
+/*
+ * 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 java.util.Arrays;
+
+public class IntIntMap {
+    private static final int DEFAULT_CAPACITY = 16;
+    private static final float LOAD_FACTOR = 0.75f;
+    private static final int NOT_PRESENT = Integer.MIN_VALUE;
+    private int[] mKeys;
+    private int[] mValues;
+    int mSize;
+
+    public IntIntMap() {
+        mKeys = new int[DEFAULT_CAPACITY];
+        Arrays.fill(mKeys, NOT_PRESENT);
+        mValues = new int[DEFAULT_CAPACITY];
+    }
+
+    /**
+     * clear the map
+     */
+    public void clear() {
+        Arrays.fill(mKeys, NOT_PRESENT);
+        Arrays.fill(mValues, 0);
+        mSize = 0;
+    }
+
+    /**
+     * is the key contained in map
+     *
+     * @param key the key to check
+     * @return true if the map contains the key
+     */
+    public boolean contains(int key) {
+        return findKey(key) != -1;
+    }
+
+    /**
+     * Put a item in the map
+     *
+     * @param key   item'values key
+     * @param value item's value
+     * @return old value if exist
+     */
+    public int put(int key, int value) {
+        if (key == NOT_PRESENT) {
+            throw new IllegalArgumentException("Key cannot be NOT_PRESENT");
+        }
+        if (mSize > mKeys.length * LOAD_FACTOR) {
+            resize();
+        }
+        return insert(key, value);
+    }
+
+    /**
+     * get an element given the key
+     *
+     * @param key the key to fetch
+     * @return the value
+     */
+    public int get(int key) {
+        int index = findKey(key);
+        if (index == -1) {
+            return 0;
+        } else
+            return mValues[index];
+    }
+
+    /**
+     * how many elements in the map
+     *
+     * @return number of elements
+     */
+    public int size() {
+        return mSize;
+    }
+
+    private int insert(int key, int value) {
+        int index = hash(key) % mKeys.length;
+        while (mKeys[index] != NOT_PRESENT && mKeys[index] != key) {
+            index = (index + 1) % mKeys.length;
+        }
+        int oldValue = 0;
+        if (mKeys[index] == NOT_PRESENT) {
+            mSize++;
+        } else {
+            oldValue = mValues[index];
+        }
+        mKeys[index] = key;
+        mValues[index] = value;
+        return oldValue;
+    }
+
+    private int findKey(int key) {
+        int index = hash(key) % mKeys.length;
+        while (mKeys[index] != NOT_PRESENT) {
+            if (mKeys[index] == key) {
+                return index;
+            }
+            index = (index + 1) % mKeys.length;
+        }
+        return -1;
+    }
+
+    private int hash(int key) {
+        return key;
+    }
+
+    private void resize() {
+        int[] oldKeys = mKeys;
+        int[] oldValues = mValues;
+        mKeys = new int[(oldKeys.length * 2)];
+        for (int i = 0; i < mKeys.length; i++) {
+            mKeys[i] = NOT_PRESENT;
+        }
+        mValues = new int[oldKeys.length * 2];
+        mSize = 0;
+        for (int i = 0; i < oldKeys.length; i++) {
+            if (oldKeys[i] != NOT_PRESENT) {
+                put(oldKeys[i], oldValues[i]);
+            }
+        }
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntegerExpressionEvaluator.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntegerExpressionEvaluator.java
new file mode 100644
index 0000000..4c1389c
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntegerExpressionEvaluator.java
@@ -0,0 +1,429 @@
+/*
+ * 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;
+
+/**
+ * High performance Integer expression evaluator
+ */
+public class IntegerExpressionEvaluator {
+    static IntMap<String> sNames = new IntMap<>();
+    public static final int OFFSET = 0x10000;
+    // add, sub, mul,div,mod,min,max, shl, shr, ushr, OR, AND , XOR, COPY_SIGN
+    public static final int I_ADD = OFFSET + 1;
+    public static final int I_SUB = OFFSET + 2;
+    public static final int I_MUL = OFFSET + 3;
+    public static final int I_DIV = OFFSET + 4;
+    public static final int I_MOD = OFFSET + 5;
+    public static final int I_SHL = OFFSET + 6;
+    public static final int I_SHR = OFFSET + 7;
+    public static final int I_USHR = OFFSET + 8;
+    public static final int I_OR = OFFSET + 9;
+    public static final int I_AND = OFFSET + 10;
+    public static final int I_XOR = OFFSET + 11;
+    public static final int I_COPY_SIGN = OFFSET + 12;
+    public static final int I_MIN = OFFSET + 13;
+    public static final int I_MAX = OFFSET + 14;
+
+    public static final int I_NEG = OFFSET + 15;
+    public static final int I_ABS = OFFSET + 16;
+    public static final int I_INCR = OFFSET + 17;
+    public static final int I_DECR = OFFSET + 18;
+    public static final int I_NOT = OFFSET + 19;
+    public static final int I_SIGN = OFFSET + 20;
+
+    public static final int I_CLAMP = OFFSET + 21;
+    public static final int I_IFELSE = OFFSET + 22;
+    public static final int I_MAD = OFFSET + 23;
+
+    public static final float LAST_OP = 24;
+
+    public static final int I_VAR1 = OFFSET + 24;
+    public static final int I_VAR2 = OFFSET + 24;
+
+
+    int[] mStack;
+    int[] mLocalStack = new int[128];
+    int[] mVar;
+
+
+    interface Op {
+        int eval(int sp);
+    }
+
+    /**
+     * Evaluate a float expression
+     *
+     * @param exp
+     * @param var
+     * @return
+     */
+    public int eval(int mask, int[] exp, int... var) {
+        mStack = exp;
+        mVar = var;
+        int sp = -1;
+        for (int i = 0; i < mStack.length; i++) {
+            int v = mStack[i];
+            if (((1 << i) & mask) != 0) {
+                sp = mOps[v - OFFSET].eval(sp);
+            } else {
+                mStack[++sp] = v;
+            }
+        }
+        return mStack[sp];
+    }
+
+    /**
+     * Evaluate a int expression
+     *
+     * @param exp
+     * @param len
+     * @param var
+     * @return
+     */
+    public int eval(int mask, int[] exp, int len, int... var) {
+        System.arraycopy(exp, 0, mLocalStack, 0, len);
+        mStack = mLocalStack;
+        mVar = var;
+        int sp = -1;
+        for (int i = 0; i < len; i++) {
+            int v = mStack[i];
+            if (((1 << i) & mask) != 0) {
+                sp = mOps[v - OFFSET].eval(sp);
+            } else {
+                mStack[++sp] = v;
+            }
+        }
+        return mStack[sp];
+    }
+
+    /**
+     * Evaluate a int expression
+     *
+     * @param exp
+     * @param var
+     * @return
+     */
+    public int evalDB(int mask, int[] exp, int... var) {
+        mStack = exp;
+        mVar = var;
+        int sp = -1;
+        for (int i = 0; i < exp.length; i++) {
+            int v = mStack[i];
+            if (((1 << i) & mask) != 0) {
+                System.out.print(" " + sNames.get((v - OFFSET)));
+                sp = mOps[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) -> { // SHL shift left
+                mStack[sp - 1] = mStack[sp - 1] << mStack[sp];
+                return sp - 1;
+            },
+            (sp) -> { // SHR shift right
+                mStack[sp - 1] = mStack[sp - 1] >> mStack[sp];
+                return sp - 1;
+            },
+            (sp) -> { // USHR unsigned shift right
+                mStack[sp - 1] = mStack[sp - 1] >>> mStack[sp];
+                return sp - 1;
+            },
+            (sp) -> { // OR operator
+                mStack[sp - 1] = mStack[sp - 1] | mStack[sp];
+                return sp - 1;
+            },
+            (sp) -> { // AND operator
+                mStack[sp - 1] = mStack[sp - 1] & mStack[sp];
+                return sp - 1;
+            },
+            (sp) -> { // XOR xor operator
+                mStack[sp - 1] = mStack[sp - 1] ^ mStack[sp];
+                return sp - 1;
+            },
+            (sp) -> { // COPY_SIGN copy the sing of (using bit magic)
+                mStack[sp - 1] = (mStack[sp - 1] ^ (mStack[sp] >> 31))
+                        - (mStack[sp] >> 31);
+                return sp - 1;
+            },
+            (sp) -> { // MIN
+                mStack[sp - 1] = Math.min(mStack[sp - 1], mStack[sp]);
+                return sp - 1;
+            },
+            (sp) -> { // MAX
+                mStack[sp - 1] = Math.max(mStack[sp - 1], mStack[sp]);
+                return sp - 1;
+            },
+            (sp) -> { // NEG
+                mStack[sp] = -mStack[sp];
+                return sp;
+            },
+            (sp) -> { // ABS
+                mStack[sp] = Math.abs(mStack[sp]);
+                return sp;
+            },
+            (sp) -> { // INCR increment
+                mStack[sp] = mStack[sp] + 1;
+                return sp;
+            },
+            (sp) -> { // DECR decrement
+                mStack[sp] = mStack[sp] - 1;
+                return sp;
+            },
+            (sp) -> { // NOT Bit invert
+                mStack[sp] = ~mStack[sp];
+                return sp;
+            },
+            (sp) -> { // SIGN x<0 = -1,x==0 =  0 , x>0 = 1
+                mStack[sp] = (mStack[sp] >> 31) | (-mStack[sp] >>> 31);
+                return sp;
+            },
+
+            (sp) -> { // CLAMP(min,max, val)
+                mStack[sp - 2] = Math.min(Math.max(mStack[sp - 2], mStack[sp]),
+                        mStack[sp - 1]);
+                return sp - 2;
+            },
+            (sp) -> { // Ternary conditional
+                mStack[sp - 2] = (mStack[sp] > 0)
+                        ? mStack[sp - 1] : mStack[sp - 2];
+                return sp - 2;
+            },
+            (sp) -> { // MAD
+                mStack[sp - 2] = mStack[sp] + mStack[sp - 1] * mStack[sp - 2];
+                return sp - 2;
+            },
+
+            (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++, "<<");
+        sNames.put(k++, ">>");
+        sNames.put(k++, ">>>");
+        sNames.put(k++, "|");
+        sNames.put(k++, "&");
+        sNames.put(k++, "^");
+        sNames.put(k++, "copySign");
+        sNames.put(k++, "min");
+        sNames.put(k++, "max");
+        sNames.put(k++, "neg");
+        sNames.put(k++, "abs");
+        sNames.put(k++, "incr");
+        sNames.put(k++, "decr");
+        sNames.put(k++, "not");
+        sNames.put(k++, "sign");
+        sNames.put(k++, "clamp");
+        sNames.put(k++, "ifElse");
+        sNames.put(k++, "mad");
+        sNames.put(k++, "ceil");
+        sNames.put(k++, "a[0]");
+        sNames.put(k++, "a[1]");
+        sNames.put(k++, "a[2]");
+    }
+
+    /**
+     * given a int command return its math name (e.g sin, cos etc.)
+     *
+     * @param f
+     * @return
+     */
+    public static String toMathName(int f) {
+        int id = f - OFFSET;
+        return sNames.get(id);
+    }
+
+    /**
+     * Convert an expression encoded as an array of ints int ot a string
+     *
+     * @param exp
+     * @param labels
+     * @return
+     */
+    public static String toString(int mask, int[] exp, String[] labels) {
+        StringBuilder s = new StringBuilder();
+        for (int i = 0; i < exp.length; i++) {
+            int v = exp[i];
+
+            if (((1 << i) & mask) != 0) {
+                if (v < OFFSET) {
+                    s.append(toMathName(v));
+                } else {
+                    s.append("[");
+                    s.append(v);
+                    s.append("]");
+                }
+            } else {
+                if (labels[i] != null) {
+                    s.append(labels[i]);
+                }
+                s.append(v);
+            }
+            s.append(" ");
+        }
+        return s.toString();
+    }
+
+    /**
+     * Convert an expression encoded as an array of ints int ot a string
+     *
+     * @param mask bit mask of operators vs commands
+     * @param exp
+     * @return
+     */
+    public static String toString(int mask, int[] exp) {
+        StringBuilder s = new StringBuilder();
+        s.append(Integer.toBinaryString(mask));
+        s.append(" : ");
+        for (int i = 0; i < exp.length; i++) {
+            int v = exp[i];
+
+            if (((1 << i) & mask) != 0) {
+                if (v > OFFSET) {
+                    s.append(" ");
+                    s.append(toMathName(v));
+                    s.append(" ");
+
+                } else {
+                    s.append("[");
+                    s.append(v);
+                    s.append("]");
+                }
+            }
+            s.append(" " + v);
+        }
+        return s.toString();
+    }
+
+    /**
+     * This creates an infix string expression
+     * @param mask The bits that are operators
+     * @param exp the array of expressions
+     * @return infix string
+     */
+    public static String toStringInfix(int mask, int[] exp) {
+        return toString(mask, exp, exp.length - 1);
+    }
+
+    static String toString(int mask, int[] exp, int sp) {
+        String[] str = new String[exp.length];
+        if (((1 << sp) & mask) != 0) {
+            int id = exp[sp] - OFFSET;
+            switch (NO_OF_OPS[id]) {
+                case -1:
+                    return "nop";
+                case 1:
+                    return sNames.get(id) + "(" + toString(mask, exp, sp - 1) + ") ";
+                case 2:
+                    if (infix(id)) {
+                        return "(" + toString(mask, exp, sp - 2)
+                                + " " + sNames.get(id) + " "
+                                + toString(mask, exp, sp - 1) + ") ";
+                    } else {
+                        return sNames.get(id) + "("
+                                + toString(mask, exp, sp - 2) + ", "
+                                + toString(mask, exp, sp - 1) + ")";
+                    }
+                case 3:
+                    if (infix(id)) {
+                        return "((" + toString(mask, exp, sp + 3) + ") ? "
+                                + toString(mask, exp, sp - 2) + ":"
+                                + toString(mask, exp, sp - 1) + ")";
+                    } else {
+                        return sNames.get(id)
+                                + "(" + toString(mask, exp, sp - 3)
+                                + ", " + toString(mask, exp, sp - 2)
+                                + ", " + toString(mask, exp, sp - 1) + ")";
+                    }
+            }
+        }
+        return Integer.toString(exp[sp]);
+    }
+
+    static final int[] NO_OF_OPS = {
+            -1, // no op
+            2, 2, 2, 2, 2, // + - * / %
+            2, 2, 2, 2, 2, 2, 2, 2, 2, //<<, >> , >>> , | , &, ^, min max
+            1, 1, 1, 1, 1, 1,   // neg, abs, ++, -- , not , sign
+
+            3, 3, 3, // clamp, ifElse, mad,
+            0, 0, 0 // mad, ?:,
+            // a[0],a[1],a[2]
+    };
+
+    /**
+     * to be used by parser to determine if command is infix
+     *
+     * @param n the operator (minus the offset)
+     * @return true if the operator is infix
+     */
+    static boolean infix(int n) {
+        return ((n < 12));
+    }
+
+    /**
+     * is it an id or operation
+     * @param mask the bits that mark elements as an operation
+     * @param i the bit to check
+     * @return true if the bit is 1
+     */
+    public static boolean isOperation(int mask, int i) {
+        return ((1 << i) & mask) != 0;
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java b/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java
new file mode 100644
index 0000000..1051192
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.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.types;
+
+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;
+
+/**
+ * Used to represent a boolean
+ */
+public class BooleanConstant implements Operation {
+    boolean mValue = false;
+    private int mId;
+
+    public BooleanConstant(int id, boolean value) {
+        mId = id;
+        mValue = value;
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        COMPANION.apply(buffer, mId, mValue);
+    }
+
+    @Override
+    public void apply(RemoteContext context) {
+
+    }
+
+    @Override
+    public String deepToString(String indent) {
+        return toString();
+    }
+
+    @Override
+    public String toString() {
+        return "BooleanConstant[" + mId + "] = " + mValue + "";
+    }
+
+    public static final Companion COMPANION = new Companion();
+
+    public static class Companion implements CompanionOperation {
+        private Companion() {
+        }
+
+        @Override
+        public String name() {
+            return "OrigamiBoolean";
+        }
+
+        @Override
+        public int id() {
+            return Operations.DATA_BOOLEAN;
+        }
+
+        /**
+         * Writes out the operation to the buffer
+         *
+         * @param buffer
+         * @param id
+         * @param value
+         */
+        public void apply(WireBuffer buffer, int id, boolean value) {
+            buffer.start(Operations.DATA_BOOLEAN);
+            buffer.writeInt(id);
+            buffer.writeBoolean(value);
+        }
+
+        @Override
+        public void read(WireBuffer buffer, List<Operation> operations) {
+            int id = buffer.readInt();
+
+            boolean value = buffer.readBoolean();
+            operations.add(new BooleanConstant(id, value));
+        }
+    }
+
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java b/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java
new file mode 100644
index 0000000..ceb3236
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.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.types;
+
+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;
+
+/**
+ * Represents a single integer typically used for states
+ * or named for input into the system
+ */
+public class IntegerConstant implements Operation {
+    private int mValue = 0;
+    private int mId;
+
+    IntegerConstant(int id, int value) {
+        mId = id;
+        mValue = value;
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        COMPANION.apply(buffer, mId, mValue);
+    }
+
+    @Override
+    public void apply(RemoteContext context) {
+        context.loadInteger(mId, mValue);
+    }
+
+    @Override
+    public String deepToString(String indent) {
+        return toString();
+    }
+
+    @Override
+    public String toString() {
+        return "IntegerConstant[" + mId + "] = " + mValue + "";
+    }
+
+    public static final Companion COMPANION = new Companion();
+
+    public static class Companion implements CompanionOperation {
+        private Companion() {
+        }
+
+        @Override
+        public String name() {
+            return "IntegerConstant";
+        }
+
+        @Override
+        public int id() {
+            return Operations.DATA_INT;
+        }
+
+        /**
+         * Writes out the operation to the buffer
+         *
+         * @param buffer
+         * @param textId
+         * @param value
+         */
+        public void apply(WireBuffer buffer, int textId, int value) {
+            buffer.start(Operations.DATA_INT);
+            buffer.writeInt(textId);
+            buffer.writeInt(value);
+        }
+
+        @Override
+        public void read(WireBuffer buffer, List<Operation> operations) {
+            int id = buffer.readInt();
+
+            int value = buffer.readInt();
+            operations.add(new IntegerConstant(id, value));
+        }
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
index 73e94fa..b2406bf 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
@@ -410,4 +410,3 @@
         }
     }
 }
-
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 ecb68bb..39a770a 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
@@ -39,12 +39,16 @@
 import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
 import com.android.internal.widget.remotecompose.core.operations.paint.PaintChanges;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * An implementation of PaintContext for the Android Canvas.
  * This is used to play the RemoteCompose operations on Android.
  */
 public class AndroidPaintContext extends PaintContext {
     Paint mPaint = new Paint();
+    List<Paint> mPaintList = new ArrayList<>();
     Canvas mCanvas;
     Rect mTmpRect = new Rect(); // use in calculation of bounds
 
@@ -162,6 +166,16 @@
     }
 
     @Override
+    public void savePaint() {
+        mPaintList.add(new Paint(mPaint));
+    }
+
+    @Override
+    public void restorePaint() {
+        mPaint = mPaintList.remove(mPaintList.size() - 1);
+    }
+
+    @Override
     public void drawRoundRect(float left,
                               float top,
                               float right,
@@ -335,6 +349,11 @@
         return null;
     }
 
+    /**
+     * This applies paint changes to the current paint
+     *
+     * @param mPaintData the list change to the paint
+     */
     @Override
     public void applyPaint(PaintBundle mPaintData) {
         mPaintData.applyPaintChange((PaintContext) this, new PaintChanges() {
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 dd43bd5..5a87c70 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
@@ -120,6 +120,11 @@
         mRemoteComposeState.updateFloat(id, value);
     }
 
+    @Override
+    public void loadInteger(int id, int value) {
+        mRemoteComposeState.updateInteger(id, value);
+    }
+
 
     @Override
     public void loadColor(int id, int color) {
@@ -142,6 +147,11 @@
     }
 
     @Override
+    public int getInteger(int id) {
+        return  mRemoteComposeState.getInteger(id);
+    }
+
+    @Override
     public int getColor(int id) {
         return mRemoteComposeState.getColor(id);
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/FloatsToPath.java b/core/java/com/android/internal/widget/remotecompose/player/platform/FloatsToPath.java
index 2d766f8..7a85427 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/FloatsToPath.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/FloatsToPath.java
@@ -58,7 +58,7 @@
                 break;
                 case PathData.CONIC: {
                     i += 3;
-                    if (Build.VERSION.SDK_INT >= 34) {
+                    if (Build.VERSION.SDK_INT >= 34) { // REMOVE IN PLATFORM
                         path.conicTo(
                                 floatPath[i + 0], floatPath[i + 1],
                                 floatPath[i + 2], floatPath[i + 3],