Update to ToT RemoteCompose

Bug: 339721781
Flag: EXEMPT External Libraries
Test: in GoB
Change-Id: Iefdb5c1a5e3b778debd74526d903697bf57f44de
diff --git a/core/java/com/android/internal/widget/remotecompose/core/CompanionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/CompanionOperation.java
index 244bb3d..b5d3895 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/CompanionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/CompanionOperation.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core;
 
+import android.annotation.NonNull;
+
 import java.util.List;
 
 /** Interface for the companion operations */
@@ -25,5 +27,5 @@
      * @param buffer data to read to create operation
      * @param operations command is to be added
      */
-    void read(WireBuffer buffer, List<Operation> operations);
+    void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations);
 }
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 0761a24..370289a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
@@ -19,6 +19,7 @@
 import android.annotation.Nullable;
 
 import com.android.internal.widget.remotecompose.core.operations.ComponentValue;
+import com.android.internal.widget.remotecompose.core.operations.FloatExpression;
 import com.android.internal.widget.remotecompose.core.operations.IntegerExpression;
 import com.android.internal.widget.remotecompose.core.operations.NamedVariable;
 import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior;
@@ -53,15 +54,16 @@
 
     private static final boolean DEBUG = false;
 
-    ArrayList<Operation> mOperations;
+    @NonNull ArrayList<Operation> mOperations = new ArrayList<>();
 
     @Nullable RootLayoutComponent mRootLayoutComponent = null;
 
-    RemoteComposeState mRemoteComposeState = new RemoteComposeState();
+    @NonNull RemoteComposeState mRemoteComposeState = new RemoteComposeState();
     @NonNull TimeVariables mTimeVariables = new TimeVariables();
     // Semantic version of the document
     @NonNull Version mVersion = new Version(0, 1, 0);
 
+    @Nullable
     String mContentDescription; // text description of the document (used for accessibility)
 
     long mRequiredCapabilities = 0L; // bitmask indicating needed capabilities of the player(unused)
@@ -74,19 +76,22 @@
 
     int mContentAlignment = RootContentBehavior.ALIGNMENT_CENTER;
 
-    RemoteComposeBuffer mBuffer = new RemoteComposeBuffer(mRemoteComposeState);
+    @NonNull RemoteComposeBuffer mBuffer = new RemoteComposeBuffer(mRemoteComposeState);
 
     private final HashMap<Long, IntegerExpression> mIntegerExpressions = new HashMap<>();
 
+    private final HashMap<Integer, FloatExpression> mFloatExpressions = new HashMap<>();
+
     private HashSet<Component> mAppliedTouchOperations = new HashSet<>();
 
     private int mLastId = 1; // last component id when inflating the file
 
+    @Nullable
     public String getContentDescription() {
         return mContentDescription;
     }
 
-    public void setContentDescription(String contentDescription) {
+    public void setContentDescription(@Nullable String contentDescription) {
         this.mContentDescription = contentDescription;
     }
 
@@ -116,19 +121,21 @@
         mRemoteComposeState.setWindowHeight(height);
     }
 
+    @NonNull
     public RemoteComposeBuffer getBuffer() {
         return mBuffer;
     }
 
-    public void setBuffer(RemoteComposeBuffer buffer) {
+    public void setBuffer(@NonNull RemoteComposeBuffer buffer) {
         this.mBuffer = buffer;
     }
 
+    @NonNull
     public RemoteComposeState getRemoteComposeState() {
         return mRemoteComposeState;
     }
 
-    public void setRemoteComposeState(RemoteComposeState remoteComposeState) {
+    public void setRemoteComposeState(@NonNull RemoteComposeState remoteComposeState) {
         this.mRemoteComposeState = remoteComposeState;
     }
 
@@ -171,7 +178,7 @@
      * @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) {
+    public void computeScale(float w, float h, @NonNull float[] scaleOutput) {
         float contentScaleX = 1f;
         float contentScaleY = 1f;
         if (mContentSizing == RootContentBehavior.SIZING_SCALE) {
@@ -236,7 +243,11 @@
      * @param translateOutput will contain the computed translation
      */
     private void computeTranslate(
-            float w, float h, float contentScaleX, float contentScaleY, float[] translateOutput) {
+            float w,
+            float h,
+            float contentScaleX,
+            float contentScaleY,
+            @NonNull float[] translateOutput) {
         int horizontalContentAlignment = mContentAlignment & 0xF0;
         int verticalContentAlignment = mContentAlignment & 0xF;
         float translateX = 0f;
@@ -350,6 +361,22 @@
         }
     }
 
+    /**
+     * Execute an integer expression with the given id and put its value on the targetId
+     *
+     * @param expressionId the id of the integer expression
+     * @param targetId the id of the value to update with the expression
+     * @param context the current context
+     */
+    public void evaluateFloatExpression(
+            int expressionId, int targetId, @NonNull RemoteContext context) {
+        FloatExpression expression = mFloatExpressions.get(expressionId);
+        if (expression != null) {
+            float v = expression.evaluate(context);
+            context.overrideFloat(targetId, v);
+        }
+    }
+
     // ============== Haptic support ==================
     public interface HapticEngine {
         void haptic(int type);
@@ -375,7 +402,7 @@
 
     /** Callback interface for host actions */
     public interface ActionCallback {
-        void onAction(String name, Object value);
+        void onAction(@NonNull String name, Object value);
     }
 
     @NonNull HashSet<ActionCallback> mActionListeners = new HashSet<ActionCallback>();
@@ -386,7 +413,7 @@
      * @param name the action name
      * @param value a parameter to the action
      */
-    public void runNamedAction(String name, Object value) {
+    public void runNamedAction(@NonNull String name, Object value) {
         // TODO: we might add an interface to group all valid parameter types
         for (ActionCallback callback : mActionListeners) {
             callback.onAction(name, value);
@@ -398,7 +425,7 @@
      *
      * @param callback
      */
-    public void addActionCallback(ActionCallback callback) {
+    public void addActionCallback(@NonNull ActionCallback callback) {
         mActionListeners.add(callback);
     }
 
@@ -408,7 +435,7 @@
     }
 
     public interface ClickCallbacks {
-        void click(int id, String metadata);
+        void click(int id, @Nullable String metadata);
     }
 
     @NonNull HashSet<ClickCallbacks> mClickListeners = new HashSet<>();
@@ -429,21 +456,21 @@
 
     public static class ClickAreaRepresentation {
         int mId;
-        String mContentDescription;
+        @Nullable final String mContentDescription;
         float mLeft;
         float mTop;
         float mRight;
         float mBottom;
-        String mMetadata;
+        @Nullable final String mMetadata;
 
         public ClickAreaRepresentation(
                 int id,
-                String contentDescription,
+                @Nullable String contentDescription,
                 float left,
                 float top,
                 float right,
                 float bottom,
-                String metadata) {
+                @Nullable String metadata) {
             this.mId = id;
             this.mContentDescription = contentDescription;
             this.mLeft = left;
@@ -484,10 +511,11 @@
             return mId;
         }
 
-        public String getContentDescription() {
+        public @Nullable String getContentDescription() {
             return mContentDescription;
         }
 
+        @Nullable
         public String getMetadata() {
             return mMetadata;
         }
@@ -502,6 +530,10 @@
                 IntegerExpression expression = (IntegerExpression) op;
                 mIntegerExpressions.put((long) expression.mId, expression);
             }
+            if (op instanceof FloatExpression) {
+                FloatExpression expression = (FloatExpression) op;
+                mFloatExpressions.put(expression.mId, expression);
+            }
         }
         mOperations = inflateComponents(mOperations);
         mBuffer = buffer;
@@ -605,7 +637,8 @@
 
     @NonNull private HashMap<Integer, Component> mComponentMap = new HashMap<Integer, Component>();
 
-    private void registerVariables(RemoteContext context, @NonNull ArrayList<Operation> list) {
+    private void registerVariables(
+            @NonNull RemoteContext context, @NonNull ArrayList<Operation> list) {
         for (Operation op : list) {
             if (op instanceof VariableSupport) {
                 ((VariableSupport) op).updateVariables(context);
@@ -701,12 +734,12 @@
      */
     public void addClickArea(
             int id,
-            String contentDescription,
+            @Nullable String contentDescription,
             float left,
             float top,
             float right,
             float bottom,
-            String metadata) {
+            @Nullable String metadata) {
         mClickAreas.add(
                 new ClickAreaRepresentation(
                         id, contentDescription, left, top, right, bottom, metadata));
@@ -726,7 +759,7 @@
      *
      * @param callback called when a click area has been hit, passing the click are id and metadata.
      */
-    public void addClickListener(ClickCallbacks callback) {
+    public void addClickListener(@NonNull ClickCallbacks callback) {
         mClickListeners.add(callback);
     }
 
@@ -744,7 +777,7 @@
      * Passing a click event to the document. This will possibly result in calling the click
      * listeners.
      */
-    public void onClick(RemoteContext context, float x, float y) {
+    public void onClick(@NonNull RemoteContext context, float x, float y) {
         for (ClickAreaRepresentation clickArea : mClickAreas) {
             if (clickArea.contains(x, y)) {
                 warnClickListeners(clickArea);
@@ -802,6 +835,14 @@
         for (TouchListener clickArea : mTouchListeners) {
             clickArea.touchDrag(context, x, y);
         }
+        if (mRootLayoutComponent != null) {
+            for (Component component : mAppliedTouchOperations) {
+                component.onTouchDrag(context, this, x, y, true);
+            }
+            if (!mAppliedTouchOperations.isEmpty()) {
+                return true;
+            }
+        }
         if (!mTouchListeners.isEmpty()) {
             return true;
         }
@@ -940,6 +981,7 @@
      */
     public void paint(@NonNull RemoteContext context, int theme) {
         context.getPaintContext().clearNeedsRepaint();
+        context.loadFloat(RemoteContext.ID_DENSITY, context.getDensity());
         context.mMode = RemoteContext.ContextMode.UNSET;
         // current theme starts as UNSPECIFIED, until a Theme setter
         // operation gets executed and modify it.
@@ -1097,6 +1139,7 @@
         }
     }
 
+    @NonNull
     public List<Operation> getOperations() {
         return mOperations;
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/Operation.java b/core/java/com/android/internal/widget/remotecompose/core/Operation.java
index f1885f9..102003e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Operation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Operation.java
@@ -15,22 +15,22 @@
  */
 package com.android.internal.widget.remotecompose.core;
 
-import android.annotation.Nullable;
+import android.annotation.NonNull;
 
 /** Base interface for RemoteCompose operations */
 public interface Operation {
 
     /** add the operation to the buffer */
-    void write(WireBuffer buffer);
+    void write(@NonNull WireBuffer buffer);
 
     /**
      * paint an operation
      *
      * @param context the paint context used to paint the operation
      */
-    void apply(RemoteContext context);
+    void apply(@NonNull RemoteContext context);
 
     /** Debug utility to display an operation + indentation */
-    @Nullable
-    String deepToString(String indent);
+    @NonNull
+    String deepToString(@NonNull String indent);
 }
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 53c45fa..687a99b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Operations.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
@@ -98,7 +98,9 @@
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.OffsetModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.PaddingModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.RoundedClipRectModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ScrollModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ValueFloatChangeActionOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ValueFloatExpressionChangeActionOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ValueIntegerChangeActionOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ValueIntegerExpressionChangeActionOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ValueStringChangeActionOperation;
@@ -214,6 +216,7 @@
     public static final int MODIFIER_OFFSET = 221;
     public static final int MODIFIER_ZINDEX = 223;
     public static final int MODIFIER_GRAPHICS_LAYER = 224;
+    public static final int MODIFIER_SCROLL = 226;
 
     public static final int LOOP_START = 215;
     public static final int LOOP_END = 216;
@@ -226,6 +229,7 @@
     public static final int VALUE_STRING_CHANGE_ACTION = 213;
     public static final int VALUE_INTEGER_EXPRESSION_CHANGE_ACTION = 218;
     public static final int VALUE_FLOAT_CHANGE_ACTION = 222;
+    public static final int VALUE_FLOAT_EXPRESSION_CHANGE_ACTION = 227;
 
     public static final int ANIMATION_SPEC = 14;
 
@@ -235,7 +239,7 @@
 
     static class UniqueIntMap<T> extends IntMap<T> {
         @Override
-        public T put(int key, T value) {
+        public T put(int key, @NonNull T value) {
             assert null == get(key) : "Opcode " + key + " already used in Operations !";
             return super.put(key, value);
         }
@@ -316,6 +320,7 @@
         map.put(MODIFIER_OFFSET, OffsetModifierOperation::read);
         map.put(MODIFIER_ZINDEX, ZIndexModifierOperation::read);
         map.put(MODIFIER_GRAPHICS_LAYER, GraphicsLayerModifierOperation::read);
+        map.put(MODIFIER_SCROLL, ScrollModifierOperation::read);
 
         map.put(OPERATIONS_LIST_END, OperationsListEnd::read);
 
@@ -327,6 +332,9 @@
                 ValueIntegerExpressionChangeActionOperation::read);
         map.put(VALUE_STRING_CHANGE_ACTION, ValueStringChangeActionOperation::read);
         map.put(VALUE_FLOAT_CHANGE_ACTION, ValueFloatChangeActionOperation::read);
+        map.put(
+                VALUE_FLOAT_EXPRESSION_CHANGE_ACTION,
+                ValueFloatExpressionChangeActionOperation::read);
 
         map.put(LAYOUT_ROOT, RootLayoutComponent::read);
         map.put(LAYOUT_CONTENT, LayoutComponentContent::read);
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 1a71afe..38b08e9 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
 
 /** Specify an abstract paint context used by RemoteCompose commands to draw */
@@ -22,9 +24,10 @@
     public static final int TEXT_MEASURE_MONOSPACE_WIDTH = 0x01;
     public static final int TEXT_MEASURE_FONT_HEIGHT = 0x02;
 
-    protected RemoteContext mContext;
+    protected @NonNull RemoteContext mContext;
     private boolean mNeedsRepaint = false;
 
+    @NonNull
     public RemoteContext getContext() {
         return mContext;
     }
@@ -37,11 +40,11 @@
         mNeedsRepaint = false;
     }
 
-    public PaintContext(RemoteContext context) {
+    public PaintContext(@NonNull RemoteContext context) {
         this.mContext = context;
     }
 
-    public void setContext(RemoteContext context) {
+    public void setContext(@NonNull RemoteContext context) {
         this.mContext = context;
     }
 
@@ -117,7 +120,8 @@
      *     descent of the font (not just of the measured text)
      * @param bounds the bounds (left, top, right, bottom)
      */
-    public abstract void getTextBounds(int textId, int start, int end, int flags, float[] bounds);
+    public abstract void getTextBounds(
+            int textId, int start, int end, int flags, @NonNull float[] bounds);
 
     /**
      * Draw a text starting ast x,y
@@ -158,7 +162,7 @@
      *
      * @param mPaintData the list of changes
      */
-    public abstract void applyPaint(PaintBundle mPaintData);
+    public abstract void applyPaint(@NonNull PaintBundle mPaintData);
 
     /**
      * Scale the rendering by scaleX and saleY (1.0 = no scale). Scaling is done about
@@ -264,7 +268,7 @@
      *
      * @param content the content to log
      */
-    public void log(String content) {
+    public void log(@NonNull String content) {
         System.out.println("[LOG] " + content);
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java b/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java
index 049e477..9999182 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java
@@ -35,17 +35,17 @@
 
     @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@NonNull String indent) {
         return indent + toString();
     }
 
-    public abstract void paint(PaintContext context);
+    public abstract void paint(@NonNull PaintContext context);
 
     /**
      * Will return true if the operation is similar enough to the current one, in the context of an
      * animated transition.
      */
-    public boolean suitableForTransition(Operation op) {
+    public boolean suitableForTransition(@NonNull Operation op) {
         // by default expects the op to not be suitable
         return false;
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/Platform.java b/core/java/com/android/internal/widget/remotecompose/core/Platform.java
index 7fbcfae..dcb8efeb 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Platform.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Platform.java
@@ -15,19 +15,20 @@
  */
 package com.android.internal.widget.remotecompose.core;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 
 /** Services that are needed to be provided by the platform during encoding. */
 public interface Platform {
     @Nullable
-    byte[] imageToByteArray(Object image);
+    byte[] imageToByteArray(@NonNull Object image);
 
-    int getImageWidth(Object image);
+    int getImageWidth(@NonNull Object image);
 
-    int getImageHeight(Object image);
+    int getImageHeight(@NonNull Object image);
 
     @Nullable
-    float[] pathToFloatArray(Object path);
+    float[] pathToFloatArray(@NonNull Object path);
 
     enum LogCategory {
         DEBUG,
@@ -42,22 +43,22 @@
     Platform None =
             new Platform() {
                 @Override
-                public byte[] imageToByteArray(Object image) {
+                public byte[] imageToByteArray(@NonNull Object image) {
                     throw new UnsupportedOperationException();
                 }
 
                 @Override
-                public int getImageWidth(Object image) {
+                public int getImageWidth(@NonNull Object image) {
                     throw new UnsupportedOperationException();
                 }
 
                 @Override
-                public int getImageHeight(Object image) {
+                public int getImageHeight(@NonNull Object image) {
                     throw new UnsupportedOperationException();
                 }
 
                 @Override
-                public float[] pathToFloatArray(Object path) {
+                public float[] pathToFloatArray(@NonNull Object path) {
                     throw new UnsupportedOperationException();
                 }
 
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 7d9439d..c05079e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
@@ -15,6 +15,9 @@
  */
 package com.android.internal.widget.remotecompose.core;
 
+import static com.android.internal.widget.remotecompose.core.operations.utilities.AnimatedFloatExpression.ADD;
+import static com.android.internal.widget.remotecompose.core.operations.utilities.AnimatedFloatExpression.MUL;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 
@@ -89,6 +92,7 @@
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.OffsetModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.PaddingModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.RoundedClipRectModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ScrollModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ZIndexModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
 import com.android.internal.widget.remotecompose.core.operations.utilities.NanMap;
@@ -117,9 +121,9 @@
     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();
-    @Nullable Platform mPlatform = null;
-    RemoteComposeState mRemoteComposeState;
+    private @NonNull WireBuffer mBuffer = new WireBuffer();
+    @Nullable private Platform mPlatform = null;
+    @NonNull private final RemoteComposeState mRemoteComposeState;
     private static final boolean DEBUG = false;
 
     private int mLastComponentId = 0;
@@ -130,7 +134,7 @@
      *
      * @param remoteComposeState the state used while encoding on the buffer
      */
-    public RemoteComposeBuffer(RemoteComposeState remoteComposeState) {
+    public RemoteComposeBuffer(@NonNull RemoteComposeState remoteComposeState) {
         this.mRemoteComposeState = remoteComposeState;
     }
 
@@ -155,15 +159,15 @@
         return mPlatform;
     }
 
-    public void setPlatform(Platform platform) {
+    public void setPlatform(@NonNull Platform platform) {
         this.mPlatform = platform;
     }
 
-    public WireBuffer getBuffer() {
+    public @NonNull WireBuffer getBuffer() {
         return mBuffer;
     }
 
-    public void setBuffer(WireBuffer buffer) {
+    public void setBuffer(@NonNull WireBuffer buffer) {
         this.mBuffer = buffer;
     }
 
@@ -200,7 +204,7 @@
      * @param height the height of the document in pixels
      * @param contentDescription content description of the document
      */
-    public void header(int width, int height, String contentDescription) {
+    public void header(int width, int height, @Nullable String contentDescription) {
         header(width, height, contentDescription, 1f, 0);
     }
 
@@ -220,7 +224,7 @@
      * @param dstBottom bottom coordinate of the destination area
      */
     public void drawBitmap(
-            Object image,
+            @NonNull Object image,
             int imageWidth,
             int imageHeight,
             int srcLeft,
@@ -235,8 +239,9 @@
         int imageId = mRemoteComposeState.dataGetId(image);
         if (imageId == -1) {
             imageId = mRemoteComposeState.cacheData(image);
-            byte[] data = mPlatform.imageToByteArray(image);
-            BitmapData.apply(mBuffer, imageId, imageWidth, imageHeight, data);
+            byte[] data = mPlatform.imageToByteArray(image); // todo: potential npe
+            BitmapData.apply(
+                    mBuffer, imageId, imageWidth, imageHeight, data); // todo: potential npe
         }
         int contentDescriptionId = 0;
         if (contentDescription != null) {
@@ -387,7 +392,7 @@
      * @param contentDescription content description of the image
      */
     public void addDrawBitmap(
-            Object image,
+            @NonNull Object image,
             float left,
             float top,
             float right,
@@ -396,11 +401,12 @@
         int imageId = mRemoteComposeState.dataGetId(image);
         if (imageId == -1) {
             imageId = mRemoteComposeState.cacheData(image);
-            byte[] data = mPlatform.imageToByteArray(image);
+            byte[] data = mPlatform.imageToByteArray(image); // todo: potential npe
             int imageWidth = mPlatform.getImageWidth(image);
             int imageHeight = mPlatform.getImageHeight(image);
 
-            BitmapData.apply(mBuffer, imageId, imageWidth, imageHeight, data);
+            BitmapData.apply(
+                    mBuffer, imageId, imageWidth, imageHeight, data); // todo: potential npe
         }
         int contentDescriptionId = 0;
         if (contentDescription != null) {
@@ -446,7 +452,7 @@
      * @param contentDescription associate a string with image for accessibility
      */
     public void drawScaledBitmap(
-            Object image,
+            @NonNull Object image,
             float srcLeft,
             float srcTop,
             float srcRight,
@@ -461,11 +467,11 @@
         int imageId = mRemoteComposeState.dataGetId(image);
         if (imageId == -1) {
             imageId = mRemoteComposeState.cacheData(image);
-            byte[] data = mPlatform.imageToByteArray(image);
+            byte[] data = mPlatform.imageToByteArray(image); // todo: potential npe
             int imageWidth = mPlatform.getImageWidth(image);
             int imageHeight = mPlatform.getImageHeight(image);
 
-            BitmapData.apply(mBuffer, imageId, imageWidth, imageHeight, data);
+            BitmapData.apply(mBuffer, imageId, imageWidth, imageHeight, data); // todo: potential pe
         }
         int contentDescriptionId = 0;
         if (contentDescription != null) {
@@ -493,11 +499,11 @@
      * @param image drawScaledBitmap
      * @return id of the image useful with
      */
-    public int addBitmap(Object image) {
+    public int addBitmap(@NonNull Object image) {
         int imageId = mRemoteComposeState.dataGetId(image);
         if (imageId == -1) {
             imageId = mRemoteComposeState.cacheData(image);
-            byte[] data = mPlatform.imageToByteArray(image);
+            byte[] data = mPlatform.imageToByteArray(image); // tODO: potential npe
             int imageWidth = mPlatform.getImageWidth(image);
             int imageHeight = mPlatform.getImageHeight(image);
 
@@ -512,11 +518,11 @@
      * @param image drawScaledBitmap
      * @return id of the image useful with
      */
-    public int addBitmap(Object image, @NonNull String name) {
+    public int addBitmap(@NonNull Object image, @NonNull String name) {
         int imageId = mRemoteComposeState.dataGetId(image);
         if (imageId == -1) {
             imageId = mRemoteComposeState.cacheData(image);
-            byte[] data = mPlatform.imageToByteArray(image);
+            byte[] data = mPlatform.imageToByteArray(image); // todo: potential npe
             int imageWidth = mPlatform.getImageWidth(image);
             int imageHeight = mPlatform.getImageHeight(image);
 
@@ -629,7 +635,7 @@
      *
      * @param path The path to be drawn
      */
-    public void addDrawPath(Object path) {
+    public void addDrawPath(@NonNull Object path) {
         int id = mRemoteComposeState.dataGetId(path);
         if (id == -1) { // never been seen before
             id = addPathData(path);
@@ -681,7 +687,8 @@
      * @param hOffset The distance along the path to add to the text's starting position
      * @param vOffset The distance above(-) or below(+) the path to position the text
      */
-    public void addDrawTextOnPath(@NonNull String text, Object path, float hOffset, float vOffset) {
+    public void addDrawTextOnPath(
+            @NonNull String text, @NonNull Object path, float hOffset, float vOffset) {
         int pathId = mRemoteComposeState.dataGetId(path);
         if (pathId == -1) { // never been seen before
             pathId = addPathData(path);
@@ -752,7 +759,7 @@
      *   <li>Panning of 1.0, 0.0 - the test is centered & to the right of x,y
      * </ul>
      *
-     * Setting panY to NaN results in y being the baseline of the text.
+     * <p>Setting panY to NaN results in y being the baseline of the text.
      *
      * @param text text to draw
      * @param x Coordinate of the Anchor
@@ -836,7 +843,7 @@
      *   <li>Panning of 1.0, 0.0 - the test is centered & to the right of x,y
      * </ul>
      *
-     * Setting panY to NaN results in y being the baseline of the text.
+     * <p>Setting panY to NaN results in y being the baseline of the text.
      *
      * @param textId text to draw
      * @param x Coordinate of the Anchor
@@ -861,7 +868,8 @@
      * @param start The start of the subrange of paths to draw 0 = start form start 0.5 is half way
      * @param stop The end of the subrange of paths to draw 1 = end at the end 0.5 is end half way
      */
-    public void addDrawTweenPath(Object path1, Object path2, float tween, float start, float stop) {
+    public void addDrawTweenPath(
+            @NonNull Object path1, @NonNull Object path2, float tween, float start, float stop) {
         int path1Id = mRemoteComposeState.dataGetId(path1);
         if (path1Id == -1) { // never been seen before
             path1Id = addPathData(path1);
@@ -892,7 +900,7 @@
      * @param path
      * @return the id of the path on the wire
      */
-    public int addPathData(Object path) {
+    public int addPathData(@NonNull Object path) {
         float[] pathData = mPlatform.pathToFloatArray(path);
         int id = mRemoteComposeState.cacheData(path);
         PathData.apply(mBuffer, id, pathData);
@@ -910,7 +918,7 @@
 
     ///////////////////////////////////////////////////////////////////////////////////////////////
 
-    public void inflateFromBuffer(ArrayList<Operation> operations) {
+    public void inflateFromBuffer(@NonNull ArrayList<Operation> operations) {
         mBuffer.setIndex(0);
         while (mBuffer.available()) {
             int opId = mBuffer.readByte();
@@ -926,7 +934,7 @@
     }
 
     public static void readNextOperation(
-            @NonNull WireBuffer buffer, ArrayList<Operation> operations) {
+            @NonNull WireBuffer buffer, @NonNull ArrayList<Operation> operations) {
         int opId = buffer.readByte();
         if (DEBUG) {
             Utils.log(">> " + opId);
@@ -957,15 +965,16 @@
 
     @NonNull
     public static RemoteComposeBuffer fromFile(
-            @NonNull String path, RemoteComposeState remoteComposeState) throws IOException {
+            @NonNull String path, @NonNull RemoteComposeState remoteComposeState)
+            throws IOException {
         RemoteComposeBuffer buffer = new RemoteComposeBuffer(remoteComposeState);
         read(new File(path), buffer);
         return buffer;
     }
 
     @NonNull
-    public RemoteComposeBuffer fromFile(@NonNull File file, RemoteComposeState remoteComposeState)
-            throws IOException {
+    public RemoteComposeBuffer fromFile(
+            @NonNull File file, @NonNull RemoteComposeState remoteComposeState) throws IOException {
         RemoteComposeBuffer buffer = new RemoteComposeBuffer(remoteComposeState);
         read(file, buffer);
         return buffer;
@@ -973,7 +982,7 @@
 
     @NonNull
     public static RemoteComposeBuffer fromInputStream(
-            @NonNull InputStream inputStream, RemoteComposeState remoteComposeState) {
+            @NonNull InputStream inputStream, @NonNull RemoteComposeState remoteComposeState) {
         RemoteComposeBuffer buffer = new RemoteComposeBuffer(remoteComposeState);
         read(inputStream, buffer);
         return buffer;
@@ -1205,6 +1214,44 @@
     /**
      * Add a touch handle system
      *
+     * @param id the float NaN id used for the returned position
+     * @param value the default value
+     * @param min the minimum value
+     * @param max the maximum value
+     * @param velocityId the id for the velocity TODO support in v2
+     * @param exp The Float Expression
+     * @param touchMode the touch up handling behaviour
+     * @param touchSpec the touch up handling parameters
+     * @param easingSpec the easing parameter TODO support in v2
+     */
+    public void addTouchExpression(
+            float id,
+            float value,
+            float min,
+            float max,
+            float velocityId,
+            int touchEffects,
+            float[] exp,
+            int touchMode,
+            float[] touchSpec,
+            float[] easingSpec) {
+        TouchExpression.apply(
+                mBuffer,
+                Utils.idFromNan(id),
+                value,
+                min,
+                max,
+                velocityId,
+                touchEffects,
+                exp,
+                touchMode,
+                touchSpec,
+                easingSpec);
+    }
+
+    /**
+     * Add a touch handle system
+     *
      * @param value the default value
      * @param min the minimum value
      * @param max the maximum value
@@ -1225,9 +1272,8 @@
             int touchMode,
             float[] touchSpec,
             float[] easingSpec) {
-        int id = mRemoteComposeState.nextId();
-        TouchExpression.apply(
-                mBuffer,
+        float id = Utils.asNan(mRemoteComposeState.nextId());
+        addTouchExpression(
                 id,
                 value,
                 min,
@@ -1238,7 +1284,7 @@
                 touchMode,
                 touchSpec,
                 easingSpec);
-        return Utils.asNan(id);
+        return id;
     }
 
     /**
@@ -1248,7 +1294,7 @@
      * @param animation Array of floats that represents animation
      * @return NaN id of the result of the calculation
      */
-    public float addAnimatedFloat(@NonNull float[] value, float[] animation) {
+    public float addAnimatedFloat(@NonNull float[] value, @Nullable float[] animation) {
         int id = mRemoteComposeState.cacheData(value);
         FloatExpression.apply(mBuffer, id, value, animation);
         return Utils.asNan(id);
@@ -1345,7 +1391,7 @@
      * @param listId
      * @return the id of the map, encoded as a float NaN
      */
-    public int addMap(@NonNull String[] keys, byte[] types, int[] listId) {
+    public int addMap(@NonNull String[] keys, @Nullable byte[] types, @NonNull int[] listId) {
         int id = mRemoteComposeState.cacheData(listId, NanMap.TYPE_ARRAY);
         DataMapIds.apply(mBuffer, id, keys, types, listId);
         return id;
@@ -1354,14 +1400,18 @@
     /**
      * This provides access to text in RemoteList
      *
+     * <p>TODO: do we want both a float and an int index version of this method? bbade@ TODO
+     * for @hoford - add a unit test for this method
+     *
      * @param dataSet
      * @param index index as a float variable
      * @return
      */
     public int textLookup(float dataSet, float index) {
         long hash =
-                ((long) Float.floatToRawIntBits(dataSet))
-                        << (32 + Float.floatToRawIntBits(index)); // TODO: is this the correct ()s?
+                (((long) Float.floatToRawIntBits(dataSet)) << 32)
+                        + Float.floatToRawIntBits(
+                                index); // TODO: is this the correct ()s? -- bbade@
         int id = mRemoteComposeState.cacheData(hash);
         TextLookup.apply(mBuffer, id, Utils.idFromNan(dataSet), index);
         return id;
@@ -1370,14 +1420,16 @@
     /**
      * This provides access to text in RemoteList
      *
+     * <p>TODO for hoford - add a unit test for this method
+     *
      * @param dataSet
      * @param index index as an int variable
      * @return
      */
     public int textLookup(float dataSet, int index) {
         long hash =
-                ((long) Float.floatToRawIntBits(dataSet))
-                        << (32 + Float.floatToRawIntBits(index)); // TODO: is this the correct ()s?
+                (((long) Float.floatToRawIntBits(dataSet)) << 32)
+                        + Float.floatToRawIntBits(index); // TODO: is this the correct ()s?
         int id = mRemoteComposeState.cacheData(hash);
         TextLookupInt.apply(mBuffer, id, Utils.idFromNan(dataSet), index);
         return id;
@@ -1521,8 +1573,8 @@
      * @param wrap the wraps value so (e.g 360 so angles 355 would animate to 5)
      * @return
      */
-    public static float[] packAnimation(
-            float duration, int type, float[] spec, float initialValue, float wrap) {
+    public static @NonNull float[] packAnimation(
+            float duration, int type, @Nullable float[] spec, float initialValue, float wrap) {
 
         return FloatAnimation.packToFloatArray(duration, type, spec, initialValue, wrap);
     }
@@ -1591,6 +1643,43 @@
     }
 
     /**
+     * Add a scroll modifier
+     *
+     * @param direction HORIZONTAL(0) or VERTICAL(1)
+     * @param positionId the position id as a NaN
+     * @param notches
+     */
+    public void addModifierScroll(int direction, float positionId, int notches) {
+        // TODO: add support for non-notch behaviors etc.
+        float max = this.addFloat(0f);
+        float notchMax = this.addFloat(0f);
+        float touchExpressionDirection =
+                direction != 0 ? RemoteContext.FLOAT_TOUCH_POS_X : RemoteContext.FLOAT_TOUCH_POS_Y;
+        this.addTouchExpression(
+                positionId,
+                0f,
+                0f,
+                max,
+                0f,
+                3,
+                new float[] {
+                    touchExpressionDirection,
+                    -1,
+                    // TODO: remove this CONTINUOUS_SEC hack...
+                    MUL,
+                    RemoteContext.FLOAT_CONTINUOUS_SEC,
+                    0f,
+                    MUL,
+                    ADD
+                },
+                TouchExpression.STOP_NOTCHES_EVEN,
+                new float[] {notches, notchMax},
+                null);
+
+        ScrollModifierOperation.apply(mBuffer, direction, positionId, max, notchMax);
+    }
+
+    /**
      * Add a background modifier of provided color
      *
      * @param color the color of the background
@@ -1869,4 +1958,8 @@
     public int createID(int type) {
         return mRemoteComposeState.nextId(type);
     }
+
+    public int nextId() {
+        return mRemoteComposeState.nextId();
+    }
 }
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 3039328..a903e6e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
@@ -49,7 +49,7 @@
     private final IntMap<Object> mObjectMap = new IntMap<>();
 
     private final boolean[] mColorOverride = new boolean[MAX_COLORS];
-    private final IntMap<ArrayAccess> mCollectionMap = new IntMap<>();
+    @NonNull private final IntMap<ArrayAccess> mCollectionMap = new IntMap<>();
 
     private final boolean[] mDataOverride = new boolean[MAX_DATA];
     private final boolean[] mIntegerOverride = new boolean[MAX_DATA];
@@ -82,7 +82,7 @@
     }
 
     /** Return the id of an item from the cache. */
-    public int dataGetId(Object data) {
+    public int dataGetId(@NonNull Object data) {
         Integer res = mDataIntMap.get(data);
         if (res == null) {
             return -1;
@@ -94,7 +94,7 @@
      * Add an item to the cache. Generates an id for the item and adds it to the cache based on that
      * id.
      */
-    public int cacheData(Object item) {
+    public int cacheData(@NonNull Object item) {
         int id = nextId();
         mDataIntMap.put(item, id);
         mIntDataMap.put(id, item);
@@ -105,7 +105,7 @@
      * Add an item to the cache. Generates an id for the item and adds it to the cache based on that
      * id.
      */
-    public int cacheData(Object item, int type) {
+    public int cacheData(@NonNull Object item, int type) {
         int id = nextId(type);
         mDataIntMap.put(item, id);
         mIntDataMap.put(id, item);
@@ -113,13 +113,13 @@
     }
 
     /** Insert an item in the cache */
-    public void cacheData(int id, Object item) {
+    public void cacheData(int id, @NonNull Object item) {
         mDataIntMap.put(item, id);
         mIntDataMap.put(id, item);
     }
 
     /** Insert an item in the cache */
-    public void updateData(int id, Object item) {
+    public void updateData(int id, @NonNull Object item) {
         if (!mDataOverride[id]) {
             Object previous = mIntDataMap.get(id);
             if (previous != item) {
@@ -137,7 +137,7 @@
      * @param id
      * @param item the new value
      */
-    public void overrideData(int id, Object item) {
+    public void overrideData(int id, @NonNull Object item) {
         Object previous = mIntDataMap.get(id);
         if (previous != item) {
             mDataIntMap.remove(previous);
@@ -379,7 +379,7 @@
     @NonNull IntMap<ArrayList<VariableSupport>> mVarListeners = new IntMap<>();
     @NonNull ArrayList<VariableSupport> mAllVarListeners = new ArrayList<>();
 
-    private void add(int id, VariableSupport variableSupport) {
+    private void add(int id, @NonNull VariableSupport variableSupport) {
         ArrayList<VariableSupport> v = mVarListeners.get(id);
         if (v == null) {
             v = new ArrayList<VariableSupport>();
@@ -395,17 +395,27 @@
      * @param id
      * @param variableSupport
      */
-    public void listenToVar(int id, VariableSupport variableSupport) {
+    public void listenToVar(int id, @NonNull VariableSupport variableSupport) {
         add(id, variableSupport);
     }
 
     /**
+     * Is any command listening to this variable
+     *
+     * @param id
+     * @return
+     */
+    public boolean hasListener(int id) {
+        return mVarListeners.get(id) != null;
+    }
+
+    /**
      * List of Commands that need to be updated
      *
      * @param context
      * @return
      */
-    public int getOpsToUpdate(RemoteContext context) {
+    public int getOpsToUpdate(@NonNull RemoteContext context) {
         for (VariableSupport vs : mAllVarListeners) {
             vs.updateVariables(context);
         }
@@ -439,18 +449,18 @@
         updateFloat(RemoteContext.ID_WINDOW_HEIGHT, height);
     }
 
-    public void addCollection(int id, ArrayAccess collection) {
+    public void addCollection(int id, @NonNull ArrayAccess collection) {
         mCollectionMap.put(id & 0xFFFFF, collection);
     }
 
     @Override
     public float getFloatValue(int id, int index) {
-        return mCollectionMap.get(id & 0xFFFFF).getFloatValue(index);
+        return mCollectionMap.get(id & 0xFFFFF).getFloatValue(index); // TODO: potential npe
     }
 
     @Override
-    public float[] getFloats(int id) {
-        return mCollectionMap.get(id & 0xFFFFF).getFloats();
+    public @Nullable float[] getFloats(int id) {
+        return mCollectionMap.get(id & 0xFFFFF).getFloats(); // TODO: potential npe
     }
 
     @Override
@@ -458,11 +468,11 @@
         return mCollectionMap.get(id & 0xFFFFF).getId(index);
     }
 
-    public void putDataMap(int id, DataMap map) {
+    public void putDataMap(int id, @NonNull DataMap map) {
         mDataMapMap.put(id, map);
     }
 
-    public DataMap getDataMap(int id) {
+    public @Nullable DataMap getDataMap(int id) {
         return mDataMapMap.get(id);
     }
 
@@ -471,15 +481,15 @@
         return mCollectionMap.get(id & 0xFFFFF).getLength();
     }
 
-    public void setContext(RemoteContext context) {
+    public void setContext(@NonNull RemoteContext context) {
         mRemoteContext = context;
     }
 
-    public void updateObject(int id, Object value) {
+    public void updateObject(int id, @NonNull Object value) {
         mObjectMap.put(id, value);
     }
 
-    public Object getObject(int id) {
+    public @Nullable Object getObject(int id) {
         return mObjectMap.get(id);
     }
 }
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 23cc5b8..26305bf 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
@@ -15,6 +15,7 @@
  */
 package com.android.internal.widget.remotecompose.core;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 
 import com.android.internal.widget.remotecompose.core.operations.FloatExpression;
@@ -39,13 +40,15 @@
  * <p>We also contain a PaintContext, so that any operation can draw as needed.
  */
 public abstract class RemoteContext {
-    protected CoreDocument mDocument;
-    public RemoteComposeState mRemoteComposeState;
+    protected @NonNull CoreDocument mDocument =
+            new CoreDocument(); // todo: is this a valid way to initialize? bbade@
+    public @NonNull RemoteComposeState mRemoteComposeState =
+            new RemoteComposeState(); // todo, is this a valid use of RemoteComposeState -- bbade@
     long mStart = System.nanoTime(); // todo This should be set at a hi level
     @Nullable protected PaintContext mPaintContext = null;
     protected float mDensity = 2.75f;
 
-    ContextMode mMode = ContextMode.UNSET;
+    @NonNull ContextMode mMode = ContextMode.UNSET;
 
     int mDebug = 0;
 
@@ -57,7 +60,7 @@
 
     private boolean mAnimate = true;
 
-    public Component lastComponent;
+    public @Nullable Component mLastComponent;
     public long currentTime = 0L;
 
     public float getDensity() {
@@ -65,7 +68,9 @@
     }
 
     public void setDensity(float density) {
-        mDensity = density;
+        if (density > 0) {
+            mDensity = density;
+        }
     }
 
     public boolean isAnimationEnabled() {
@@ -81,7 +86,7 @@
      *
      * @return the CollectionsAccess implementation
      */
-    public CollectionsAccess getCollectionsAccess() {
+    public @Nullable CollectionsAccess getCollectionsAccess() {
         return mRemoteComposeState;
     }
 
@@ -91,7 +96,7 @@
      * @param instanceId
      * @param floatPath
      */
-    public abstract void loadPathData(int instanceId, float[] floatPath);
+    public abstract void loadPathData(int instanceId, @NonNull float[] floatPath);
 
     /**
      * Associate a name with a give id.
@@ -100,7 +105,7 @@
      * @param varId the id (color,integer,float etc.)
      * @param varType thetype
      */
-    public abstract void loadVariableName(String varName, int varId, int varType);
+    public abstract void loadVariableName(@NonNull String varName, int varId, int varType);
 
     /**
      * Save a color under a given id
@@ -135,7 +140,7 @@
      * @param colorName the name of the color to override
      * @param color Override the default color
      */
-    public abstract void setNamedColorOverride(String colorName, int color);
+    public abstract void setNamedColorOverride(@NonNull String colorName, int color);
 
     /**
      * Set the value of a named String. This overrides the string in the document
@@ -143,7 +148,7 @@
      * @param stringName the name of the string to override
      * @param value Override the default string
      */
-    public abstract void setNamedStringOverride(String stringName, String value);
+    public abstract void setNamedStringOverride(@NonNull String stringName, @NonNull String value);
 
     /**
      * Allows to clear a named String.
@@ -152,7 +157,7 @@
      *
      * @param stringName the name of the string to override
      */
-    public abstract void clearNamedStringOverride(String stringName);
+    public abstract void clearNamedStringOverride(@NonNull String stringName);
 
     /**
      * Set the value of a named Integer. This overrides the integer in the document
@@ -160,7 +165,7 @@
      * @param integerName the name of the integer to override
      * @param value Override the default integer
      */
-    public abstract void setNamedIntegerOverride(String integerName, int value);
+    public abstract void setNamedIntegerOverride(@NonNull String integerName, int value);
 
     /**
      * Allows to clear a named Integer.
@@ -169,7 +174,7 @@
      *
      * @param integerName the name of the integer to override
      */
-    public abstract void clearNamedIntegerOverride(String integerName);
+    public abstract void clearNamedIntegerOverride(@NonNull String integerName);
 
     /**
      * Support Collections by registering this collection
@@ -177,20 +182,20 @@
      * @param id id of the collection
      * @param collection the collection under this id
      */
-    public abstract void addCollection(int id, ArrayAccess collection);
+    public abstract void addCollection(int id, @NonNull ArrayAccess collection);
 
-    public abstract void putDataMap(int id, DataMap map);
+    public abstract void putDataMap(int id, @NonNull DataMap map);
 
-    public abstract DataMap getDataMap(int id);
+    public abstract @Nullable DataMap getDataMap(int id);
 
-    public abstract void runAction(int id, String metadata);
+    public abstract void runAction(int id, @NonNull String metadata);
 
     // TODO: we might add an interface to group all valid parameter types
     public abstract void runNamedAction(int textId, Object value);
 
-    public abstract void putObject(int mId, Object command);
+    public abstract void putObject(int mId, @NonNull Object command);
 
-    public abstract Object getObject(int mId);
+    public abstract @Nullable Object getObject(int mId);
 
     public void addTouchListener(TouchListener touchExpression) {}
 
@@ -220,11 +225,11 @@
         this.mTheme = theme;
     }
 
-    public ContextMode getMode() {
+    public @NonNull ContextMode getMode() {
         return mMode;
     }
 
-    public void setMode(ContextMode mode) {
+    public void setMode(@NonNull ContextMode mode) {
         this.mMode = mode;
     }
 
@@ -233,11 +238,11 @@
         return mPaintContext;
     }
 
-    public void setPaintContext(PaintContext paintContext) {
+    public void setPaintContext(@NonNull PaintContext paintContext) {
         this.mPaintContext = paintContext;
     }
 
-    public CoreDocument getDocument() {
+    public @Nullable CoreDocument getDocument() {
         return mDocument;
     }
 
@@ -253,7 +258,7 @@
         this.mDebug = debug;
     }
 
-    public void setDocument(CoreDocument document) {
+    public void setDocument(@NonNull CoreDocument document) {
         this.mDocument = document;
     }
 
@@ -310,11 +315,14 @@
      * Save a bitmap under an imageId
      *
      * @param imageId the id of the image
+     * @param encoding how the data is encoded 0 = png, 1 = raw, 2 = url
+     * @param type the type of the data 0 = RGBA 8888, 1 = 888, 2 = 8 gray
      * @param width the width of the image
      * @param height the height of the image
      * @param bitmap the bytes that represent the image
      */
-    public abstract void loadBitmap(int imageId, int width, int height, byte[] bitmap);
+    public abstract void loadBitmap(
+            int imageId, short encoding, short type, int width, int height, @NonNull byte[] bitmap);
 
     /**
      * Save a string under a given id
@@ -322,7 +330,7 @@
      * @param id the id of the string
      * @param text the value to set
      */
-    public abstract void loadText(int id, String text);
+    public abstract void loadText(int id, @NonNull String text);
 
     /**
      * Get a string given an id
@@ -330,7 +338,7 @@
      * @param id the id of the string
      * @return
      */
-    public abstract String getText(int id);
+    public abstract @Nullable String getText(int id);
 
     /**
      * Load a float
@@ -373,12 +381,12 @@
     public abstract void overrideText(int id, int valueId);
 
     /**
-     * Load an animated float associated with an id Todo: Remove?
+     * Load an animated float associated with an id Todo: Remove? cc @hoford
      *
      * @param id the id of the float
      * @param animatedFloat The animated float
      */
-    public abstract void loadAnimatedFloat(int id, FloatExpression animatedFloat);
+    public abstract void loadAnimatedFloat(int id, @NonNull FloatExpression animatedFloat);
 
     /**
      * Save a shader under and ID
@@ -386,7 +394,7 @@
      * @param id the id of the Shader
      * @param value the shader
      */
-    public abstract void loadShader(int id, ShaderData value);
+    public abstract void loadShader(int id, @NonNull ShaderData value);
 
     /**
      * Get a float given an id
@@ -418,7 +426,7 @@
      * @param id track when this id changes value
      * @param variableSupport call back when value changes
      */
-    public abstract void listensTo(int id, VariableSupport variableSupport);
+    public abstract void listensTo(int id, @NonNull VariableSupport variableSupport);
 
     /**
      * Notify commands with variables have changed
@@ -433,6 +441,7 @@
      * @param id get a shader given the id
      * @return The shader
      */
+    @Nullable
     public abstract ShaderData getShader(int id);
 
     public static final int ID_CONTINUOUS_SEC = 1;
@@ -467,6 +476,10 @@
 
     public static final int ID_LIGHT = 26;
 
+    public static final int ID_DENSITY = 27;
+
+    public static final float FLOAT_DENSITY = Utils.asNan(ID_DENSITY);
+
     /** CONTINUOUS_SEC is seconds from midnight looping every hour 0-3600 */
     public static final float FLOAT_CONTINUOUS_SEC = Utils.asNan(ID_CONTINUOUS_SEC);
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/SerializableToString.java b/core/java/com/android/internal/widget/remotecompose/core/SerializableToString.java
index 8f9741d..79ac789 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/SerializableToString.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/SerializableToString.java
@@ -15,8 +15,10 @@
  */
 package com.android.internal.widget.remotecompose.core;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
 
 public interface SerializableToString {
-    void serializeToString(int indent, StringSerializer serializer);
+    void serializeToString(int indent, @NonNull StringSerializer serializer);
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/VariableSupport.java b/core/java/com/android/internal/widget/remotecompose/core/VariableSupport.java
index 51e58a1..e9fa897 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/VariableSupport.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/VariableSupport.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core;
 
+import android.annotation.NonNull;
+
 /**
  * Interface for operators that interact with variables Through this they register to listen to
  * particular variables and are notified when they change
@@ -26,12 +28,12 @@
      *
      * @param context
      */
-    void registerListening(RemoteContext context);
+    void registerListening(@NonNull RemoteContext context);
 
     /**
      * Called to be notified that the variables you are interested have changed.
      *
      * @param context
      */
-    void updateVariables(RemoteContext context);
+    void updateVariables(@NonNull RemoteContext context);
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
index 738e42b..a64b706 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
@@ -23,7 +23,7 @@
 public class WireBuffer {
     private static final int BUFFER_SIZE = 1024 * 1024 * 1;
     int mMaxSize;
-    byte[] mBuffer;
+    @NonNull byte[] mBuffer;
     int mIndex = 0;
     int mStartingIndex = 0;
     int mSize = 0;
@@ -44,7 +44,7 @@
         }
     }
 
-    public byte[] getBuffer() {
+    public @NonNull byte[] getBuffer() {
         return mBuffer;
     }
 
@@ -168,14 +168,14 @@
         return java.lang.Double.longBitsToDouble(readLong());
     }
 
-    public byte[] readBuffer() {
+    public @NonNull byte[] readBuffer() {
         int count = readInt();
         byte[] b = Arrays.copyOfRange(mBuffer, mIndex, mIndex + count);
         mIndex += count;
         return b;
     }
 
-    public byte[] readBuffer(int maxSize) {
+    public @NonNull byte[] readBuffer(int maxSize) {
         int count = readInt();
         if (count < 0 || count > maxSize) {
             throw new RuntimeException(
diff --git a/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentationBuilder.java b/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentationBuilder.java
index f6dfe2e..0174ce5 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentationBuilder.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentationBuilder.java
@@ -15,10 +15,14 @@
  */
 package com.android.internal.widget.remotecompose.core.documentation;
 
+import android.annotation.NonNull;
+
 public interface DocumentationBuilder {
-    void add(String value);
+    void add(@NonNull String value);
 
-    DocumentedOperation operation(String category, int id, String name);
+    @NonNull
+    DocumentedOperation operation(@NonNull String category, int id, @NonNull String name);
 
-    DocumentedOperation wipOperation(String category, int id, String name);
+    @NonNull
+    DocumentedOperation wipOperation(@NonNull String category, int id, @NonNull String name);
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentedCompanionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentedCompanionOperation.java
index 4b84291..2806a5e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentedCompanionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentedCompanionOperation.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.documentation;
 
+import android.annotation.NonNull;
+
 public interface DocumentedCompanionOperation {
-    void documentation(DocumentationBuilder doc);
+    void documentation(@NonNull DocumentationBuilder doc);
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentedOperation.java b/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentedOperation.java
index 5edecaa..bfab623 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentedOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentedOperation.java
@@ -16,6 +16,7 @@
 package com.android.internal.widget.remotecompose.core.documentation;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 
 import java.util.ArrayList;
 
@@ -34,13 +35,13 @@
     public static final int FLOAT_ARRAY = 10;
     public static final int INT_ARRAY = 11;
 
-    String mCategory;
+    @NonNull final String mCategory;
     int mId;
-    String mName;
-    String mDescription;
+    @NonNull final String mName;
+    @NonNull String mDescription = "";
 
     boolean mWIP;
-    String mTextExamples;
+    @Nullable String mTextExamples;
 
     @NonNull ArrayList<StringPair> mExamples = new ArrayList<>();
     @NonNull ArrayList<OperationField> mFields = new ArrayList<>();
@@ -77,14 +78,15 @@
         return "UNKNOWN";
     }
 
-    public DocumentedOperation(String category, int id, String name, boolean wip) {
+    public DocumentedOperation(
+            @NonNull String category, int id, @NonNull String name, boolean wip) {
         mCategory = category;
         mId = id;
         mName = name;
         mWIP = wip;
     }
 
-    public DocumentedOperation(String category, int id, String name) {
+    public DocumentedOperation(@NonNull String category, int id, @NonNull String name) {
         this(category, id, name, false);
     }
 
@@ -93,7 +95,7 @@
         return mFields;
     }
 
-    public String getCategory() {
+    public @NonNull String getCategory() {
         return mCategory;
     }
 
@@ -101,6 +103,7 @@
         return mId;
     }
 
+    @NonNull
     public String getName() {
         return mName;
     }
@@ -126,10 +129,12 @@
         return size;
     }
 
+    @Nullable
     public String getDescription() {
         return mDescription;
     }
 
+    @Nullable
     public String getTextExamples() {
         return mTextExamples;
     }
@@ -148,19 +153,20 @@
     }
 
     @NonNull
-    public DocumentedOperation field(int type, String name, String description) {
+    public DocumentedOperation field(int type, @NonNull String name, @NonNull String description) {
         mFields.add(new OperationField(type, name, description));
         return this;
     }
 
     @NonNull
-    public DocumentedOperation field(int type, String name, String varSize, String description) {
+    public DocumentedOperation field(
+            int type, @NonNull String name, @NonNull String varSize, @NonNull String description) {
         mFields.add(new OperationField(type, name, varSize, description));
         return this;
     }
 
     @NonNull
-    public DocumentedOperation possibleValues(String name, int value) {
+    public DocumentedOperation possibleValues(@NonNull String name, int value) {
         if (!mFields.isEmpty()) {
             mFields.get(mFields.size() - 1).possibleValue(name, "" + value);
         }
@@ -168,19 +174,19 @@
     }
 
     @NonNull
-    public DocumentedOperation description(String description) {
+    public DocumentedOperation description(@NonNull String description) {
         mDescription = description;
         return this;
     }
 
     @NonNull
-    public DocumentedOperation examples(String examples) {
+    public DocumentedOperation examples(@NonNull String examples) {
         mTextExamples = examples;
         return this;
     }
 
     @NonNull
-    public DocumentedOperation exampleImage(String name, String imagePath) {
+    public DocumentedOperation exampleImage(@NonNull String name, @NonNull String imagePath) {
         mExamples.add(new StringPair(name, imagePath));
         return this;
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/documentation/OperationField.java b/core/java/com/android/internal/widget/remotecompose/core/documentation/OperationField.java
index cbb5ca9..9febcfe 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/documentation/OperationField.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/documentation/OperationField.java
@@ -21,20 +21,21 @@
 import java.util.ArrayList;
 
 public class OperationField {
-    int mType;
-    String mName;
-    String mDescription;
+    final int mType;
+    @NonNull final String mName;
+    @NonNull final String mDescription;
     @Nullable String mVarSize = null;
 
     @NonNull ArrayList<StringPair> mPossibleValues = new ArrayList<>();
 
-    public OperationField(int type, String name, String description) {
+    public OperationField(int type, @NonNull String name, @NonNull String description) {
         mType = type;
         mName = name;
         mDescription = description;
     }
 
-    public OperationField(int type, String name, String varSize, String description) {
+    public OperationField(
+            int type, @NonNull String name, @Nullable String varSize, @NonNull String description) {
         mType = type;
         mName = name;
         mDescription = description;
@@ -45,10 +46,12 @@
         return mType;
     }
 
+    @NonNull
     public String getName() {
         return mName;
     }
 
+    @NonNull
     public String getDescription() {
         return mDescription;
     }
@@ -58,7 +61,7 @@
         return mPossibleValues;
     }
 
-    public void possibleValue(String name, String value) {
+    public void possibleValue(@NonNull String name, @NonNull String value) {
         mPossibleValues.add(new StringPair(name, value));
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/documentation/StringPair.java b/core/java/com/android/internal/widget/remotecompose/core/documentation/StringPair.java
index 5b0cedb..c1d8858 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/documentation/StringPair.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/documentation/StringPair.java
@@ -15,20 +15,22 @@
  */
 package com.android.internal.widget.remotecompose.core.documentation;
 
-public class StringPair {
-    String mName;
-    String mValue;
+import android.annotation.NonNull;
 
-    StringPair(String name, String value) {
+public class StringPair {
+    final @NonNull String mName;
+    final @NonNull String mValue;
+
+    StringPair(@NonNull String name, @NonNull String value) {
         mName = name;
         mValue = value;
     }
 
-    public String getName() {
+    public @NonNull String getName() {
         return mName;
     }
 
-    public String getValue() {
+    public @NonNull String getValue() {
         return mValue;
     }
 }
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 8da0e18..9480076 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
@@ -17,6 +17,7 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT_ARRAY;
+import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.SHORT;
 
 import android.annotation.NonNull;
 
@@ -41,10 +42,19 @@
     int mImageId;
     int mImageWidth;
     int mImageHeight;
-    byte[] mBitmap;
+    short mType;
+    short mEncoding;
+    @NonNull final byte[] mBitmap;
     public static final int MAX_IMAGE_DIMENSION = 8000;
+    public static final short ENCODING_INLINE = 0;
+    public static final short ENCODING_URL = 1;
+    public static final short ENCODING_FILE = 2;
+    public static final short TYPE_PNG_8888 = 0;
+    public static final short TYPE_PNG = 1;
+    public static final short TYPE_RAW8 = 2;
+    public static final short TYPE_RAW8888 = 3;
 
-    public BitmapData(int imageId, int width, int height, byte[] bitmap) {
+    public BitmapData(int imageId, int width, int height, @NonNull byte[] bitmap) {
         this.mImageId = imageId;
         this.mImageWidth = width;
         this.mImageHeight = height;
@@ -92,6 +102,23 @@
         buffer.writeBuffer(bitmap);
     }
 
+    public static void apply(
+            @NonNull WireBuffer buffer,
+            int imageId,
+            short type,
+            short width,
+            short encoding,
+            short height,
+            @NonNull byte[] bitmap) {
+        buffer.start(OP_CODE);
+        buffer.writeInt(imageId);
+        int w = (((int) type) << 16) | width;
+        int h = (((int) encoding) << 16) | height;
+        buffer.writeInt(w);
+        buffer.writeInt(h);
+        buffer.writeBuffer(bitmap);
+    }
+
     public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int imageId = buffer.readInt();
         int width = buffer.readInt();
@@ -110,19 +137,22 @@
         doc.operation("Data Operations", OP_CODE, CLASS_NAME)
                 .description("Bitmap data")
                 .field(DocumentedOperation.INT, "id", "id of bitmap data")
+                .field(SHORT, "type", "width of the image")
+                .field(SHORT, "width", "width of the image")
+                .field(SHORT, "encoding", "height of the image")
                 .field(INT, "width", "width of the image")
-                .field(INT, "height", "height of the image")
+                .field(SHORT, "height", "height of the image")
                 .field(INT_ARRAY, "values", "length", "Array of ints");
     }
 
     @Override
     public void apply(@NonNull RemoteContext context) {
-        context.loadBitmap(mImageId, mImageWidth, mImageHeight, mBitmap);
+        context.loadBitmap(mImageId, mEncoding, mType, mImageWidth, mImageHeight, mBitmap);
     }
 
     @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@NonNull String indent) {
         return indent + toString();
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java
index 83d0ac7..310b194 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java
@@ -109,7 +109,7 @@
 
     @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@NonNull String indent) {
         return indent + toString();
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorConstant.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorConstant.java
index 929c9a60..34e93f5 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorConstant.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorConstant.java
@@ -93,7 +93,7 @@
 
     @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@NonNull String indent) {
         return indent + toString();
     }
 }
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
index 3d840c5..c947d11 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java
@@ -255,7 +255,7 @@
 
     @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@NonNull String indent) {
         return indent + toString();
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ComponentValue.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ComponentValue.java
index 142c97b2..b0ccd187 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ComponentValue.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ComponentValue.java
@@ -18,7 +18,6 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
@@ -75,7 +74,7 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         // Nothing
     }
 
@@ -123,9 +122,9 @@
         buffer.writeInt(valueId);
     }
 
-    @Nullable
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@NonNull String indent) {
         return null;
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java
index ba02b91..bfaf139 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java
@@ -35,17 +35,17 @@
 public class DataListFloat implements VariableSupport, ArrayAccess, Operation {
     private static final int OP_CODE = Operations.FLOAT_LIST;
     private static final String CLASS_NAME = "IdListData";
-    int mId;
-    float[] mValues;
+    private final int mId;
+    @NonNull private final float[] mValues;
     private static final int MAX_FLOAT_ARRAY = 2000;
 
-    public DataListFloat(int id, float[] values) {
+    public DataListFloat(int id, @NonNull float[] values) {
         mId = id;
         mValues = values;
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         // TODO add support for variables in arrays
     }
 
@@ -103,7 +103,7 @@
 
     @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@NonNull String indent) {
         return indent + toString();
     }
 
@@ -117,6 +117,7 @@
         return mValues[index];
     }
 
+    @NonNull
     @Override
     public float[] getFloats() {
         return mValues;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java
index b9820f8..9b286b9 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java
@@ -19,6 +19,7 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT_ARRAY;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
@@ -35,20 +36,20 @@
 public class DataListIds implements VariableSupport, ArrayAccess, Operation {
     private static final int OP_CODE = Operations.ID_LIST;
     private static final String CLASS_NAME = "IdListData";
-    int mId;
-    int[] mIds;
+    private final int mId;
+    @NonNull private final int[] mIds;
     private static final int MAX_LIST = 2000;
 
-    public DataListIds(int id, int[] ids) {
+    public DataListIds(int id, @NonNull int[] ids) {
         mId = id;
         mIds = ids;
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {}
+    public void updateVariables(@NonNull RemoteContext context) {}
 
     @Override
-    public void registerListening(RemoteContext context) {}
+    public void registerListening(@NonNull RemoteContext context) {}
 
     @Override
     public void write(@NonNull WireBuffer buffer) {
@@ -94,7 +95,7 @@
 
     @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@NonNull String indent) {
         return indent + toString();
     }
 
@@ -113,6 +114,7 @@
         return mIds[index];
     }
 
+    @Nullable
     @Override
     public float[] getFloats() {
         return null;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java
index fb559bb..643afc8 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java
@@ -19,6 +19,7 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.UTF8;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
@@ -34,7 +35,7 @@
     private static final int OP_CODE = Operations.ID_MAP;
     private static final String CLASS_NAME = "DataMapIds";
     int mId;
-    DataMap mDataMap;
+    final DataMap mDataMap;
 
     private static final int MAX_MAP = 2000;
 
@@ -44,6 +45,7 @@
     public static final byte TYPE_LONG = 3;
     public static final byte TYPE_BOOLEAN = 4;
 
+    @NonNull
     private String typeString(byte type) {
         switch (type) {
             case TYPE_STRING:
@@ -60,7 +62,7 @@
         return "?";
     }
 
-    public DataMapIds(int id, String[] names, byte[] types, int[] ids) {
+    public DataMapIds(int id, @NonNull String[] names, @NonNull byte[] types, @NonNull int[] ids) {
         mId = id;
         mDataMap = new DataMap(names, types, ids);
     }
@@ -88,7 +90,11 @@
     }
 
     public static void apply(
-            @NonNull WireBuffer buffer, int id, @NonNull String[] names, byte[] type, int[] ids) {
+            @NonNull WireBuffer buffer,
+            int id,
+            @NonNull String[] names,
+            @Nullable byte[] type, // todo: can we make this not nullable?
+            @NonNull int[] ids) {
         buffer.start(OP_CODE);
         buffer.writeInt(id);
         buffer.writeInt(names.length);
@@ -128,7 +134,7 @@
 
     @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@NonNull String indent) {
         return indent + toString();
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapLookup.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapLookup.java
index fb5e5d1..eae532c 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapLookup.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapLookup.java
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -50,10 +52,11 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mId, mDataMapId, mStringId);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "DataMapLookup[" + mId + "] = " + Utils.idString(mDataMapId) + " " + mStringId;
@@ -64,6 +67,7 @@
      *
      * @return the name of the class
      */
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -85,7 +89,7 @@
      * @param dataMapId the map to extract from
      * @param keyStringId the map to extract from
      */
-    public static void apply(WireBuffer buffer, int id, int dataMapId, int keyStringId) {
+    public static void apply(@NonNull WireBuffer buffer, int id, int dataMapId, int keyStringId) {
         buffer.start(OP_CODE);
         buffer.writeInt(id);
         buffer.writeInt(dataMapId);
@@ -98,14 +102,14 @@
      * @param buffer buffer
      * @param operations the created command is added to the list
      */
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
         int mapId = buffer.readInt();
         int stringId = buffer.readInt();
         operations.add(new DataMapLookup(id, mapId, stringId));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, CLASS_NAME)
                 .description("A float and its associated id")
                 .field(INT, "id", "id of float")
@@ -114,7 +118,7 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         String str = context.getText(mStringId);
         DataMap data = context.getDataMap(mDataMapId);
         int pos = data.getPos(str);
@@ -141,8 +145,9 @@
         }
     }
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@NonNull String indent) {
         return indent + toString();
     }
 }
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
index 984599e..c1e2e66 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java
@@ -60,11 +60,11 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         write(buffer, mV1, mV2);
     }
 
-    protected abstract void write(WireBuffer buffer, float v1, float v2);
+    protected abstract void write(@NonNull WireBuffer buffer, float v1, float v2);
 
     protected interface Maker {
         DrawBase2 create(float v1, float v2);
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 825da52..6fedea3 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
@@ -70,11 +70,11 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         write(buffer, mV1, mV2, mV3);
     }
 
-    protected abstract void write(WireBuffer buffer, float v1, float v2, float v3);
+    protected abstract void write(@NonNull WireBuffer buffer, float v1, float v2, float v3);
 
     interface Maker {
         DrawBase3 create(float v1, float v2, float v3);
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
index a23bcb9..aa9cc68 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java
@@ -77,11 +77,12 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         write(buffer, mX1, mY1, mX2, mY2);
     }
 
-    protected abstract void write(WireBuffer buffer, float v1, float v2, float v3, float v4);
+    protected abstract void write(
+            @NonNull WireBuffer buffer, float v1, float v2, float v3, float v4);
 
     protected interface Maker {
         DrawBase4 create(float v1, float v2, float v3, float v4);
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
index 68c9f8c..6c288a35 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java
@@ -91,12 +91,12 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         write(buffer, mV1, mV2, mV3, mV4, mV5, mV6);
     }
 
     protected abstract void write(
-            WireBuffer buffer, float v1, float v2, float v3, float v4, float v5, float v6);
+            @NonNull WireBuffer buffer, float v1, float v2, float v3, float v4, float v5, float v6);
 
     @NonNull
     @Override
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java
index e20bcd2..e9f81d5 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java
@@ -46,6 +46,7 @@
     int mContentDescId;
     float mScaleFactor, mOutScaleFactor;
     int mScaleType;
+    int mMode;
 
     @NonNull ImageScaling mScaling = new ImageScaling();
     public static final int SCALE_NONE = ImageScaling.SCALE_NONE;
@@ -79,7 +80,8 @@
         mOutDstTop = mDstTop = dstTop;
         mOutDstRight = mDstRight = dstRight;
         mOutDstBottom = mDstBottom = dstBottom;
-        mScaleType = type;
+        mScaleType = type & 0xFF;
+        mMode = type >> 8;
         mOutScaleFactor = mScaleFactor = scale;
         this.mContentDescId = cdId;
     }
@@ -308,8 +310,14 @@
                 mOutScaleFactor);
         context.save();
         context.clipRect(mOutDstLeft, mOutDstTop, mOutDstRight, mOutDstBottom);
+
+        int imageId = mImageId;
+        if ((mMode & 0x1) != 0) {
+            imageId = context.getContext().getInteger(imageId);
+        }
+
         context.drawBitmap(
-                mImageId,
+                imageId,
                 (int) mOutSrcLeft,
                 (int) mOutSrcTop,
                 (int) mOutSrcRight,
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
index 89390ac..17aaf3b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java
@@ -94,7 +94,7 @@
 
     @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@NonNull 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
index e1c6c25..eef9746 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java
@@ -33,6 +33,7 @@
 import com.android.internal.widget.remotecompose.core.operations.utilities.AnimatedFloatExpression;
 import com.android.internal.widget.remotecompose.core.operations.utilities.NanMap;
 import com.android.internal.widget.remotecompose.core.operations.utilities.easing.FloatAnimation;
+import com.android.internal.widget.remotecompose.core.operations.utilities.easing.SpringStopEngine;
 
 import java.util.List;
 
@@ -45,21 +46,26 @@
     private static final int OP_CODE = Operations.ANIMATED_FLOAT;
     private static final String CLASS_NAME = "FloatExpression";
     public int mId;
-    public float[] mSrcValue;
-    public float[] mSrcAnimation;
-    public FloatAnimation mFloatAnimation;
-    public float[] mPreCalcValue;
+    @NonNull public float[] mSrcValue;
+    @Nullable public float[] mSrcAnimation;
+    @Nullable public FloatAnimation mFloatAnimation;
+    @Nullable private SpringStopEngine mSpring;
+    @Nullable public float[] mPreCalcValue;
     private float mLastChange = Float.NaN;
     private float mLastCalculatedValue = Float.NaN;
     @NonNull AnimatedFloatExpression mExp = new AnimatedFloatExpression();
     public static final int MAX_EXPRESSION_SIZE = 32;
 
-    public FloatExpression(int id, float[] value, float[] animation) {
+    public FloatExpression(int id, @NonNull float[] value, @Nullable float[] animation) {
         this.mId = id;
         this.mSrcValue = value;
         this.mSrcAnimation = animation;
         if (mSrcAnimation != null) {
-            mFloatAnimation = new FloatAnimation(mSrcAnimation);
+            if (mSrcAnimation.length > 4 && mSrcAnimation[0] == 0) {
+                mSpring = new SpringStopEngine(mSrcAnimation);
+            } else {
+                mFloatAnimation = new FloatAnimation(mSrcAnimation);
+            }
         }
     }
 
@@ -75,12 +81,23 @@
             if (Float.isNaN(v)
                     && !AnimatedFloatExpression.isMathOperator(v)
                     && !NanMap.isDataVariable(v)) {
+                int id = Utils.idFromNan(v);
                 float newValue = context.getFloat(Utils.idFromNan(v));
+
+                // TODO: rethink the lifecycle for variable updates
+                if (id == RemoteContext.ID_DENSITY && newValue == 0f) {
+                    newValue = 1f;
+                }
                 if (mFloatAnimation != null) {
                     if (mPreCalcValue[i] != newValue) {
                         value_changed = true;
                         mPreCalcValue[i] = newValue;
                     }
+                } else if (mSpring != null) {
+                    if (mPreCalcValue[i] != newValue) {
+                        value_changed = true;
+                        mPreCalcValue[i] = newValue;
+                    }
                 } else {
                     mPreCalcValue[i] = newValue;
                 }
@@ -106,6 +123,8 @@
                 mFloatAnimation.setInitialValue(mFloatAnimation.getTargetValue());
             }
             mFloatAnimation.setTargetValue(v);
+        } else if (value_changed && mSpring != null) {
+            mSpring.setTargetValue(v);
         }
     }
 
@@ -130,6 +149,9 @@
         if (mFloatAnimation != null) {
             float f = mFloatAnimation.get(t - mLastChange);
             context.loadFloat(mId, f);
+        } else if (mSpring != null) {
+            float f = mSpring.get(t - mLastChange);
+            context.loadFloat(mId, f);
         } else {
             context.loadFloat(
                     mId,
@@ -137,6 +159,21 @@
         }
     }
 
+    /**
+     * Evaluate the expression
+     *
+     * @param context current context
+     * @return the resulting value
+     */
+    public float evaluate(@NonNull RemoteContext context) {
+        updateVariables(context);
+        float t = context.getAnimationTime();
+        if (Float.isNaN(mLastChange)) {
+            mLastChange = t;
+        }
+        return mExp.eval(context.getCollectionsAccess(), mPreCalcValue, mPreCalcValue.length);
+    }
+
     @Override
     public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mId, mSrcValue, mSrcAnimation);
@@ -256,7 +293,7 @@
 
     @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@NonNull String indent) {
         return indent + toString();
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java
index 1979bc5..009aa03 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java
@@ -111,7 +111,7 @@
 
     @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@NonNull String indent) {
         return toString();
     }
 
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
index 6375f00..c49f74d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java
@@ -19,6 +19,7 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT_ARRAY;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
@@ -43,13 +44,13 @@
     public int mId;
     private int mMask;
     private int mPreMask;
-    public int[] mSrcValue;
-    public int[] mPreCalcValue;
+    @NonNull public final int[] mSrcValue;
+    @Nullable public int[] mPreCalcValue;
     private float mLastChange = Float.NaN;
     public static final int MAX_SIZE = 320;
     @NonNull IntegerExpressionEvaluator mExp = new IntegerExpressionEvaluator();
 
-    public IntegerExpression(int id, int mask, int[] value) {
+    public IntegerExpression(int id, int mask, @NonNull int[] value) {
         this.mId = id;
         this.mMask = mask;
         this.mSrcValue = value;
@@ -188,7 +189,7 @@
 
     @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@NonNull 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 6a620e5..4e7ee4d 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 @@
         apply(buffer);
     }
 
-    public static void read(WireBuffer buffer, @NonNull List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         MatrixRestore op = new MatrixRestore();
         operations.add(op);
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java
index 1880b19..09f54a5 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java
@@ -41,7 +41,7 @@
         return "MatrixSave;";
     }
 
-    public static void read(WireBuffer buffer, @NonNull List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         MatrixSave op = new MatrixSave();
         operations.add(op);
     }
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
index 6310521e..5ca91af 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java
@@ -33,16 +33,16 @@
 public class NamedVariable implements Operation {
     private static final int OP_CODE = Operations.NAMED_VARIABLE;
     private static final String CLASS_NAME = "NamedVariable";
-    public int mVarId;
-    public String mVarName;
-    public int mVarType;
+    public final int mVarId;
+    public final @NonNull String mVarName;
+    public final int mVarType;
     public static final int MAX_STRING_SIZE = 4000;
     public static final int COLOR_TYPE = 2;
     public static final int FLOAT_TYPE = 1;
     public static final int STRING_TYPE = 0;
     public static final int IMAGE_TYPE = 3;
 
-    public NamedVariable(int varId, int varType, String name) {
+    public NamedVariable(int varId, int varType, @NonNull String name) {
         this.mVarId = varId;
         this.mVarType = varType;
         this.mVarName = name;
@@ -111,7 +111,7 @@
 
     @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@NonNull 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 527d5610..b24df8a 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
@@ -90,7 +90,7 @@
 
     @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@NonNull String indent) {
         return indent + toString();
     }
 
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 06a1fec..509f362 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
@@ -73,7 +73,7 @@
 
     @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@NonNull String indent) {
         return pathString(mFloatPath);
     }
 
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 6ff9ad7..8494126 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
@@ -196,7 +196,7 @@
 
     @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@NonNull String indent) {
         return toString();
     }
 
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 c2d62a7..109945f 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
@@ -60,7 +60,7 @@
 
     @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@NonNull String indent) {
         return toString();
     }
 
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
index ae61c3a..e967ff4 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java
@@ -48,7 +48,7 @@
     int mShaderID; // allows shaders to be referenced by number
     @Nullable HashMap<String, float[]> mUniformRawFloatMap = null;
     @Nullable HashMap<String, float[]> mUniformFloatMap = null;
-    @Nullable HashMap<String, int[]> mUniformIntMap = null;
+    @Nullable HashMap<String, int[]> mUniformIntMap;
     @Nullable HashMap<String, Integer> mUniformBitmapMap = null;
 
     public ShaderData(
@@ -104,8 +104,8 @@
      * @param name name of uniform
      * @return value of uniform
      */
-    public float[] getUniformFloats(String name) {
-        return mUniformFloatMap.get(name);
+    public @NonNull float[] getUniformFloats(@NonNull String name) {
+        return mUniformFloatMap != null ? mUniformFloatMap.get(name) : new float[0];
     }
 
     /**
@@ -125,8 +125,8 @@
      * @param name Name of uniform
      * @return value of uniform
      */
-    public int[] getUniformInts(String name) {
-        return mUniformIntMap.get(name);
+    public @NonNull int[] getUniformInts(@NonNull String name) {
+        return mUniformIntMap != null ? mUniformIntMap.get(name) : new int[0];
     }
 
     /**
@@ -146,8 +146,10 @@
      * @param name Name of bitmap uniform
      * @return Bitmap ID
      */
-    public int getUniformBitmapId(String name) {
-        return mUniformBitmapMap.get(name);
+    public int getUniformBitmapId(@NonNull String name) {
+        return mUniformBitmapMap != null
+                ? mUniformBitmapMap.get(name)
+                : -1; // TODO: what is the proper return value here? -- bbade@
     }
 
     @Override
@@ -169,7 +171,7 @@
 
     @Override
     public void updateVariables(@NonNull RemoteContext context) {
-        for (String name : mUniformRawFloatMap.keySet()) {
+        for (String name : mUniformRawFloatMap.keySet()) { // TODO: potential npe
             float[] value = mUniformRawFloatMap.get(name);
             float[] out = null;
             for (int i = 0; i < value.length; i++) {
@@ -186,7 +188,7 @@
 
     @Override
     public void registerListening(@NonNull RemoteContext context) {
-        for (String name : mUniformRawFloatMap.keySet()) {
+        for (String name : mUniformRawFloatMap.keySet()) { // TODO: potential npe
             float[] value = mUniformRawFloatMap.get(name);
             for (float v : value) {
                 if (Float.isNaN(v)) {
@@ -340,7 +342,7 @@
 
     @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@NonNull 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 dbaef7e..ade008e 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
@@ -34,11 +34,11 @@
 public class TextData implements Operation, SerializableToString {
     private static final int OP_CODE = Operations.DATA_TEXT;
     private static final String CLASS_NAME = "TextData";
-    public int mTextId;
-    public String mText;
+    public final int mTextId;
+    @NonNull public final String mText;
     public static final int MAX_STRING_SIZE = 4000;
 
-    public TextData(int textId, String text) {
+    public TextData(int textId, @NonNull String text) {
         this.mTextId = textId;
         this.mText = text;
     }
@@ -90,7 +90,7 @@
 
     @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@NonNull String indent) {
         return indent + toString();
     }
 
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
index fb5087f..865ee81f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java
@@ -185,7 +185,7 @@
 
     @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@NonNull String indent) {
         return indent + toString();
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLength.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLength.java
index e148fb9..6ff687b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLength.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLength.java
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -38,16 +40,17 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mLengthId, mTextId);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return CLASS_NAME + "[" + mLengthId + "] = " + mTextId;
     }
 
-    public static String name() {
+    public static @NonNull String name() {
         return CLASS_NAME;
     }
 
@@ -62,19 +65,19 @@
      * @param lengthId the id to output
      * @param textId the id of the text to measure
      */
-    public static void apply(WireBuffer buffer, int lengthId, int textId) {
+    public static void apply(@NonNull WireBuffer buffer, int lengthId, int textId) {
         buffer.start(OP_CODE);
         buffer.writeInt(lengthId);
         buffer.writeInt(textId);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int lengthId = buffer.readInt();
         int textId = buffer.readInt();
         operations.add(new TextLength(lengthId, textId));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, CLASS_NAME)
                 .description("get the length of the text and store in float table")
                 .field(INT, "id", "id of float length")
@@ -82,12 +85,13 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         context.loadFloat(mLengthId, context.getText(mTextId).length());
     }
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@NonNull String indent) {
         return indent + toString();
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java
index 2129edd..cc812a8 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java
@@ -126,7 +126,7 @@
 
     @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@NonNull String indent) {
         return indent + toString();
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java
index ea550cb..74be698 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java
@@ -119,7 +119,7 @@
 
     @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@NonNull String indent) {
         return indent + toString();
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java
index 0281d69..6d48f67 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMeasure.java
@@ -19,6 +19,8 @@
 import static com.android.internal.widget.remotecompose.core.PaintContext.TEXT_MEASURE_MONOSPACE_WIDTH;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -55,16 +57,16 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mId, mTextId, mType);
     }
 
     @Override
-    public String toString() {
+    public @NonNull String toString() {
         return "FloatConstant[" + mId + "] = " + mTextId + " " + mType;
     }
 
-    public static String name() {
+    public static @NonNull String name() {
         return CLASS_NAME;
     }
 
@@ -80,21 +82,21 @@
      * @param textId the id
      * @param type the value of the float
      */
-    public static void apply(WireBuffer buffer, int id, int textId, int type) {
+    public static void apply(@NonNull WireBuffer buffer, int id, int textId, int type) {
         buffer.start(OP_CODE);
         buffer.writeInt(id);
         buffer.writeInt(textId);
         buffer.writeInt(type);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
         int textId = buffer.readInt();
         int type = buffer.readInt();
         operations.add(new TextMeasure(id, textId, type));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, CLASS_NAME)
                 .description("A float and its associated id")
                 .field(INT, "id", "id of float result of the measure")
@@ -102,15 +104,16 @@
                 .field(INT, "type", "type: measure 0=width,1=height");
     }
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@NonNull String indent) {
         return indent + toString();
     }
 
-    float[] mBounds = new float[4];
+    @NonNull float[] mBounds = new float[4];
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         int val = mType & 255;
         int flags = mType >> 8;
         context.getTextBounds(mTextId, 0, -1, flags, mBounds);
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
index fa18b4d..ecd5baa 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java
@@ -102,7 +102,7 @@
 
     @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@NonNull String indent) {
         return indent + toString();
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java
index 1e90ab1..d265070 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java
@@ -68,7 +68,7 @@
 
     @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@NonNull String indent) {
         return indent + toString();
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java
index b25a7f6..1bb7b2a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java
@@ -20,6 +20,8 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.SHORT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -71,6 +73,7 @@
     boolean mWrapMode = false;
     float[] mNotches;
     float[] mStopSpec;
+    float[] mOutStopSpec;
     int mTouchEffects;
     float mVelocityId;
 
@@ -98,6 +101,9 @@
         mOutDefValue = mDefValue = defValue;
         mMode = STOP_ABSOLUTE_POS == stopMode ? 1 : 0;
         mOutMax = mMax = max;
+        if (stopSpec != null) {
+            mOutStopSpec = Arrays.copyOf(stopSpec, stopSpec.length);
+        }
         mTouchEffects = touchEffects;
         mVelocityId = velocityId;
         if (Float.isNaN(min) && Utils.idFromNan(min) == 0) {
@@ -108,10 +114,8 @@
         mStopMode = stopMode;
         mStopSpec = stopSpec;
         if (easingSpec != null) {
-            Utils.log("easingSpec  " + Arrays.toString(easingSpec));
             if (easingSpec.length >= 4) {
                 if (Float.floatToRawIntBits(easingSpec[0]) == 0) {
-                    Utils.log("easingSpec[2]  " + easingSpec[2]);
                     mMaxTime = easingSpec[1];
                     mMaxAcceleration = easingSpec[2];
                     mMaxVelocity = easingSpec[3];
@@ -126,6 +130,9 @@
         if (mPreCalcValue == null || mPreCalcValue.length != mSrcExp.length) {
             mPreCalcValue = new float[mSrcExp.length];
         }
+        if (mOutStopSpec == null || mOutStopSpec.length != mStopSpec.length) {
+            mOutStopSpec = new float[mStopSpec.length];
+        }
         if (Float.isNaN(mMax)) {
             mOutMax = context.getFloat(Utils.idFromNan(mMax));
         }
@@ -150,6 +157,15 @@
                 mPreCalcValue[i] = mSrcExp[i];
             }
         }
+        for (int i = 0; i < mStopSpec.length; i++) {
+            float v = mStopSpec[i];
+            if (Float.isNaN(v)) {
+                float newValue = context.getFloat(Utils.idFromNan(v));
+                mOutStopSpec[i] = newValue;
+            } else {
+                mOutStopSpec[i] = v;
+            }
+        }
         float v = mLastCalculatedValue;
         if (value_changed) { // inputs changed check if output changed
             v = mExp.eval(mPreCalcValue, mPreCalcValue.length);
@@ -181,6 +197,11 @@
                 context.listensTo(Utils.idFromNan(v), this);
             }
         }
+        for (float v : mStopSpec) {
+            if (Float.isNaN(v)) {
+                context.listensTo(Utils.idFromNan(v), this);
+            }
+        }
     }
 
     private float wrap(float pos) {
@@ -211,12 +232,14 @@
             case STOP_INSTANTLY:
                 return pos;
             case STOP_NOTCHES_EVEN:
-                int evenSpacing = (int) mStopSpec[0];
-                float step = (mOutMax - min) / evenSpacing;
+                int evenSpacing = (int) mOutStopSpec[0];
+                float notchMax = (mOutStopSpec.length > 1) ? mOutStopSpec[1] : mOutMax;
+                float step = (notchMax - min) / evenSpacing;
 
                 float notch = min + step * (int) (0.5f + (target - mOutMin) / step);
-
-                notch = Math.max(Math.min(notch, mOutMax), min);
+                if (!mWrapMode) {
+                    notch = Math.max(Math.min(notch, mOutMax), min);
+                }
                 return notch;
             case STOP_NOTCHES_PERCENTS:
                 positions = new float[mStopSpec.length];
@@ -265,7 +288,6 @@
         float next = mCurrentValue;
         mLastValue = next;
 
-        //        System.out.println(mStopMode + "    " + prev + "  -> " + next);
         float min = (mWrapMode) ? 0 : mOutMin;
         float max = mOutMax;
 
@@ -309,7 +331,7 @@
 
     @Override
     public void apply(RemoteContext context) {
-        Component comp = context.lastComponent;
+        Component comp = context.mLastComponent;
         if (comp != null) {
             float x = comp.getX();
             float y = comp.getY();
@@ -329,7 +351,6 @@
         updateVariables(context);
         if (mUnmodified) {
             mCurrentValue = mOutDefValue;
-
             context.loadFloat(mId, wrap(mCurrentValue));
             return;
         }
@@ -337,7 +358,11 @@
             float time = context.getAnimationTime() - mTouchUpTime;
             float value = mEasyTouch.getPos(time);
             mCurrentValue = value;
-            value = wrap(value);
+            if (mWrapMode) {
+                value = wrap(value);
+            } else {
+                value = Math.min(Math.max(value, mOutMin), mOutMax);
+            }
             context.loadFloat(mId, value);
             if (mEasyTouch.getDuration() < time) {
                 mEasingToStop = false;
@@ -410,7 +435,8 @@
         mTouchUpTime = context.getAnimationTime();
 
         float dest = getStopPosition(value, slope);
-        mEasyTouch.config(value, dest, slope, mMaxTime, mMaxAcceleration, mMaxVelocity, null);
+        float time = mMaxTime * Math.abs(dest - value) / (2 * mMaxVelocity);
+        mEasyTouch.config(value, dest, slope, time, mMaxAcceleration, mMaxVelocity, null);
         mEasingToStop = true;
     }
 
@@ -543,7 +569,6 @@
         int stopLen = stopLogic & 0xFFFF;
         int stopMode = stopLogic >> 16;
 
-        Utils.log("stopMode " + stopMode + " stopLen " + stopLen);
         float[] stopsData = new float[stopLen];
         for (int i = 0; i < stopsData.length; i++) {
             stopsData[i] = buffer.readFloat();
@@ -592,8 +617,9 @@
                 .field(FLOAT, "wrapValue", "> [Wrap value] ");
     }
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@NonNull 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 03f7e05..baca3e0 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
@@ -69,7 +69,7 @@
      * @param value
      * @return
      */
-    public static String floatToString(float idvalue, float value) {
+    public static @NonNull String floatToString(float idvalue, float value) {
         if (Float.isNaN(idvalue)) {
             if (idFromNan(value) == 0) {
                 return "NaN";
@@ -85,7 +85,7 @@
      * @param value
      * @return
      */
-    public static String floatToString(float value) {
+    public static @NonNull String floatToString(float value) {
         if (Float.isNaN(value)) {
             if (idFromNan(value) == 0) {
                 return "NaN";
@@ -100,7 +100,7 @@
      *
      * @param str
      */
-    public static void log(String str) {
+    public static void log(@NonNull String str) {
         StackTraceElement s = new Throwable().getStackTrace()[1];
         System.out.println(
                 "("
@@ -119,7 +119,7 @@
      * @param str
      * @param n
      */
-    public static void logStack(String str, int n) {
+    public static void logStack(@NonNull String str, int n) {
         StackTraceElement[] st = new Throwable().getStackTrace();
         for (int i = 1; i < n + 1; i++) {
             StackTraceElement s = st[i];
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ActionOperation.java
index bdc2a886..7f1d101 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ActionOperation.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.CoreDocument;
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -22,8 +24,12 @@
 
 /** Operations representing actions on the document */
 public interface ActionOperation extends Operation {
-    void serializeToString(int indent, StringSerializer serializer);
+    void serializeToString(int indent, @NonNull StringSerializer serializer);
 
     void runAction(
-            RemoteContext context, CoreDocument document, Component component, float x, float y);
+            @NonNull RemoteContext context,
+            @NonNull CoreDocument document,
+            @NonNull Component component,
+            float x,
+            float y);
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/AnimatableValue.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/AnimatableValue.java
index e789710..19f4c2b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/AnimatableValue.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/AnimatableValue.java
@@ -77,4 +77,9 @@
 
         return mValue;
     }
+
+    @Override
+    public String toString() {
+        return "AnimatableValue{mId=" + mId + "}";
+    }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasContent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasContent.java
index 9886518..aa8f7580 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasContent.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasContent.java
@@ -18,6 +18,7 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
@@ -35,7 +36,7 @@
             float y,
             float width,
             float height,
-            Component parent,
+            @Nullable Component parent,
             int animationId) {
         super(parent, componentId, animationId, x, y, width, height);
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java
index b567538..f44e20d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java
@@ -16,7 +16,6 @@
 package com.android.internal.widget.remotecompose.core.operations.layout;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 
 import com.android.internal.widget.remotecompose.core.CoreDocument;
 import com.android.internal.widget.remotecompose.core.Operation;
@@ -80,7 +79,7 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         for (Operation op : mList) {
             if (op instanceof TextData) {
                 op.apply(context);
@@ -90,7 +89,7 @@
 
     @NonNull
     @Override
-    public String deepToString(@Nullable String indent) {
+    public String deepToString(@NonNull String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
@@ -137,7 +136,7 @@
     }
 
     @Override
-    public void layout(RemoteContext context, float width, float height) {
+    public void layout(@NonNull RemoteContext context, float width, float height) {
         mWidth = width;
         mHeight = height;
     }
@@ -154,8 +153,8 @@
 
     @Override
     public void onClick(
-            RemoteContext context,
-            CoreDocument document,
+            @NonNull RemoteContext context,
+            @NonNull CoreDocument document,
             @NonNull Component component,
             float x,
             float y) {
@@ -171,6 +170,7 @@
                 ((ActionOperation) o).runAction(context, document, component, x, y);
             }
         }
+        context.hapticEffect(3);
     }
 
     @NonNull
@@ -182,7 +182,7 @@
         buffer.start(OP_CODE);
     }
 
-    public static void read(WireBuffer buffer, @NonNull List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         operations.add(new ClickModifierOperation());
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java
index f4f4ee2..fbfc796 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java
@@ -50,12 +50,12 @@
     protected float mY;
     protected float mWidth;
     protected float mHeight;
-    protected Component mParent;
+    @Nullable protected Component mParent;
     protected int mAnimationId = -1;
-    public Visibility mVisibility = Visibility.VISIBLE;
-    public Visibility mScheduledVisibility = Visibility.VISIBLE;
+    @NonNull public Visibility mVisibility = Visibility.VISIBLE;
+    @NonNull public Visibility mScheduledVisibility = Visibility.VISIBLE;
     @NonNull public ArrayList<Operation> mList = new ArrayList<>();
-    public PaintOperation mPreTranslate;
+    public PaintOperation mPreTranslate; // todo, can we initialize this here and make it NonNull?
     public boolean mNeedsMeasure = true;
     public boolean mNeedsRepaint = false;
     @Nullable public AnimateMeasure mAnimateMeasure;
@@ -99,6 +99,7 @@
         return mAnimationId;
     }
 
+    @Nullable
     public Component getParent() {
         return mParent;
     }
@@ -160,7 +161,7 @@
     }
 
     public Component(
-            Component parent,
+            @Nullable Component parent,
             int componentId,
             int animationId,
             float x,
@@ -177,7 +178,12 @@
     }
 
     public Component(
-            int componentId, float x, float y, float width, float height, Component parent) {
+            int componentId,
+            float x,
+            float y,
+            float width,
+            float height,
+            @Nullable Component parent) {
         this(parent, componentId, -1, x, y, width, height);
     }
 
@@ -211,7 +217,7 @@
         return mNeedsMeasure;
     }
 
-    public void setParent(Component parent) {
+    public void setParent(@Nullable Component parent) {
         mParent = parent;
     }
 
@@ -222,8 +228,8 @@
      * @param context the current context
      */
     public void updateVariables(@NonNull RemoteContext context) {
-        Component prev = context.lastComponent;
-        context.lastComponent = this;
+        Component prev = context.mLastComponent;
+        context.mLastComponent = this;
 
         if (!mComponentValues.isEmpty()) {
             updateComponentValues(context);
@@ -236,13 +242,21 @@
                 o.apply(context);
             }
         }
-        context.lastComponent = prev;
+        context.mLastComponent = prev;
     }
 
-    public void addComponentValue(ComponentValue v) {
+    public void addComponentValue(@NonNull ComponentValue v) {
         mComponentValues.add(v);
     }
 
+    public float intrinsicWidth() {
+        return getWidth();
+    }
+
+    public float intrinsicHeight() {
+        return getHeight();
+    }
+
     public enum Visibility {
         GONE,
         VISIBLE,
@@ -253,13 +267,13 @@
         if (mVisibility != Visibility.VISIBLE || mParent == null) {
             return mVisibility == Visibility.VISIBLE;
         }
-        if (mParent != null) {
+        if (mParent != null) { // TODO: this is always true -- bbade@
             return mParent.isVisible();
         }
         return true;
     }
 
-    public void setVisibility(Visibility visibility) {
+    public void setVisibility(@NonNull Visibility visibility) {
         if (visibility != mVisibility || visibility != mScheduledVisibility) {
             mScheduledVisibility = visibility;
             invalidateMeasure();
@@ -267,7 +281,7 @@
     }
 
     @Override
-    public boolean suitableForTransition(Operation o) {
+    public boolean suitableForTransition(@NonNull Operation o) {
         if (!(o instanceof Component)) {
             return false;
         }
@@ -291,7 +305,7 @@
 
     @Override
     public void measure(
-            PaintContext context,
+            @NonNull PaintContext context,
             float minWidth,
             float maxWidth,
             float minHeight,
@@ -358,16 +372,27 @@
         return x >= lx1 && x < lx2 && y >= ly1 && y < ly2;
     }
 
-    public void onClick(RemoteContext context, CoreDocument document, float x, float y) {
+    public float getScrollX() {
+        return 0;
+    }
+
+    public float getScrollY() {
+        return 0;
+    }
+
+    public void onClick(
+            @NonNull RemoteContext context, @NonNull CoreDocument document, float x, float y) {
         if (!contains(x, y)) {
             return;
         }
+        float cx = x - getScrollX();
+        float cy = y - getScrollY();
         for (Operation op : mList) {
             if (op instanceof Component) {
-                ((Component) op).onClick(context, document, x, y);
+                ((Component) op).onClick(context, document, cx, cy);
             }
             if (op instanceof ClickHandler) {
-                ((ClickHandler) op).onClick(context, document, this, x, y);
+                ((ClickHandler) op).onClick(context, document, this, cx, cy);
             }
         }
     }
@@ -376,12 +401,14 @@
         if (!contains(x, y)) {
             return;
         }
+        float cx = x - getScrollX();
+        float cy = y - getScrollY();
         for (Operation op : mList) {
             if (op instanceof Component) {
-                ((Component) op).onTouchDown(context, document, x, y);
+                ((Component) op).onTouchDown(context, document, cx, cy);
             }
             if (op instanceof TouchHandler) {
-                ((TouchHandler) op).onTouchDown(context, document, this, x, y);
+                ((TouchHandler) op).onTouchDown(context, document, this, cx, cy);
             }
         }
     }
@@ -391,12 +418,14 @@
         if (!force && !contains(x, y)) {
             return;
         }
+        float cx = x - getScrollX();
+        float cy = y - getScrollY();
         for (Operation op : mList) {
             if (op instanceof Component) {
-                ((Component) op).onTouchUp(context, document, x, y, force);
+                ((Component) op).onTouchUp(context, document, cx, cy, force);
             }
             if (op instanceof TouchHandler) {
-                ((TouchHandler) op).onTouchUp(context, document, this, x, y);
+                ((TouchHandler) op).onTouchUp(context, document, this, cx, cy);
             }
         }
     }
@@ -406,12 +435,31 @@
         if (!force && !contains(x, y)) {
             return;
         }
+        float cx = x - getScrollX();
+        float cy = y - getScrollY();
         for (Operation op : mList) {
             if (op instanceof Component) {
-                ((Component) op).onTouchCancel(context, document, x, y, force);
+                ((Component) op).onTouchCancel(context, document, cx, cy, force);
             }
             if (op instanceof TouchHandler) {
-                ((TouchHandler) op).onTouchCancel(context, document, this, x, y);
+                ((TouchHandler) op).onTouchCancel(context, document, this, cx, cy);
+            }
+        }
+    }
+
+    public void onTouchDrag(
+            RemoteContext context, CoreDocument document, float x, float y, boolean force) {
+        if (!force && !contains(x, y)) {
+            return;
+        }
+        float cx = x - getScrollX();
+        float cy = y - getScrollY();
+        for (Operation op : mList) {
+            if (op instanceof Component) {
+                ((Component) op).onTouchDrag(context, document, cx, cy, force);
+            }
+            if (op instanceof TouchHandler) {
+                ((TouchHandler) op).onTouchDrag(context, document, this, cx, cy);
             }
         }
     }
@@ -480,7 +528,7 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         // nothing
     }
 
@@ -502,7 +550,7 @@
 
     @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@NonNull String indent) {
         StringBuilder builder = new StringBuilder();
         builder.append(indent);
         builder.append(toString());
@@ -602,8 +650,8 @@
         if (mPreTranslate != null) {
             mPreTranslate.paint(context);
         }
-        Component prev = context.getContext().lastComponent;
-        context.getContext().lastComponent = this;
+        Component prev = context.getContext().mLastComponent;
+        context.getContext().mLastComponent = this;
         context.save();
         context.translate(mX, mY);
         if (context.isVisualDebug()) {
@@ -618,7 +666,7 @@
             }
         }
         context.restore();
-        context.getContext().lastComponent = prev;
+        context.getContext().mLastComponent = prev;
     }
 
     public boolean applyAnimationAsNeeded(@NonNull PaintContext context) {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentEnd.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentEnd.java
index f370e20..476b73c 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentEnd.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentEnd.java
@@ -16,7 +16,6 @@
 package com.android.internal.widget.remotecompose.core.operations.layout;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
@@ -40,13 +39,13 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         // nothing
     }
 
     @NonNull
     @Override
-    public String deepToString(@Nullable String indent) {
+    public String deepToString(@NonNull String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
@@ -67,7 +66,7 @@
         return 1 + 4 + 4 + 4;
     }
 
-    public static void read(WireBuffer buffer, @NonNull List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         operations.add(new ComponentEnd());
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStart.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStart.java
index f250d9a..def9f78 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStart.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStart.java
@@ -19,7 +19,6 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
@@ -96,12 +95,12 @@
 
     @NonNull
     @Override
-    public String deepToString(@Nullable String indent) {
+    public String deepToString(@NonNull String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         // nothing
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/DecoratorComponent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/DecoratorComponent.java
index bb43119..d617007 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/DecoratorComponent.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/DecoratorComponent.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.RemoteContext;
 
 /**
@@ -22,5 +24,5 @@
  * measured. Eg borders, background, clips, etc.
  */
 public interface DecoratorComponent {
-    void layout(RemoteContext context, float width, float height);
+    void layout(@NonNull RemoteContext context, float width, float height);
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java
index e0923dfb..0041582 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java
@@ -24,7 +24,9 @@
 import com.android.internal.widget.remotecompose.core.operations.MatrixRestore;
 import com.android.internal.widget.remotecompose.core.operations.MatrixSave;
 import com.android.internal.widget.remotecompose.core.operations.MatrixTranslate;
+import com.android.internal.widget.remotecompose.core.operations.PaintData;
 import com.android.internal.widget.remotecompose.core.operations.TextData;
+import com.android.internal.widget.remotecompose.core.operations.TouchExpression;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentModifiers;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentVisibilityOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.DimensionModifierOperation;
@@ -57,12 +59,14 @@
     protected float mPaddingBottom = 0f;
 
     @NonNull protected ComponentModifiers mComponentModifiers = new ComponentModifiers();
-    @NonNull protected ArrayList<Component> mChildrenComponents = new ArrayList<>();
+
+    @NonNull
+    protected ArrayList<Component> mChildrenComponents = new ArrayList<>(); // members are not null
 
     protected boolean mChildrenHaveZIndex = false;
 
     public LayoutComponent(
-            Component parent,
+            @Nullable Component parent,
             int componentId,
             int animationId,
             float x,
@@ -129,6 +133,9 @@
 
     public void inflate() {
         ArrayList<TextData> data = new ArrayList<>();
+        ArrayList<TouchExpression> touchExpressions = new ArrayList<>();
+        ArrayList<PaintData> paintData = new ArrayList<>();
+
         for (Operation op : mList) {
             if (op instanceof LayoutComponentContent) {
                 mContent = (LayoutComponentContent) op;
@@ -172,6 +179,10 @@
                 mComponentModifiers.add((ModifierOperation) op);
             } else if (op instanceof TextData) {
                 data.add((TextData) op);
+            } else if (op instanceof TouchExpression) {
+                touchExpressions.add((TouchExpression) op);
+            } else if (op instanceof PaintData) {
+                paintData.add((PaintData) op);
             } else {
                 // nothing
             }
@@ -179,6 +190,8 @@
 
         mList.clear();
         mList.addAll(data);
+        mList.addAll(touchExpressions);
+        mList.addAll(paintData);
         mList.add(mComponentModifiers);
         for (Component c : mChildrenComponents) {
             c.mParent = this;
@@ -255,11 +268,24 @@
     }
 
     @Override
+    public float getScrollX() {
+        return mComponentModifiers.getScrollX();
+    }
+
+    @Override
+    public float getScrollY() {
+        return mComponentModifiers.getScrollY();
+    }
+
+    @Override
     public void paintingComponent(@NonNull PaintContext context) {
-        Component prev = context.getContext().lastComponent;
-        context.getContext().lastComponent = this;
+        Component prev = context.getContext().mLastComponent;
+        context.getContext().mLastComponent = this;
         context.save();
         context.translate(mX, mY);
+        if (context.isVisualDebug()) {
+            debugBox(this, context);
+        }
         if (mGraphicsLayerModifier != null) {
             context.startGraphicsLayer((int) getWidth(), (int) getHeight());
             float scaleX = mGraphicsLayerModifier.getScaleX();
@@ -285,8 +311,8 @@
                     renderEffectId);
         }
         mComponentModifiers.paint(context);
-        float tx = mPaddingLeft;
-        float ty = mPaddingTop;
+        float tx = mPaddingLeft + getScrollX();
+        float ty = mPaddingTop + getScrollY();
         context.translate(tx, ty);
         if (mChildrenHaveZIndex) {
             // TODO -- should only sort when something has changed
@@ -305,7 +331,7 @@
         }
         context.translate(-tx, -ty);
         context.restore();
-        context.getContext().lastComponent = prev;
+        context.getContext().mLastComponent = prev;
     }
 
     /** Traverse the modifiers to compute indicated dimension */
@@ -337,7 +363,7 @@
      * @param padding output start and end padding values
      * @return padding width
      */
-    public float computeModifierDefinedPaddingWidth(float[] padding) {
+    public float computeModifierDefinedPaddingWidth(@NonNull float[] padding) {
         float s = 0f;
         float e = 0f;
         for (Operation c : mComponentModifiers.getList()) {
@@ -381,7 +407,7 @@
      * @param padding output top and bottom padding values
      * @return padding height
      */
-    public float computeModifierDefinedPaddingHeight(float[] padding) {
+    public float computeModifierDefinedPaddingHeight(@NonNull float[] padding) {
         float t = 0f;
         float b = 0f;
         for (Operation c : mComponentModifiers.getList()) {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponentContent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponentContent.java
index 0a085b4..7eea885 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponentContent.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponentContent.java
@@ -18,6 +18,7 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
@@ -35,7 +36,7 @@
             float y,
             float width,
             float height,
-            Component parent,
+            @Nullable Component parent,
             int animationId) {
         super(parent, componentId, animationId, x, y, width, height);
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ListActionsOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ListActionsOperation.java
index c4df075..df960e4 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ListActionsOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ListActionsOperation.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.CoreDocument;
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -59,8 +61,9 @@
         }
     }
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@NonNull String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopEnd.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopEnd.java
index c90077b..71de285 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopEnd.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopEnd.java
@@ -16,7 +16,6 @@
 package com.android.internal.widget.remotecompose.core.operations.layout;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
@@ -40,13 +39,13 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         // nothing
     }
 
     @NonNull
     @Override
-    public String deepToString(@Nullable String indent) {
+    public String deepToString(@NonNull String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
@@ -63,7 +62,7 @@
         buffer.start(id());
     }
 
-    public static void read(WireBuffer buffer, @NonNull List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         operations.add(new LoopEnd());
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java
index eeaeafd..d88382d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java
@@ -16,7 +16,6 @@
 package com.android.internal.widget.remotecompose.core.operations.layout;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
@@ -70,7 +69,7 @@
 
     @NonNull
     @Override
-    public String deepToString(@Nullable String indent) {
+    public String deepToString(@NonNull String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/OperationsListEnd.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/OperationsListEnd.java
index bd8d1f0..ca79003 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/OperationsListEnd.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/OperationsListEnd.java
@@ -16,7 +16,6 @@
 package com.android.internal.widget.remotecompose.core.operations.layout;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
@@ -40,13 +39,13 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         // nothing
     }
 
     @NonNull
     @Override
-    public String deepToString(@Nullable String indent) {
+    public String deepToString(@NonNull String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
@@ -63,7 +62,7 @@
         buffer.start(id());
     }
 
-    public static void read(WireBuffer buffer, @NonNull List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         operations.add(new OperationsListEnd());
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java
index 524ae59..85c7153 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java
@@ -18,6 +18,7 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
@@ -45,13 +46,18 @@
             float y,
             float width,
             float height,
-            Component parent,
+            @Nullable Component parent,
             int animationId) {
         super(parent, componentId, animationId, x, y, width, height);
     }
 
     public RootLayoutComponent(
-            int componentId, float x, float y, float width, float height, Component parent) {
+            int componentId,
+            float x,
+            float y,
+            float width,
+            float height,
+            @Nullable Component parent) {
         super(parent, componentId, -1, x, y, width, height);
     }
 
@@ -130,7 +136,7 @@
         if (!mNeedsMeasure) {
             return;
         }
-        context.lastComponent = this;
+        context.mLastComponent = this;
         mWidth = context.mWidth;
         mHeight = context.mHeight;
 
@@ -149,7 +155,7 @@
     @Override
     public void paint(@NonNull PaintContext context) {
         mNeedsRepaint = false;
-        context.getContext().lastComponent = this;
+        context.getContext().mLastComponent = this;
         context.save();
 
         if (mParent == null) { // root layout
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java
index 486efbd..0316f96 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java
@@ -70,6 +70,12 @@
         applyActions(context, document, component, x, y, true);
     }
 
+    @Override
+    public void onTouchDrag(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+        // nothing
+    }
+
     public static String name() {
         return "TouchCancelModifier";
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java
index 5d379fe..d98911f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java
@@ -72,6 +72,12 @@
         // nothing
     }
 
+    @Override
+    public void onTouchDrag(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+        // nothing
+    }
+
     public static String name() {
         return "TouchModifier";
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchHandler.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchHandler.java
index 5adfc33..ac9dd90 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchHandler.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchHandler.java
@@ -46,6 +46,18 @@
             RemoteContext context, CoreDocument document, Component component, float x, float y);
 
     /**
+     * callback for a touch move event
+     *
+     * @param context the current context
+     * @param document the current document
+     * @param component the component on which the touch has been received
+     * @param x the x position of the click in document coordinates
+     * @param y the y position of the click in document coordinates
+     */
+    void onTouchDrag(
+            RemoteContext context, CoreDocument document, Component component, float x, float y);
+
+    /**
      * callback for a touch cancel event
      *
      * @param context the current context
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java
index 263cc43..f6cb375 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java
@@ -70,6 +70,12 @@
         // nothing
     }
 
+    @Override
+    public void onTouchDrag(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+        // nothing
+    }
+
     public static String name() {
         return "TouchUpModifier";
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java
index 6036b74..b343099 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java
@@ -33,40 +33,40 @@
  * <p>Handles position, size and visibility
  */
 public class AnimateMeasure {
-    long mStartTime = System.currentTimeMillis();
-    Component mComponent;
-    ComponentMeasure mOriginal;
-    ComponentMeasure mTarget;
-    int mDuration;
-    int mDurationVisibilityChange = mDuration;
-    AnimationSpec.ANIMATION mEnterAnimation = AnimationSpec.ANIMATION.FADE_IN;
-    AnimationSpec.ANIMATION mExitAnimation = AnimationSpec.ANIMATION.FADE_OUT;
-    int mMotionEasingType = GeneralEasing.CUBIC_STANDARD;
-    int mVisibilityEasingType = GeneralEasing.CUBIC_ACCELERATE;
+    private long mStartTime = System.currentTimeMillis();
+    private final @NonNull Component mComponent;
+    private final @NonNull ComponentMeasure mOriginal;
+    private final @NonNull ComponentMeasure mTarget;
+    private int mDuration;
+    private int mDurationVisibilityChange = mDuration;
+    private @NonNull AnimationSpec.ANIMATION mEnterAnimation = AnimationSpec.ANIMATION.FADE_IN;
+    private @NonNull AnimationSpec.ANIMATION mExitAnimation = AnimationSpec.ANIMATION.FADE_OUT;
+    private int mMotionEasingType = GeneralEasing.CUBIC_STANDARD;
+    private int mVisibilityEasingType = GeneralEasing.CUBIC_ACCELERATE;
 
-    float mP = 0f;
-    float mVp = 0f;
+    private float mP = 0f;
+    private float mVp = 0f;
 
     @NonNull
-    FloatAnimation mMotionEasing =
+    private FloatAnimation mMotionEasing =
             new FloatAnimation(mMotionEasingType, mDuration / 1000f, null, 0f, Float.NaN);
 
     @NonNull
-    FloatAnimation mVisibilityEasing =
+    private FloatAnimation mVisibilityEasing =
             new FloatAnimation(
                     mVisibilityEasingType, mDurationVisibilityChange / 1000f, null, 0f, Float.NaN);
 
-    ParticleAnimation mParticleAnimation;
+    private ParticleAnimation mParticleAnimation;
 
     public AnimateMeasure(
             long startTime,
             @NonNull Component component,
-            ComponentMeasure original,
+            @NonNull ComponentMeasure original,
             @NonNull ComponentMeasure target,
             int duration,
             int durationVisibilityChange,
-            AnimationSpec.ANIMATION enterAnimation,
-            AnimationSpec.ANIMATION exitAnimation,
+            @NonNull AnimationSpec.ANIMATION enterAnimation,
+            @NonNull AnimationSpec.ANIMATION exitAnimation,
             int motionEasingType,
             int visibilityEasingType) {
         this.mStartTime = startTime;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java
index 47abade..6fb7059 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java
@@ -18,7 +18,6 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
@@ -36,8 +35,8 @@
     int mMotionEasingType = GeneralEasing.CUBIC_STANDARD;
     int mVisibilityDuration = 300;
     int mVisibilityEasingType = GeneralEasing.CUBIC_STANDARD;
-    ANIMATION mEnterAnimation = ANIMATION.FADE_IN;
-    ANIMATION mExitAnimation = ANIMATION.FADE_OUT;
+    @NonNull ANIMATION mEnterAnimation = ANIMATION.FADE_IN;
+    @NonNull ANIMATION mExitAnimation = ANIMATION.FADE_OUT;
 
     public AnimationSpec(
             int animationId,
@@ -45,8 +44,8 @@
             int motionEasingType,
             int visibilityDuration,
             int visibilityEasingType,
-            ANIMATION enterAnimation,
-            ANIMATION exitAnimation) {
+            @NonNull ANIMATION enterAnimation,
+            @NonNull ANIMATION exitAnimation) {
         this.mAnimationId = animationId;
         this.mMotionDuration = motionDuration;
         this.mMotionEasingType = motionEasingType;
@@ -87,10 +86,12 @@
         return mVisibilityEasingType;
     }
 
+    @NonNull
     public ANIMATION getEnterAnimation() {
         return mEnterAnimation;
     }
 
+    @NonNull
     public ANIMATION getExitAnimation() {
         return mExitAnimation;
     }
@@ -126,13 +127,13 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         // nothing here
     }
 
     @NonNull
     @Override
-    public String deepToString(@Nullable String indent) {
+    public String deepToString(@NonNull String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/ParticleAnimation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/ParticleAnimation.java
index 37d2078..64e2f004 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/ParticleAnimation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/ParticleAnimation.java
@@ -34,7 +34,7 @@
             @NonNull PaintContext context,
             @NonNull Component component,
             @NonNull ComponentMeasure start,
-            ComponentMeasure end,
+            @NonNull ComponentMeasure end,
             float progress) {
         ArrayList<Particle> particles = mAllParticles.get(component.getComponentId());
         if (particles == null) {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java
index f3e5509..47a9421 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java
@@ -18,6 +18,7 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
@@ -45,7 +46,7 @@
     int mVerticalPositioning;
 
     public BoxLayout(
-            Component parent,
+            @Nullable Component parent,
             int componentId,
             int animationId,
             float x,
@@ -60,7 +61,7 @@
     }
 
     public BoxLayout(
-            Component parent,
+            @Nullable Component parent,
             int componentId,
             int animationId,
             int horizontalPositioning,
@@ -104,7 +105,7 @@
 
     @Override
     public void computeWrapSize(
-            PaintContext context,
+            @NonNull PaintContext context,
             float maxWidth,
             float maxHeight,
             @NonNull MeasurePass measure,
@@ -122,7 +123,7 @@
 
     @Override
     public void computeSize(
-            PaintContext context,
+            @NonNull PaintContext context,
             float minWidth,
             float maxWidth,
             float minHeight,
@@ -134,7 +135,7 @@
     }
 
     @Override
-    public void internalLayoutMeasure(PaintContext context, @NonNull MeasurePass measure) {
+    public void internalLayoutMeasure(@NonNull PaintContext context, @NonNull MeasurePass measure) {
         ComponentMeasure selfMeasure = measure.get(this);
         float selfWidth = selfMeasure.getW() - mPaddingLeft - mPaddingRight;
         float selfHeight = selfMeasure.getH() - mPaddingTop - mPaddingBottom;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java
index 12ff969..476b1a66 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java
@@ -18,6 +18,7 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
@@ -32,7 +33,7 @@
 
 public class CanvasLayout extends BoxLayout {
     public CanvasLayout(
-            Component parent,
+            @Nullable Component parent,
             int componentId,
             int animationId,
             float x,
@@ -42,7 +43,7 @@
         super(parent, componentId, animationId, x, y, width, height, 0, 0);
     }
 
-    public CanvasLayout(Component parent, int componentId, int animationId) {
+    public CanvasLayout(@Nullable Component parent, int componentId, int animationId) {
         this(parent, componentId, animationId, 0, 0, 0, 0);
     }
 
@@ -103,7 +104,7 @@
     }
 
     @Override
-    public void internalLayoutMeasure(PaintContext context, @NonNull MeasurePass measure) {
+    public void internalLayoutMeasure(@NonNull PaintContext context, @NonNull MeasurePass measure) {
         ComponentMeasure selfMeasure = measure.get(this);
         float selfWidth = selfMeasure.getW() - mPaddingLeft - mPaddingRight;
         float selfHeight = selfMeasure.getH() - mPaddingTop - mPaddingBottom;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java
index 52bf4c5..68e18c6 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java
@@ -19,6 +19,7 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
@@ -53,7 +54,7 @@
     float mSpacedBy = 0f;
 
     public ColumnLayout(
-            Component parent,
+            @Nullable Component parent,
             int componentId,
             int animationId,
             float x,
@@ -70,7 +71,7 @@
     }
 
     public ColumnLayout(
-            Component parent,
+            @Nullable Component parent,
             int componentId,
             int animationId,
             int horizontalPositioning,
@@ -121,7 +122,7 @@
 
     @Override
     public void computeWrapSize(
-            PaintContext context,
+            @NonNull PaintContext context,
             float maxWidth,
             float maxHeight,
             @NonNull MeasurePass measure,
@@ -145,7 +146,7 @@
 
     @Override
     public void computeSize(
-            PaintContext context,
+            @NonNull PaintContext context,
             float minWidth,
             float maxWidth,
             float minHeight,
@@ -164,7 +165,17 @@
     }
 
     @Override
-    public void internalLayoutMeasure(PaintContext context, @NonNull MeasurePass measure) {
+    public float intrinsicHeight() {
+        float height = computeModifierDefinedHeight();
+        float componentHeights = 0f;
+        for (Component c : mChildrenComponents) {
+            componentHeights += c.intrinsicHeight();
+        }
+        return Math.max(height, componentHeights);
+    }
+
+    @Override
+    public void internalLayoutMeasure(@NonNull PaintContext context, @NonNull MeasurePass measure) {
         ComponentMeasure selfMeasure = measure.get(this);
         DebugLog.s(
                 () ->
@@ -188,6 +199,16 @@
         float childrenWidth = 0f;
         float childrenHeight = 0f;
 
+        if (mComponentModifiers.hasHorizontalScroll()) {
+            selfWidth =
+                    mComponentModifiers.getHorizontalScrollDimension()
+                            - mPaddingLeft
+                            - mPaddingRight;
+        }
+        if (mComponentModifiers.hasVerticalScroll()) {
+            selfHeight =
+                    mComponentModifiers.getVerticalScrollDimension() - mPaddingTop - mPaddingBottom;
+        }
         boolean hasWeights = false;
         float totalWeights = 0f;
         for (Component child : mChildrenComponents) {
@@ -286,6 +307,7 @@
                 ty = verticalGap / 2f;
                 break;
         }
+
         for (Component child : mChildrenComponents) {
             ComponentMeasure childMeasure = measure.get(child);
             switch (mHorizontalPositioning) {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java
index 0c4d24a..3b5aaf3 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java
@@ -16,6 +16,7 @@
 package com.android.internal.widget.remotecompose.core.operations.layout.managers;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 
 import com.android.internal.widget.remotecompose.core.PaintContext;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -32,7 +33,7 @@
     @NonNull Size mCachedWrapSize = new Size(0f, 0f);
 
     public LayoutManager(
-            Component parent,
+            @Nullable Component parent,
             int componentId,
             int animationId,
             float x,
@@ -43,24 +44,46 @@
     }
 
     /** Implemented by subclasses to provide a layout/measure pass */
-    public void internalLayoutMeasure(PaintContext context, MeasurePass measure) {
+    public void internalLayoutMeasure(@NonNull PaintContext context, @NonNull MeasurePass measure) {
         // nothing here
     }
 
     /** Subclasses can implement this to provide wrap sizing */
     public void computeWrapSize(
-            PaintContext context, float maxWidth, float maxHeight, MeasurePass measure, Size size) {
+            @NonNull PaintContext context,
+            float maxWidth,
+            float maxHeight,
+            @NonNull MeasurePass measure,
+            @NonNull Size size) {
         // nothing here
     }
 
+    @Override
+    public float intrinsicHeight() {
+        float height = computeModifierDefinedHeight();
+        for (Component c : mChildrenComponents) {
+            height = Math.max(c.intrinsicHeight(), height);
+        }
+        return height;
+    }
+
+    @Override
+    public float intrinsicWidth() {
+        float width = computeModifierDefinedWidth();
+        for (Component c : mChildrenComponents) {
+            width = Math.max(c.intrinsicWidth(), width);
+        }
+        return width;
+    }
+
     /** Subclasses can implement this when not in wrap sizing */
     public void computeSize(
-            PaintContext context,
+            @NonNull PaintContext context,
             float minWidth,
             float maxWidth,
             float minHeight,
             float maxHeight,
-            MeasurePass measure) {
+            @NonNull MeasurePass measure) {
         // nothing here
     }
 
@@ -99,7 +122,7 @@
     /** Base implementation of the measure resolution */
     @Override
     public void measure(
-            PaintContext context,
+            @NonNull PaintContext context,
             float minWidth,
             float maxWidth,
             float minHeight,
@@ -112,7 +135,13 @@
                 Math.min(maxHeight, computeModifierDefinedHeight() - mMarginTop - mMarginBottom);
         float insetMaxWidth = maxWidth - mMarginLeft - mMarginRight;
         float insetMaxHeight = maxHeight - mMarginTop - mMarginBottom;
-        if (mWidthModifier.isWrap() || mHeightModifier.isWrap()) {
+        if (mWidthModifier.isIntrinsicMin()) {
+            maxWidth = intrinsicWidth();
+        }
+        if (mHeightModifier.isIntrinsicMin()) {
+            maxHeight = intrinsicHeight();
+        }
+        if (mWidthModifier.isWrap() || mHeightModifier.isWrap()) { // TODO: potential npe -- bbade@
             mCachedWrapSize.setWidth(0f);
             mCachedWrapSize.setHeight(0f);
             computeWrapSize(context, maxWidth, maxHeight, measure, mCachedWrapSize);
@@ -129,7 +158,7 @@
             measuredWidth = Math.max(measuredWidth, minWidth);
             measuredWidth = Math.min(measuredWidth, insetMaxWidth);
         }
-        if (isInVerticalFill()) {
+        if (isInVerticalFill()) { // todo: potential npe -- bbade@
             measuredHeight = insetMaxHeight;
         } else if (mHeightModifier.hasWeight()) {
             measuredHeight = Math.max(measuredHeight, computeModifierDefinedHeight());
@@ -146,7 +175,23 @@
         measuredWidth = Math.min(measuredWidth, insetMaxWidth);
         measuredHeight = Math.min(measuredHeight, insetMaxHeight);
         if (!hasWrap) {
-            computeSize(context, 0f, measuredWidth, 0f, measuredHeight, measure);
+            if (hasHorizontalScroll()) {
+                mCachedWrapSize.setWidth(0f);
+                mCachedWrapSize.setHeight(0f);
+                computeWrapSize(context, Float.MAX_VALUE, maxHeight, measure, mCachedWrapSize);
+                float w = mCachedWrapSize.getWidth();
+                computeSize(context, 0f, w, 0, measuredHeight, measure);
+                mComponentModifiers.setHorizontalScrollDimension(measuredWidth, w);
+            } else if (hasVerticalScroll()) {
+                mCachedWrapSize.setWidth(0f);
+                mCachedWrapSize.setHeight(0f);
+                computeWrapSize(context, maxWidth, Float.MAX_VALUE, measure, mCachedWrapSize);
+                float h = mCachedWrapSize.getHeight();
+                computeSize(context, 0f, measuredWidth, 0, h, measure);
+                mComponentModifiers.setVerticalScrollDimension(measuredHeight, h);
+            } else {
+                computeSize(context, 0f, measuredWidth, 0f, measuredHeight, measure);
+            }
         }
 
         if (mContent != null) {
@@ -168,6 +213,14 @@
         internalLayoutMeasure(context, measure);
     }
 
+    private boolean hasHorizontalScroll() {
+        return mComponentModifiers.hasHorizontalScroll();
+    }
+
+    private boolean hasVerticalScroll() {
+        return mComponentModifiers.hasVerticalScroll();
+    }
+
     /** basic layout of internal components */
     @Override
     public void layout(@NonNull RemoteContext context, @NonNull MeasurePass measure) {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java
index a366dc8..0ce634f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java
@@ -19,6 +19,7 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
@@ -51,7 +52,7 @@
     float mSpacedBy = 0f;
 
     public RowLayout(
-            Component parent,
+            @Nullable Component parent,
             int componentId,
             int animationId,
             float x,
@@ -68,7 +69,7 @@
     }
 
     public RowLayout(
-            Component parent,
+            @Nullable Component parent,
             int componentId,
             int animationId,
             int horizontalPositioning,
@@ -119,7 +120,7 @@
 
     @Override
     public void computeWrapSize(
-            PaintContext context,
+            @NonNull PaintContext context,
             float maxWidth,
             float maxHeight,
             @NonNull MeasurePass measure,
@@ -143,7 +144,7 @@
 
     @Override
     public void computeSize(
-            PaintContext context,
+            @NonNull PaintContext context,
             float minWidth,
             float maxWidth,
             float minHeight,
@@ -162,7 +163,17 @@
     }
 
     @Override
-    public void internalLayoutMeasure(PaintContext context, @NonNull MeasurePass measure) {
+    public float intrinsicWidth() {
+        float width = computeModifierDefinedWidth();
+        float componentWidths = 0f;
+        for (Component c : mChildrenComponents) {
+            componentWidths += c.intrinsicWidth();
+        }
+        return Math.max(width, componentWidths);
+    }
+
+    @Override
+    public void internalLayoutMeasure(@NonNull PaintContext context, @NonNull MeasurePass measure) {
         ComponentMeasure selfMeasure = measure.get(this);
         DebugLog.s(
                 () ->
@@ -186,6 +197,17 @@
         float childrenWidth = 0f;
         float childrenHeight = 0f;
 
+        if (mComponentModifiers.hasHorizontalScroll()) {
+            selfWidth =
+                    mComponentModifiers.getHorizontalScrollDimension()
+                            - mPaddingLeft
+                            - mPaddingRight;
+        }
+        if (mComponentModifiers.hasVerticalScroll()) {
+            selfHeight =
+                    mComponentModifiers.getVerticalScrollDimension() - mPaddingTop - mPaddingBottom;
+        }
+
         boolean hasWeights = false;
         float totalWeights = 0f;
         for (Component child : mChildrenComponents) {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java
index e47ffde..73a104b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java
@@ -16,6 +16,7 @@
 package com.android.internal.widget.remotecompose.core.operations.layout.managers;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 
 import com.android.internal.widget.remotecompose.core.CoreDocument;
 import com.android.internal.widget.remotecompose.core.Operation;
@@ -60,7 +61,7 @@
     public boolean inTransition = false;
 
     public StateLayout(
-            Component parent,
+            @Nullable Component parent,
             int componentId,
             int animationId,
             float x,
@@ -132,21 +133,21 @@
 
     @Override
     public void computeSize(
-            PaintContext context,
+            @NonNull PaintContext context,
             float minWidth,
             float maxWidth,
             float minHeight,
             float maxHeight,
-            MeasurePass measure) {
+            @NonNull MeasurePass measure) {
         LayoutManager layout = getLayout(currentLayoutIndex);
         layout.computeSize(context, minWidth, maxWidth, minHeight, maxHeight, measure);
     }
 
     @Override
     public void internalLayoutMeasure(
-            PaintContext context,
+            @NonNull PaintContext context,
             // layoutInfo: LayoutInfo,
-            MeasurePass measure) {
+            @NonNull MeasurePass measure) {
         LayoutManager layout = getLayout(currentLayoutIndex);
         //        layout.internalLayoutMeasure(context, layoutInfo, measure)
         layout.internalLayoutMeasure(context, measure);
@@ -155,13 +156,18 @@
     /** Subclasses can implement this to provide wrap sizing */
     @Override
     public void computeWrapSize(
-            PaintContext context, float maxWidth, float maxHeight, MeasurePass measure, Size size) {
+            @NonNull PaintContext context,
+            float maxWidth,
+            float maxHeight,
+            @NonNull MeasurePass measure,
+            @NonNull Size size) {
         LayoutManager layout = getLayout(currentLayoutIndex);
         layout.computeWrapSize(context, maxWidth, maxHeight, measure, size);
     }
 
     @Override
-    public void onClick(RemoteContext context, CoreDocument document, float x, float y) {
+    public void onClick(
+            @NonNull RemoteContext context, @NonNull CoreDocument document, float x, float y) {
         if (!contains(x, y)) {
             return;
         }
@@ -352,7 +358,7 @@
         }
     }
 
-    public LayoutManager getLayout(int idx) {
+    public @NonNull LayoutManager getLayout(int idx) {
         int index = 0;
         for (Component pane : mChildrenComponents) {
             if (pane instanceof LayoutComponent) {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java
index 8aa7712..a527e5a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java
@@ -19,6 +19,7 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
@@ -52,8 +53,9 @@
     private float mTextX;
     private float mTextY;
     private float mTextW;
+    private float mTextH;
 
-    private String mCachedString = "";
+    @Nullable private String mCachedString = "";
 
     @Override
     public void registerListening(@NonNull RemoteContext context) {
@@ -89,7 +91,7 @@
     }
 
     public TextLayout(
-            Component parent,
+            @Nullable Component parent,
             int componentId,
             int animationId,
             float x,
@@ -114,7 +116,7 @@
     }
 
     public TextLayout(
-            Component parent,
+            @Nullable Component parent,
             int componentId,
             int animationId,
             int textId,
@@ -162,6 +164,9 @@
         mPaint.setTextSize(mFontSize);
         mPaint.setTextStyle(mType, (int) mFontWeight, mFontStyle == 1);
         context.applyPaint(mPaint);
+        if (mCachedString == null) {
+            return;
+        }
         int length = mCachedString.length();
         context.drawTextRun(mTextId, 0, length, 0, 0, mTextX, mTextY, false);
         if (DEBUG) {
@@ -241,7 +246,7 @@
             @NonNull PaintContext context,
             float maxWidth,
             float maxHeight,
-            MeasurePass measure,
+            @NonNull MeasurePass measure,
             @NonNull Size size) {
         context.savePaint();
         mPaint.reset();
@@ -250,6 +255,9 @@
         context.applyPaint(mPaint);
         float[] bounds = new float[4];
         int flags = PaintContext.TEXT_MEASURE_FONT_HEIGHT;
+        if (mCachedString == null) {
+            return;
+        }
         context.getTextBounds(mTextId, 0, mCachedString.length(), flags, bounds);
         context.restorePaint();
         float w = bounds[2] - bounds[0];
@@ -259,6 +267,17 @@
         size.setHeight(h);
         mTextY = -bounds[1];
         mTextW = w;
+        mTextH = h;
+    }
+
+    @Override
+    public float intrinsicHeight() {
+        return mTextH;
+    }
+
+    @Override
+    public float intrinsicWidth() {
+        return mTextW;
     }
 
     @NonNull
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java
index 426e023..82f23cd 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java
@@ -26,7 +26,7 @@
     float mY;
     float mW;
     float mH;
-    Component.Visibility mVisibility = Component.Visibility.VISIBLE;
+    @NonNull Component.Visibility mVisibility = Component.Visibility.VISIBLE;
 
     public void setX(float value) {
         mX = value;
@@ -60,16 +60,16 @@
         return mH;
     }
 
-    public Component.Visibility getVisibility() {
+    public @NonNull Component.Visibility getVisibility() {
         return mVisibility;
     }
 
-    public void setVisibility(Component.Visibility visibility) {
+    public void setVisibility(@NonNull Component.Visibility visibility) {
         mVisibility = visibility;
     }
 
     public ComponentMeasure(
-            int id, float x, float y, float w, float h, Component.Visibility visibility) {
+            int id, float x, float y, float w, float h, @NonNull Component.Visibility visibility) {
         this.mId = id;
         this.mX = x;
         this.mY = y;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/Measurable.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/Measurable.java
index b48c2d5..fbf2784 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/Measurable.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/Measurable.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout.measure;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.PaintContext;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
 
@@ -26,15 +28,15 @@
      * does not apply the measure to the component.
      */
     void measure(
-            PaintContext context,
+            @NonNull PaintContext context,
             float minWidth,
             float maxWidth,
             float minHeight,
             float maxHeight,
-            MeasurePass measure);
+            @NonNull MeasurePass measure);
 
     /** Apply a given measure to the component */
-    void layout(RemoteContext context, MeasurePass measure);
+    void layout(@NonNull RemoteContext context, @NonNull MeasurePass measure);
 
     /**
      * Return true if the component needs to be remeasured
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/MeasurePass.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/MeasurePass.java
index 112ab1b..5cfb1b4 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/MeasurePass.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/MeasurePass.java
@@ -43,7 +43,7 @@
         return mList.containsKey(id);
     }
 
-    public ComponentMeasure get(@NonNull Component c) {
+    public @NonNull ComponentMeasure get(@NonNull Component c) {
         if (!mList.containsKey(c.getComponentId())) {
             ComponentMeasure measure =
                     new ComponentMeasure(
@@ -54,7 +54,7 @@
         return mList.get(c.getComponentId());
     }
 
-    public ComponentMeasure get(int id) {
+    public @NonNull ComponentMeasure get(int id) {
         if (!mList.containsKey(id)) {
             ComponentMeasure measure =
                     new ComponentMeasure(id, 0f, 0f, 0f, 0f, Component.Visibility.GONE);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java
index 76a97ca..71d2ba6 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java
@@ -98,7 +98,7 @@
     }
 
     @Override
-    public void layout(RemoteContext context, float width, float height) {
+    public void layout(@NonNull RemoteContext context, float width, float height) {
         this.mWidth = width;
         this.mHeight = height;
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java
index d48a9c7..0707cd6 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java
@@ -124,7 +124,7 @@
     }
 
     @Override
-    public void layout(RemoteContext context, float width, float height) {
+    public void layout(@NonNull RemoteContext context, float width, float height) {
         this.mWidth = width;
         this.mHeight = height;
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java
index 78b51c3..e05b027 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java
@@ -40,7 +40,7 @@
     }
 
     @Override
-    public void layout(RemoteContext context, float width, float height) {
+    public void layout(@NonNull RemoteContext context, float width, float height) {
         this.mWidth = width;
         this.mHeight = height;
     }
@@ -68,7 +68,7 @@
         buffer.start(OP_CODE);
     }
 
-    public static void read(WireBuffer buffer, @NonNull List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         operations.add(new ClipRectModifierOperation());
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java
index 011d7ed..d11f26f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java
@@ -62,7 +62,7 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         // nothing
     }
 
@@ -73,7 +73,7 @@
         }
     }
 
-    public void add(ModifierOperation operation) {
+    public void add(@NonNull ModifierOperation operation) {
         mList.add(operation);
     }
 
@@ -109,7 +109,7 @@
     }
 
     @Override
-    public void layout(RemoteContext context, float width, float height) {
+    public void layout(@NonNull RemoteContext context, float width, float height) {
         float w = width;
         float h = height;
         for (ModifierOperation op : mList) {
@@ -126,13 +126,17 @@
         }
     }
 
-    public void addAll(ArrayList<ModifierOperation> operations) {
+    public void addAll(@NonNull ArrayList<ModifierOperation> operations) {
         mList.addAll(operations);
     }
 
     @Override
     public void onClick(
-            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+            @NonNull RemoteContext context,
+            @NonNull CoreDocument document,
+            @NonNull Component component,
+            float x,
+            float y) {
         for (ModifierOperation op : mList) {
             if (op instanceof ClickHandler) {
                 ((ClickHandler) op).onClick(context, document, component, x, y);
@@ -169,4 +173,110 @@
             }
         }
     }
+
+    @Override
+    public void onTouchDrag(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+        for (ModifierOperation op : mList) {
+            if (op instanceof TouchHandler) {
+                ((TouchHandler) op).onTouchDrag(context, document, component, x, y);
+            }
+        }
+    }
+
+    public boolean hasHorizontalScroll() {
+        for (ModifierOperation op : mList) {
+            if (op instanceof ScrollModifierOperation) {
+                ScrollModifierOperation scrollModifier = (ScrollModifierOperation) op;
+                if (scrollModifier.isHorizontalScroll()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public boolean hasVerticalScroll() {
+        for (ModifierOperation op : mList) {
+            if (op instanceof ScrollModifierOperation) {
+                ScrollModifierOperation scrollModifier = (ScrollModifierOperation) op;
+                if (scrollModifier.isVerticalScroll()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public float getScrollX() {
+        float scroll = 0;
+        for (ModifierOperation op : mList) {
+            if (op instanceof ScrollModifierOperation) {
+                ScrollModifierOperation scrollModifier = (ScrollModifierOperation) op;
+                if (scrollModifier.isHorizontalScroll()) {
+                    scroll = Math.min(scroll, scrollModifier.getScrollX());
+                }
+            }
+        }
+        return scroll;
+    }
+
+    public float getScrollY() {
+        float scroll = 0;
+        for (ModifierOperation op : mList) {
+            if (op instanceof ScrollModifierOperation) {
+                ScrollModifierOperation scrollModifier = (ScrollModifierOperation) op;
+                if (scrollModifier.isVerticalScroll()) {
+                    scroll = Math.min(scroll, scrollModifier.getScrollY());
+                }
+            }
+        }
+        return scroll;
+    }
+
+    public void setHorizontalScrollDimension(float hostDimension, float contentDimension) {
+        for (ModifierOperation op : mList) {
+            if (op instanceof ScrollModifierOperation) {
+                ScrollModifierOperation scrollModifier = (ScrollModifierOperation) op;
+                if (scrollModifier.isHorizontalScroll()) {
+                    scrollModifier.setHorizontalScrollDimension(hostDimension, contentDimension);
+                }
+            }
+        }
+    }
+
+    public void setVerticalScrollDimension(float hostDimension, float contentDimension) {
+        for (ModifierOperation op : mList) {
+            if (op instanceof ScrollModifierOperation) {
+                ScrollModifierOperation scrollModifier = (ScrollModifierOperation) op;
+                if (scrollModifier.isVerticalScroll()) {
+                    scrollModifier.setVerticalScrollDimension(hostDimension, contentDimension);
+                }
+            }
+        }
+    }
+
+    public float getHorizontalScrollDimension() {
+        for (ModifierOperation op : mList) {
+            if (op instanceof ScrollModifierOperation) {
+                ScrollModifierOperation scrollModifier = (ScrollModifierOperation) op;
+                if (scrollModifier.isHorizontalScroll()) {
+                    return scrollModifier.getContentDimension();
+                }
+            }
+        }
+        return 0f;
+    }
+
+    public float getVerticalScrollDimension() {
+        for (ModifierOperation op : mList) {
+            if (op instanceof ScrollModifierOperation) {
+                ScrollModifierOperation scrollModifier = (ScrollModifierOperation) op;
+                if (scrollModifier.isVerticalScroll()) {
+                    return scrollModifier.getContentDimension();
+                }
+            }
+        }
+        return 0f;
+    }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java
index 26e737b3..471db0b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java
@@ -63,16 +63,16 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {}
+    public void apply(@NonNull RemoteContext context) {}
 
     @NonNull
     @Override
-    public String deepToString(@Nullable String indent) {
+    public String deepToString(@NonNull String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
     @Override
-    public void write(WireBuffer buffer) {}
+    public void write(@NonNull WireBuffer buffer) {}
 
     public static void apply(@NonNull WireBuffer buffer, int valueId) {
         buffer.start(OP_CODE);
@@ -114,10 +114,10 @@
         }
     }
 
-    public void setParent(LayoutComponent parent) {
+    public void setParent(@Nullable LayoutComponent parent) {
         mParent = parent;
     }
 
     @Override
-    public void layout(RemoteContext context, float width, float height) {}
+    public void layout(@NonNull RemoteContext context, float width, float height) {}
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionModifierOperation.java
index 3c2d85c..b9324f0 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionModifierOperation.java
@@ -16,7 +16,6 @@
 package com.android.internal.widget.remotecompose.core.operations.layout.modifiers;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 
 import com.android.internal.widget.remotecompose.core.RemoteContext;
 import com.android.internal.widget.remotecompose.core.VariableSupport;
@@ -57,16 +56,16 @@
         }
     }
 
-    Type mType = Type.EXACT;
+    @NonNull Type mType = Type.EXACT;
     float mValue = Float.NaN;
     float mOutValue = Float.NaN;
 
-    public DimensionModifierOperation(Type type, float value) {
+    public DimensionModifierOperation(@NonNull Type type, float value) {
         mType = type;
         mOutValue = mValue = value;
     }
 
-    public DimensionModifierOperation(Type type) {
+    public DimensionModifierOperation(@NonNull Type type) {
         this(type, Float.NaN);
     }
 
@@ -115,7 +114,15 @@
         return mType == Type.FILL;
     }
 
-    public Type getType() {
+    public boolean isIntrinsicMin() {
+        return mType == Type.INTRINSIC_MIN;
+    }
+
+    public boolean isIntrinsicMax() {
+        return mType == Type.INTRINSIC_MAX;
+    }
+
+    public @NonNull Type getType() {
         return mType;
     }
 
@@ -143,11 +150,11 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {}
+    public void apply(@NonNull RemoteContext context) {}
 
     @NonNull
     @Override
-    public String deepToString(@Nullable String indent) {
+    public String deepToString(@NonNull String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java
index 2b30382..571e554 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java
@@ -18,6 +18,8 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -175,8 +177,9 @@
         serializer.append(indent, "GRAPHICS_LAYER = [" + mScaleX + ", " + mScaleY + "]");
     }
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@NonNull String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java
index 97c76c0..7bb4a75 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java
@@ -59,11 +59,11 @@
         apply(buffer, mType.ordinal(), mValue);
     }
 
-    public HeightModifierOperation(Type type, float value) {
+    public HeightModifierOperation(@NonNull Type type, float value) {
         super(type, value);
     }
 
-    public HeightModifierOperation(Type type) {
+    public HeightModifierOperation(@NonNull Type type) {
         super(type);
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java
index 836321f..d239bc8 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java
@@ -18,7 +18,6 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 
 import com.android.internal.widget.remotecompose.core.CoreDocument;
 import com.android.internal.widget.remotecompose.core.Operation;
@@ -63,22 +62,22 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {}
+    public void apply(@NonNull RemoteContext context) {}
 
     @NonNull
     @Override
-    public String deepToString(@Nullable String indent) {
+    public String deepToString(@NonNull String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
     @Override
-    public void write(WireBuffer buffer) {}
+    public void write(@NonNull WireBuffer buffer) {}
 
     @Override
     public void runAction(
             @NonNull RemoteContext context,
-            CoreDocument document,
-            Component component,
+            @NonNull CoreDocument document,
+            @NonNull Component component,
             float x,
             float y) {
         context.runAction(mActionId, "");
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java
index e97e897..3268e5e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java
@@ -18,7 +18,6 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 
 import com.android.internal.widget.remotecompose.core.CoreDocument;
 import com.android.internal.widget.remotecompose.core.Operation;
@@ -39,6 +38,7 @@
     public static final int FLOAT_TYPE = 0;
     public static final int INT_TYPE = 1;
     public static final int STRING_TYPE = 2;
+    public static final int FLOAT_ARRAY_TYPE = 3;
     public static final int NONE_TYPE = -1;
 
     int mTextId = -1;
@@ -72,22 +72,22 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {}
+    public void apply(@NonNull RemoteContext context) {}
 
     @NonNull
     @Override
-    public String deepToString(@Nullable String indent) {
+    public String deepToString(@NonNull String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
     @Override
-    public void write(WireBuffer buffer) {}
+    public void write(@NonNull WireBuffer buffer) {}
 
     @Override
     public void runAction(
             @NonNull RemoteContext context,
-            CoreDocument document,
-            Component component,
+            @NonNull CoreDocument document,
+            @NonNull Component component,
             float x,
             float y) {
         Object value = null;
@@ -98,6 +98,8 @@
                 value = context.mRemoteComposeState.getFromId(mValueId);
             } else if (mType == FLOAT_TYPE) {
                 value = context.mRemoteComposeState.getFloat(mValueId);
+            } else if (mType == FLOAT_ARRAY_TYPE) {
+                value = context.mRemoteComposeState.getFloats(mValueId);
             }
         }
         context.runNamedAction(mTextId, value);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ModifierOperation.java
index 50f098e..8f08f14 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ModifierOperation.java
@@ -15,10 +15,12 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout.modifiers;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
 
 /** Represents a modifier */
 public interface ModifierOperation extends Operation {
-    void serializeToString(int indent, StringSerializer serializer);
+    void serializeToString(int indent, @NonNull StringSerializer serializer);
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java
index 65fe345..8c07059 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -67,8 +69,9 @@
         serializer.append(indent, "OFFSET = [" + mX + ", " + mY + "]");
     }
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@NonNull String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java
index ed5522e..2b6621e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java
@@ -18,7 +18,6 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
@@ -92,11 +91,11 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {}
+    public void apply(@NonNull RemoteContext context) {}
 
     @NonNull
     @Override
-    public String deepToString(@Nullable String indent) {
+    public String deepToString(@NonNull String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java
index 6218dd5..3fefc58 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java
@@ -96,7 +96,7 @@
     }
 
     @Override
-    public void layout(RemoteContext context, float width, float height) {
+    public void layout(@NonNull RemoteContext context, float width, float height) {
         this.mWidth = width;
         this.mHeight = height;
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java
new file mode 100644
index 0000000..8dcfed9
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ScrollModifierOperation.java
@@ -0,0 +1,227 @@
+/*
+ * 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.layout.modifiers;
+
+import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
+
+import android.annotation.NonNull;
+
+import com.android.internal.widget.remotecompose.core.CoreDocument;
+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.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.operations.Utils;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.layout.RootLayoutComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.TouchHandler;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+import java.util.List;
+
+/** Represents a scroll modifier. */
+public class ScrollModifierOperation extends DecoratorModifierOperation implements TouchHandler {
+    private static final int OP_CODE = Operations.MODIFIER_SCROLL;
+    public static final String CLASS_NAME = "ScrollModifierOperation";
+
+    private final float mPositionExpression;
+    private final float mMax;
+    private final float mNotchMax;
+
+    float mWidth = 0;
+    float mHeight = 0;
+
+    int mDirection;
+
+    float mTouchDownX;
+    float mTouchDownY;
+
+    float mInitialScrollX;
+    float mInitialScrollY;
+
+    float mScrollX;
+    float mScrollY;
+
+    float mMaxScrollX;
+    float mMaxScrollY;
+
+    float mHostDimension;
+    float mContentDimension;
+
+    public ScrollModifierOperation(int direction, float position, float max, float notchMax) {
+        this.mDirection = direction;
+        this.mPositionExpression = position;
+        this.mMax = max;
+        this.mNotchMax = notchMax;
+    }
+
+    public boolean isVerticalScroll() {
+        return mDirection == 0;
+    }
+
+    public boolean isHorizontalScroll() {
+        return mDirection != 0;
+    }
+
+    public float getScrollX() {
+        return mScrollX;
+    }
+
+    public float getScrollY() {
+        return mScrollY;
+    }
+
+    @Override
+    public void apply(RemoteContext context) {
+        RootLayoutComponent root = context.getDocument().getRootLayoutComponent();
+        if (root != null) {
+            root.setHasTouchListeners(true);
+        }
+        super.apply(context);
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        apply(buffer, mDirection, mPositionExpression, mMax, mNotchMax);
+    }
+
+    // @Override
+    public void serializeToString(int indent, StringSerializer serializer) {
+        serializer.append(indent, "SCROLL = [" + mDirection + "]");
+    }
+
+    @NonNull
+    @Override
+    public String deepToString(@NonNull String indent) {
+        return (indent != null ? indent : "") + toString();
+    }
+
+    @Override
+    public void paint(PaintContext context) {
+        float position =
+                context.getContext()
+                        .mRemoteComposeState
+                        .getFloat(Utils.idFromNan(mPositionExpression));
+
+        if (mDirection == 0) {
+            mScrollY = -position;
+        } else {
+            mScrollX = -position;
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "ScrollModifierOperation(" + mDirection + ")";
+    }
+
+    public static String name() {
+        return CLASS_NAME;
+    }
+
+    public static int id() {
+        return OP_CODE;
+    }
+
+    public static void apply(
+            WireBuffer buffer, int direction, float position, float max, float notchMax) {
+        buffer.start(OP_CODE);
+        buffer.writeInt(direction);
+        buffer.writeFloat(position);
+        buffer.writeFloat(max);
+        buffer.writeFloat(notchMax);
+    }
+
+    public static void read(WireBuffer buffer, List<Operation> operations) {
+        int direction = buffer.readInt();
+        float position = buffer.readFloat();
+        float max = buffer.readFloat();
+        float notchMax = buffer.readFloat();
+        operations.add(new ScrollModifierOperation(direction, position, max, notchMax));
+    }
+
+    public static void documentation(DocumentationBuilder doc) {
+        doc.operation("Modifier Operations", OP_CODE, CLASS_NAME)
+                .description("define a Scroll Modifier")
+                .field(INT, "direction", "");
+    }
+
+    @Override
+    public void layout(RemoteContext context, float width, float height) {
+        mWidth = width;
+        mHeight = height;
+        if (mDirection == 0) { // VERTICAL
+            context.loadFloat(Utils.idFromNan(mMax), mMaxScrollY);
+            context.loadFloat(Utils.idFromNan(mNotchMax), mContentDimension);
+        } else {
+            context.loadFloat(Utils.idFromNan(mMax), mMaxScrollX);
+            context.loadFloat(Utils.idFromNan(mNotchMax), mContentDimension);
+        }
+    }
+
+    @Override
+    public void onTouchDown(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+        mTouchDownX = x;
+        mTouchDownY = y;
+        mInitialScrollX = mScrollX;
+        mInitialScrollY = mScrollY;
+        document.appliedTouchOperation(component);
+    }
+
+    @Override
+    public void onTouchUp(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+        // If not using touch expression, should add velocity decay here
+    }
+
+    @Override
+    public void onTouchDrag(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+        float dx = x - mTouchDownX;
+        float dy = y - mTouchDownY;
+
+        if (!Utils.isVariable(mPositionExpression)) {
+            if (mDirection == 0) {
+                mScrollY = Math.max(-mMaxScrollY, Math.min(0, mInitialScrollY + dy));
+            } else {
+                mScrollX = Math.max(-mMaxScrollX, Math.min(0, mInitialScrollX + dx));
+            }
+        }
+    }
+
+    @Override
+    public void onTouchCancel(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {}
+
+    public void setHorizontalScrollDimension(float hostDimension, float contentDimension) {
+        mHostDimension = hostDimension;
+        mContentDimension = contentDimension;
+        mMaxScrollX = contentDimension - hostDimension;
+    }
+
+    public void setVerticalScrollDimension(float hostDimension, float contentDimension) {
+        mHostDimension = hostDimension;
+        mContentDimension = contentDimension;
+        mMaxScrollY = contentDimension - hostDimension;
+    }
+
+    public float getContentDimension() {
+        return mContentDimension;
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java
index 29ec828..a97fcff 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java
@@ -18,6 +18,8 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.CoreDocument;
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
@@ -59,8 +61,9 @@
     @Override
     public void apply(RemoteContext context) {}
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@NonNull String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatExpressionChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatExpressionChangeActionOperation.java
new file mode 100644
index 0000000..41586b4
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatExpressionChangeActionOperation.java
@@ -0,0 +1,105 @@
+/*
+ * 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.layout.modifiers;
+
+import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
+
+import android.annotation.NonNull;
+
+import com.android.internal.widget.remotecompose.core.CoreDocument;
+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 com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.operations.layout.ActionOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+import java.util.List;
+
+/** Apply a value change on an integer variable. */
+public class ValueFloatExpressionChangeActionOperation implements ActionOperation {
+    private static final int OP_CODE = Operations.VALUE_FLOAT_EXPRESSION_CHANGE_ACTION;
+
+    int mTargetValueId = -1;
+    int mValueExpressionId = -1;
+
+    public ValueFloatExpressionChangeActionOperation(int id, int valueId) {
+        mTargetValueId = id;
+        mValueExpressionId = valueId;
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return "ValueFloatExpressionChangeActionOperation(" + mTargetValueId + ")";
+    }
+
+    @NonNull
+    public String serializedName() {
+        return "VALUE_FLOAT_EXPRESSION_CHANGE";
+    }
+
+    @Override
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
+        serializer.append(
+                indent, serializedName() + " = " + mTargetValueId + " -> " + mValueExpressionId);
+    }
+
+    @Override
+    public void apply(RemoteContext context) {}
+
+    @NonNull
+    @Override
+    public String deepToString(@NonNull String indent) {
+        return (indent != null ? indent : "") + toString();
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {}
+
+    @Override
+    public void runAction(
+            @NonNull RemoteContext context,
+            @NonNull CoreDocument document,
+            Component component,
+            float x,
+            float y) {
+        document.evaluateFloatExpression(mValueExpressionId, mTargetValueId, context);
+    }
+
+    public static void apply(@NonNull WireBuffer buffer, int valueId, int value) {
+        buffer.start(OP_CODE);
+        buffer.writeInt(valueId);
+        buffer.writeInt(value);
+    }
+
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
+        int valueId = buffer.readInt();
+        int value = buffer.readInt();
+        operations.add(new ValueFloatExpressionChangeActionOperation(valueId, value));
+    }
+
+    public static void documentation(@NonNull DocumentationBuilder doc) {
+        doc.operation("Layout Operations", OP_CODE, "ValueIntegerExpressionChangeActionOperation")
+                .description(
+                        "ValueIntegerExpressionChange action. "
+                                + " This operation represents a value change for the given id")
+                .field(INT, "TARGET_VALUE_ID", "Value ID")
+                .field(INT, "VALUE_ID", "id of the value to be assigned to the target");
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java
index d7ce8ac..c2cd2ab 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java
@@ -18,7 +18,6 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 
 import com.android.internal.widget.remotecompose.core.CoreDocument;
 import com.android.internal.widget.remotecompose.core.Operation;
@@ -61,22 +60,22 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {}
+    public void apply(@NonNull RemoteContext context) {}
 
     @NonNull
     @Override
-    public String deepToString(@Nullable String indent) {
+    public String deepToString(@NonNull String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
     @Override
-    public void write(WireBuffer buffer) {}
+    public void write(@NonNull WireBuffer buffer) {}
 
     @Override
     public void runAction(
             @NonNull RemoteContext context,
-            CoreDocument document,
-            Component component,
+            @NonNull CoreDocument document,
+            @NonNull Component component,
             float x,
             float y) {
         context.overrideInteger(mTargetValueId, mValue);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java
index 75d13e7..43fbb85 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java
@@ -18,7 +18,6 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 
 import com.android.internal.widget.remotecompose.core.CoreDocument;
 import com.android.internal.widget.remotecompose.core.Operation;
@@ -62,22 +61,22 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {}
+    public void apply(@NonNull RemoteContext context) {}
 
     @NonNull
     @Override
-    public String deepToString(@Nullable String indent) {
+    public String deepToString(@NonNull String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
     @Override
-    public void write(WireBuffer buffer) {}
+    public void write(@NonNull WireBuffer buffer) {}
 
     @Override
     public void runAction(
             @NonNull RemoteContext context,
             @NonNull CoreDocument document,
-            Component component,
+            @NonNull Component component,
             float x,
             float y) {
         document.evaluateIntExpression(mValueExpressionId, (int) mTargetValueId, context);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java
index 26d7244..1107889 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java
@@ -18,7 +18,6 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 
 import com.android.internal.widget.remotecompose.core.CoreDocument;
 import com.android.internal.widget.remotecompose.core.Operation;
@@ -65,22 +64,22 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {}
+    public void apply(@NonNull RemoteContext context) {}
 
     @NonNull
     @Override
-    public String deepToString(@Nullable String indent) {
+    public String deepToString(@NonNull String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
     @Override
-    public void write(WireBuffer buffer) {}
+    public void write(@NonNull WireBuffer buffer) {}
 
     @Override
     public void runAction(
             @NonNull RemoteContext context,
-            CoreDocument document,
-            Component component,
+            @NonNull CoreDocument document,
+            @NonNull Component component,
             float x,
             float y) {
         context.overrideText(mTargetValueId, mValueId);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java
index e2f899c..3c757a8 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java
@@ -54,7 +54,7 @@
         operations.add(op);
     }
 
-    public WidthModifierOperation(Type type, float value) {
+    public WidthModifierOperation(@NonNull Type type, float value) {
         super(type, value);
     }
 
@@ -63,7 +63,7 @@
         apply(buffer, mType.ordinal(), mValue);
     }
 
-    public WidthModifierOperation(Type type) {
+    public WidthModifierOperation(@NonNull Type type) {
         super(type);
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java
index aa20e03..82c8f34 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -57,8 +59,9 @@
         serializer.append(indent, "ZINDEX = [" + mValue + "]");
     }
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@NonNull String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/utils/DebugLog.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/utils/DebugLog.java
index d8e49b0..842c9c1 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/utils/DebugLog.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/utils/DebugLog.java
@@ -27,11 +27,11 @@
 
     public static class Node {
         @Nullable public Node parent;
-        public String name;
-        public String endString;
+        @NonNull public String name;
+        @NonNull public String endString;
         @NonNull public ArrayList<Node> list = new ArrayList<>();
 
-        public Node(@Nullable Node parent, String name) {
+        public Node(@Nullable Node parent, @NonNull String name) {
             this.parent = parent;
             this.name = name;
             this.endString = name + " DONE";
@@ -40,13 +40,13 @@
             }
         }
 
-        public void add(Node node) {
+        public void add(@NonNull Node node) {
             list.add(node);
         }
     }
 
     public static class LogNode extends Node {
-        public LogNode(Node parent, String name) {
+        public LogNode(@Nullable Node parent, @NonNull String name) {
             super(parent, name);
         }
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/utils/StringValueSupplier.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/utils/StringValueSupplier.java
index 701167a..5ec1493 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/utils/StringValueSupplier.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/utils/StringValueSupplier.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout.utils;
 
+import android.annotation.NonNull;
+
 /** Basic interface for a lambda (used for logging) */
 public interface StringValueSupplier {
     /**
@@ -22,5 +24,6 @@
      *
      * @return a string
      */
+    @NonNull
     String getString();
 }
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 e714947..07cf762 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,6 +15,9 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.paint;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.PaintContext;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
 import com.android.internal.widget.remotecompose.core.VariableSupport;
@@ -25,8 +28,8 @@
 
 /** Paint Bundle represents a delta of changes to a paint object */
 public class PaintBundle {
-    int[] mArray = new int[200];
-    int[] mOutArray = null;
+    @NonNull int[] mArray = new int[200];
+    @Nullable int[] mOutArray = null;
     int mPos = 0;
 
     /**
@@ -35,7 +38,7 @@
      * @param paintContext
      * @param p
      */
-    public void applyPaintChange(PaintContext paintContext, PaintChanges p) {
+    public void applyPaintChange(@NonNull PaintContext paintContext, @NonNull PaintChanges p) {
         int i = 0;
         int mask = 0;
         if (mOutArray == null) {
@@ -138,12 +141,14 @@
     //        return "????" + id + "????";
     //    }
 
+    @NonNull
     private static String colorInt(int color) {
         String str = "000000000000" + Integer.toHexString(color);
         return "0x" + str.substring(str.length() - 8);
     }
 
-    private static String colorInt(int[] color) {
+    @NonNull
+    private static String colorInt(@NonNull int[] color) {
         String str = "[";
         for (int i = 0; i < color.length; i++) {
             if (i > 0) {
@@ -162,6 +167,7 @@
         return Float.toString(fValue);
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuilder ret = new StringBuilder("\n");
@@ -244,7 +250,8 @@
         return ret.toString();
     }
 
-    private void registerFloat(int iv, RemoteContext context, VariableSupport support) {
+    private void registerFloat(
+            int iv, @NonNull RemoteContext context, @NonNull VariableSupport support) {
         float v = Float.intBitsToFloat(iv);
         if (Float.isNaN(v)) {
             context.listensTo(Utils.idFromNan(v), support);
@@ -252,7 +259,11 @@
     }
 
     int callRegisterGradient(
-            int cmd, int[] array, int i, RemoteContext context, VariableSupport support) {
+            int cmd,
+            int[] array,
+            int i,
+            @NonNull RemoteContext context,
+            @NonNull VariableSupport support) {
         int ret = i;
         int type = (cmd >> 16);
         int control = array[ret++];
@@ -343,7 +354,7 @@
         return ret;
     }
 
-    int callPrintGradient(int cmd, int[] array, int i, StringBuilder p) {
+    int callPrintGradient(int cmd, int[] array, int i, @NonNull StringBuilder p) {
         int ret = i;
         int type = (cmd >> 16);
         int tileMode = 0;
@@ -432,7 +443,7 @@
         return ret;
     }
 
-    int callSetGradient(int cmd, int[] array, int i, PaintChanges p) {
+    int callSetGradient(int cmd, @NonNull int[] array, int i, @NonNull PaintChanges p) {
         int ret = i;
         int gradientType = (cmd >> 16);
 
@@ -487,14 +498,14 @@
         return ret;
     }
 
-    public void writeBundle(WireBuffer buffer) {
+    public void writeBundle(@NonNull WireBuffer buffer) {
         buffer.writeInt(mPos);
         for (int index = 0; index < mPos; index++) {
             buffer.writeInt(mArray[index]);
         }
     }
 
-    public void readBundle(WireBuffer buffer) {
+    public void readBundle(@NonNull WireBuffer buffer) {
         int len = buffer.readInt();
         if (len <= 0 || len > 1024) {
             throw new RuntimeException("buffer corrupt paint len = " + len);
@@ -589,9 +600,9 @@
      * @param tileMode The Shader tiling mode
      */
     public void setLinearGradient(
-            int[] colors,
+            @NonNull int[] colors,
             int idMask,
-            float[] stops,
+            @Nullable float[] stops,
             float startX,
             float startY,
             float endX,
@@ -600,7 +611,7 @@
         //        int startPos = mPos;
         int len;
         mArray[mPos++] = GRADIENT | (LINEAR_GRADIENT << 16);
-        mArray[mPos++] = (idMask << 16) | (len = (colors == null) ? 0 : colors.length);
+        mArray[mPos++] = (idMask << 16) | (len = colors.length);
         for (int i = 0; i < len; i++) {
             mArray[mPos++] = colors[i];
         }
@@ -629,7 +640,12 @@
      *     spaced evenly.
      */
     public void setSweepGradient(
-            int[] colors, int idMask, float[] stops, float centerX, float centerY) {
+            @NonNull int[] colors,
+            int idMask,
+            @Nullable float[] stops, // TODO: rename positions to stops or stops to positions, but
+            // don't have both in the same file
+            float centerX,
+            float centerY) {
         int len;
         mArray[mPos++] = GRADIENT | (SWEEP_GRADIENT << 16);
         mArray[mPos++] = (idMask << 16) | (len = (colors == null) ? 0 : colors.length);
@@ -659,9 +675,9 @@
      * @param tileMode The Shader tiling mode
      */
     public void setRadialGradient(
-            int[] colors,
+            @NonNull int[] colors,
             int idMask,
-            float[] stops,
+            @Nullable float[] stops,
             float centerX,
             float centerY,
             float radius,
@@ -900,7 +916,7 @@
         mPos = 0;
     }
 
-    public static String blendModeString(int mode) {
+    public static @NonNull String blendModeString(int mode) {
         switch (mode) {
             case PaintBundle.BLEND_MODE_CLEAR:
                 return "CLEAR";
@@ -974,7 +990,7 @@
      * @param context
      * @param support
      */
-    public void registerVars(RemoteContext context, VariableSupport support) {
+    public void registerVars(@NonNull RemoteContext context, @NonNull VariableSupport support) {
         int i = 0;
         while (i < mPos) {
             int cmd = mArray[i++];
@@ -1020,7 +1036,7 @@
      *
      * @param context
      */
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         if (mOutArray == null) {
             mOutArray = Arrays.copyOf(mArray, mArray.length);
         } else {
@@ -1066,7 +1082,7 @@
         }
     }
 
-    private int fixFloatVar(int val, RemoteContext context) {
+    private int fixFloatVar(int val, @NonNull RemoteContext context) {
         float v = Float.intBitsToFloat(val);
         if (Float.isNaN(v)) {
             int id = Utils.idFromNan(v);
@@ -1075,12 +1091,13 @@
         return val;
     }
 
-    private int fixColor(int colorId, RemoteContext context) {
+    private int fixColor(int colorId, @NonNull RemoteContext context) {
         int n = context.getColor(colorId);
         return n;
     }
 
-    int updateFloatsInGradient(int cmd, int[] out, int[] array, int i, RemoteContext context) {
+    int updateFloatsInGradient(
+            int cmd, int[] out, int[] array, int i, @NonNull RemoteContext context) {
         int ret = i;
         int type = (cmd >> 16);
         int control = array[ret++];
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 e2402be..87a6632 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
@@ -15,6 +15,9 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.paint;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 public class PaintChangeAdapter implements PaintChanges {
 
     @Override
@@ -64,8 +67,8 @@
 
     @Override
     public void setLinearGradient(
-            int[] colorsArray,
-            float[] stopsArray,
+            @NonNull int[] colorsArray,
+            @Nullable float[] stopsArray,
             float startX,
             float startY,
             float endX,
@@ -74,8 +77,8 @@
 
     @Override
     public void setRadialGradient(
-            int[] colorsArray,
-            float[] stopsArray,
+            @NonNull int[] colorsArray,
+            @Nullable float[] stopsArray,
             float centerX,
             float centerY,
             float radius,
@@ -83,7 +86,10 @@
 
     @Override
     public void setSweepGradient(
-            int[] colorsArray, float[] stopsArray, float centerX, float centerY) {}
+            @NonNull int[] colorsArray,
+            @Nullable float[] stopsArray,
+            float centerX,
+            float centerY) {}
 
     @Override
     public void setColorFilter(int color, int mode) {}
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 486d763..e681647 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,6 +15,9 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.paint;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 /** Interface to a paint object For more details see Android Paint */
 public interface PaintChanges {
 
@@ -135,7 +138,7 @@
      * Set a linear gradient fill
      *
      * @param colorsArray
-     * @param stopsArray
+     * @param stopsArray // todo: standardize naming
      * @param startX
      * @param startY
      * @param endX
@@ -143,8 +146,8 @@
      * @param tileMode
      */
     void setLinearGradient(
-            int[] colorsArray,
-            float[] stopsArray,
+            @NonNull int[] colorsArray,
+            @Nullable float[] stopsArray,
             float startX,
             float startY,
             float endX,
@@ -155,15 +158,15 @@
      * Set a radial gradient fill
      *
      * @param colorsArray
-     * @param stopsArray
+     * @param stopsArray // todo: standardize naming
      * @param centerX
      * @param centerY
      * @param radius
      * @param tileMode
      */
     void setRadialGradient(
-            int[] colorsArray,
-            float[] stopsArray,
+            @NonNull int[] colorsArray,
+            @Nullable float[] stopsArray,
             float centerX,
             float centerY,
             float radius,
@@ -173,11 +176,12 @@
      * Set a sweep gradient fill
      *
      * @param colorsArray
-     * @param stopsArray
+     * @param stopsArray // todo: standardize naming to either "positions" or "stops"
      * @param centerX
      * @param centerY
      */
-    void setSweepGradient(int[] colorsArray, float[] stopsArray, float centerX, float centerY);
+    void setSweepGradient(
+            @NonNull int[] colorsArray, @Nullable float[] stopsArray, float centerX, float centerY);
 
     /**
      * Set Color filter mod
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
index a808cf0..e5f6f28 100644
--- 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
@@ -16,6 +16,7 @@
 package com.android.internal.widget.remotecompose.core.operations.paint;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 
 /** Provides a Builder pattern for a PaintBundle */
 class Painter {
@@ -173,8 +174,8 @@
             float centerX,
             float centerY,
             float radius,
-            int[] colors,
-            float[] positions,
+            @NonNull int[] colors,
+            @NonNull float[] positions,
             int tileMode) {
         mPaint.setRadialGradient(colors, 0, positions, centerX, centerY, radius, tileMode);
         return this;
@@ -193,7 +194,8 @@
      *     spaced evenly.
      */
     @NonNull
-    public Painter setSweepGradient(float centerX, float centerY, int[] colors, float[] positions) {
+    public Painter setSweepGradient(
+            float centerX, float centerY, @NonNull int[] colors, @Nullable float[] positions) {
         mPaint.setSweepGradient(colors, 0, positions, centerX, centerY);
         return this;
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/TextPaint.java b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/TextPaint.java
index 1c0bec7..ff6f45d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/TextPaint.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/TextPaint.java
@@ -15,6 +15,9 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.paint;
 
+import android.annotation.NonNull;
+
+// TODO: this interface is unused. Delete it.
 public interface TextPaint {
     void setARGB(int a, int r, int g, int b);
 
@@ -28,7 +31,7 @@
 
     void setFlags(int flags);
 
-    void setFontFeatureSettings(String settings);
+    void setFontFeatureSettings(@NonNull String settings);
 
     void setHinting(int mode);
 
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
index b25f4cd..e5633c7 100644
--- 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
@@ -18,6 +18,9 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 
+import com.android.internal.widget.remotecompose.core.operations.Utils;
+import com.android.internal.widget.remotecompose.core.operations.utilities.easing.MonotonicSpline;
+
 /** high performance floating point expression evaluator used in animation */
 public class AnimatedFloatExpression {
     @NonNull static IntMap<String> sNames = new IntMap<>();
@@ -64,20 +67,37 @@
     public static final float A_SUM = asNan(OFFSET + 35);
     public static final float A_AVG = asNan(OFFSET + 36);
     public static final float A_LEN = asNan(OFFSET + 37);
-    public static final int LAST_OP = OFFSET + 37;
+    public static final float A_SPLINE = asNan(OFFSET + 38);
 
-    public static final float VAR1 = asNan(OFFSET + 38);
-    public static final float VAR2 = asNan(OFFSET + 39);
+    public static final int LAST_OP = OFFSET + 38;
+
+    public static final float VAR1 = asNan(OFFSET + 39);
+    public static final float VAR2 = asNan(OFFSET + 40);
 
     // 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.29578f; // 180/PI
     private static final float FP_TO_DEG = 0.017453292f; // 180/PI
 
-    float[] mStack;
+    @NonNull float[] mStack = new float[0];
     @NonNull float[] mLocalStack = new float[128];
-    float[] mVar;
-    CollectionsAccess mCollectionsAccess;
+    @NonNull float[] mVar = new float[0];
+    @Nullable CollectionsAccess mCollectionsAccess;
+    IntMap<MonotonicSpline> mSplineMap = new IntMap<>();
+
+    private float getSplineValue(int arrayId, float pos) {
+        MonotonicSpline fit = mSplineMap.get(arrayId);
+        float[] f = mCollectionsAccess.getFloats(arrayId);
+        if (fit != null) {
+            if (fit.getArray() == f) { // the array has not changed.
+                return fit.getPos(pos);
+            }
+        }
+
+        fit = new MonotonicSpline(null, f);
+        mSplineMap.put(arrayId, fit);
+        return fit.getPos(pos);
+    }
 
     /**
      * is float a math operator
@@ -114,7 +134,7 @@
      * @param var
      * @return
      */
-    public float eval(float[] exp, float... var) {
+    public float eval(@NonNull float[] exp, @NonNull float... var) {
         mStack = exp;
         mVar = var;
         int sp = -1;
@@ -137,7 +157,8 @@
      * @param var
      * @return
      */
-    public float eval(CollectionsAccess ca, float[] exp, int len, float... var) {
+    public float eval(
+            @NonNull CollectionsAccess ca, @NonNull float[] exp, int len, @NonNull float... var) {
         System.arraycopy(exp, 0, mLocalStack, 0, len);
         mStack = mLocalStack;
         mVar = var;
@@ -167,7 +188,7 @@
      * @param exp
      * @return
      */
-    public float eval(CollectionsAccess ca, float[] exp, int len) {
+    public float eval(@NonNull CollectionsAccess ca, @NonNull float[] exp, int len) {
         System.arraycopy(exp, 0, mLocalStack, 0, len);
         mStack = mLocalStack;
         mCollectionsAccess = ca;
@@ -189,7 +210,7 @@
         return mStack[sp];
     }
 
-    private int dereference(CollectionsAccess ca, int id, int sp) {
+    private int dereference(@NonNull CollectionsAccess ca, int id, int sp) {
         mStack[sp] = ca.getFloatValue(id, (int) (mStack[sp]));
         return sp;
     }
@@ -202,7 +223,7 @@
      * @param var
      * @return
      */
-    public float eval(@NonNull float[] exp, int len, float... var) {
+    public float eval(@NonNull float[] exp, int len, @NonNull float... var) {
         System.arraycopy(exp, 0, mLocalStack, 0, len);
         mStack = mLocalStack;
         mVar = var;
@@ -225,13 +246,12 @@
      * @param var
      * @return
      */
-    public float evalDB(@NonNull float[] exp, float... var) {
+    public float evalDB(@NonNull float[] exp, @NonNull 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);
@@ -375,12 +395,12 @@
                     return sp - 2;
                 };
         Op mCLAMP =
-                (sp) -> { // CLAMP
+                (sp) -> { // CLAMP (min, max, value)
                     mStack[sp - 2] = Math.min(Math.max(mStack[sp - 2], mStack[sp]), mStack[sp - 1]);
                     return sp - 2;
                 };
         Op mCBRT =
-                (sp) -> { // CBRT
+                (sp) -> { // CBRT is cube root
                     mStack[sp] = (float) Math.pow(mStack[sp], 1 / 3.);
                     return sp;
                 };
@@ -401,8 +421,10 @@
                 };
         Op mA_DEREF =
                 (sp) -> { // A_DEREF
-                    int id = fromNaN(mStack[sp]);
-                    mStack[sp - 1] = mCollectionsAccess.getFloatValue(id, (int) mStack[sp - 1]);
+                    Utils.log(" \n >>> DREF " + Integer.toHexString(fromNaN(mStack[sp - 1])));
+                    Utils.log(" >>> DREF " + mStack[sp] + "  " + mStack[sp - 1]);
+                    int id = fromNaN(mStack[sp - 1]);
+                    mStack[sp - 1] = mCollectionsAccess.getFloatValue(id, (int) mStack[sp]);
                     return sp - 1;
                 };
         Op mA_MAX =
@@ -420,11 +442,14 @@
                 (sp) -> { // A_MIN
                     int id = fromNaN(mStack[sp]);
                     float[] array = mCollectionsAccess.getFloats(id);
-                    float max = array[0];
-                    for (int i = 1; i < array.length; i++) {
-                        max = Math.max(max, array[i]);
+                    if (array.length == 0) {
+                        return sp;
                     }
-                    mStack[sp] = max;
+                    float min = array[0];
+                    for (int i = 1; i < array.length; i++) {
+                        min = Math.min(min, array[i]);
+                    }
+                    mStack[sp] = min;
                     return sp;
                 };
         Op mA_SUM =
@@ -455,6 +480,12 @@
                     mStack[sp] = mCollectionsAccess.getListLength(id);
                     return sp;
                 };
+        Op mA_SPLINE =
+                (sp) -> { // A_SPLINE
+                    int id = fromNaN(mStack[sp - 1]);
+                    mStack[sp - 1] = getSplineValue(id, mStack[sp]);
+                    return sp - 1;
+                };
         Op mFIRST_VAR =
                 (sp) -> { // FIRST_VAR
                     mStack[sp] = mVar[0];
@@ -510,6 +541,7 @@
             mA_SUM,
             mA_AVG,
             mA_LEN,
+            mA_SPLINE,
             mFIRST_VAR,
             mSECOND_VAR,
             mTHIRD_VAR,
@@ -558,6 +590,7 @@
         sNames.put(k++, "A_SUM");
         sNames.put(k++, "A_AVG");
         sNames.put(k++, "A_LEN");
+        sNames.put(k++, "A_SPLINE");
 
         sNames.put(k++, "a[0]");
         sNames.put(k++, "a[1]");
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ArrayAccess.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ArrayAccess.java
index eb5e482..182d36a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ArrayAccess.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ArrayAccess.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.utilities;
 
+import android.annotation.Nullable;
+
 /**
  * Support a standardized interface to commands that contain arrays All commands that implement
  * array access will be collected in a map in the state TODO refactor to DataAccess,
@@ -27,6 +29,7 @@
         return 0;
     }
 
+    @Nullable
     float[] getFloats();
 
     int getLength();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/CollectionsAccess.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/CollectionsAccess.java
index 0128253..4f12872 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/CollectionsAccess.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/CollectionsAccess.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.utilities;
 
+import android.annotation.Nullable;
+
 /**
  * interface to allow expressions to access collections Todo define a convention for when access is
  * unavailable
@@ -22,6 +24,7 @@
 public interface CollectionsAccess {
     float getFloatValue(int id, int index);
 
+    @Nullable
     float[] getFloats(int id);
 
     int getListLength(int id);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/DataMap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/DataMap.java
index 24f17d7..07a3d84 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/DataMap.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/DataMap.java
@@ -15,18 +15,20 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.utilities;
 
-public class DataMap {
-    public String[] mNames;
-    public int[] mIds;
-    public byte[] mTypes;
+import android.annotation.NonNull;
 
-    public DataMap(String[] names, byte[] types, int[] ids) {
+public class DataMap {
+    @NonNull public final String[] mNames;
+    @NonNull public final int[] mIds;
+    @NonNull public final byte[] mTypes;
+
+    public DataMap(@NonNull String[] names, @NonNull byte[] types, @NonNull int[] ids) {
         mNames = names;
         mTypes = types;
         mIds = ids;
     }
 
-    public int getPos(String str) {
+    public int getPos(@NonNull String str) {
         for (int i = 0; i < mNames.length; i++) {
             String name = mNames[i];
             if (str.equals(name)) {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ImageScaling.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ImageScaling.java
index e74b335..98ee91b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ImageScaling.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ImageScaling.java
@@ -17,6 +17,8 @@
 
 import android.annotation.NonNull;
 
+import com.android.internal.widget.remotecompose.core.operations.Utils;
+
 /** Implement the scaling logic for Compose Image or ImageView */
 public class ImageScaling {
 
@@ -109,7 +111,7 @@
         String s = str;
         s += str(left) + ", " + str(top) + ", " + str(right) + ", " + str(bottom) + ", ";
         s += " [" + str(right - left) + " x " + str(bottom - top) + "]";
-        System.out.println(s);
+        Utils.log(s);
     }
 
     /** This adjust destnation on the DrawBitMapInt to support all contentScale types */
@@ -128,7 +130,7 @@
             print("test rc ", mSrcLeft, mSrcTop, mSrcRight, mSrcBottom);
             print("test dst ", mDstLeft, mDstTop, mDstRight, mDstBottom);
         }
-
+        if (sh == 0 || sw == 0) return;
         switch (mScaleType) {
             case SCALE_NONE:
                 dh = sh;
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 749c0fe..b9aa881 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
@@ -15,6 +15,7 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.utilities;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 
 import java.util.ArrayList;
@@ -45,7 +46,7 @@
     }
 
     @Nullable
-    public T put(int key, T value) {
+    public T put(int key, @NonNull T value) {
         if (key == NOT_PRESENT) throw new IllegalArgumentException("Key cannot be NOT_PRESENT");
         if (mSize > mKeys.length * LOAD_FACTOR) {
             resize();
@@ -66,7 +67,7 @@
     }
 
     @Nullable
-    private T insert(int key, T value) {
+    private T insert(int key, @NonNull T value) {
         int index = hash(key) % mKeys.length;
         while (mKeys[index] != NOT_PRESENT && mKeys[index] != key) {
             index = (index + 1) % mKeys.length;
@@ -116,6 +117,7 @@
         }
     }
 
+    @Nullable
     public T remove(int key) {
         int index = hash(key) % mKeys.length;
         int initialIndex = index;
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
index 8905431..f73ab39 100644
--- 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
@@ -59,9 +59,9 @@
     public static final int I_VAR1 = OFFSET + 24;
     public static final int I_VAR2 = OFFSET + 25;
 
-    int[] mStack;
+    @NonNull int[] mStack = new int[0];
     @NonNull int[] mLocalStack = new int[128];
-    int[] mVar;
+    @NonNull int[] mVar = new int[0];
 
     interface Op {
         int eval(int sp);
@@ -75,7 +75,7 @@
      * @param var variables if the expression is a function
      * @return return the results of evaluating the expression
      */
-    public int eval(int mask, int[] exp, int... var) {
+    public int eval(int mask, @NonNull int[] exp, @NonNull int... var) {
         mStack = exp;
         mVar = var;
         int sp = -1;
@@ -99,7 +99,7 @@
      * @param var variables if the expression is a function
      * @return return the results of evaluating the expression
      */
-    public int eval(int mask, @NonNull int[] exp, int len, int... var) {
+    public int eval(int mask, @NonNull int[] exp, int len, @NonNull int... var) {
         System.arraycopy(exp, 0, mLocalStack, 0, len);
         mStack = mLocalStack;
         mVar = var;
@@ -123,17 +123,15 @@
      * @param var variables if the expression is a function
      * @return return the results of evaluating the expression
      */
-    public int evalDB(int opMask, @NonNull int[] exp, int... var) {
+    public int evalDB(int opMask, @NonNull int[] exp, @NonNull int... var) {
         mStack = exp;
         mVar = var;
         int sp = -1;
         for (int i = 0; i < exp.length; i++) {
             int v = mStack[i];
             if (((1 << i) & opMask) != 0) {
-                System.out.print(" " + sNames.get((v - OFFSET)));
                 sp = mOps[v - OFFSET].eval(sp);
             } else {
-                System.out.print(" " + v);
                 mStack[++sp] = v;
             }
         }
@@ -199,7 +197,7 @@
                     return sp - 1;
                 };
         Op mCOPY_SIGN =
-                (sp) -> { // COPY_SIGN
+                (sp) -> { // COPY_SIGN copy the sign via bit manipulation
                     mStack[sp - 1] = (mStack[sp - 1] ^ (mStack[sp] >> 31)) - (mStack[sp] >> 31);
                     return sp - 1;
                 };
@@ -239,12 +237,12 @@
                     return sp;
                 };
         Op mSIGN =
-                (sp) -> { // SIGN
+                (sp) -> { // SIGN x<0 = -1,x==0 =  0 , x>0 = 1
                     mStack[sp] = (mStack[sp] >> 31) | (-mStack[sp] >>> 31);
                     return sp;
                 };
         Op mCLAMP =
-                (sp) -> { // CLAMP
+                (sp) -> { // CLAMP(min,max, val)
                     mStack[sp - 2] = Math.min(Math.max(mStack[sp - 2], mStack[sp]), mStack[sp - 1]);
                     return sp - 2;
                 };
@@ -360,7 +358,7 @@
      * @return
      */
     @NonNull
-    public static String toString(int opMask, @NonNull int[] exp, String[] labels) {
+    public static String toString(int opMask, @NonNull int[] exp, @NonNull String[] labels) {
         StringBuilder s = new StringBuilder();
         for (int i = 0; i < exp.length; i++) {
             int v = exp[i];
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
index ebb22b6..465c95d 100644
--- 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
@@ -57,13 +57,17 @@
         mEasingCurve = new CubicEasing(mType);
     }
 
-    public FloatAnimation(float... description) {
+    public FloatAnimation(@NonNull float... description) {
         mType = CUBIC_STANDARD;
         setAnimationDescription(description);
     }
 
     public FloatAnimation(
-            int type, float duration, float[] description, float initialValue, float wrap) {
+            int type,
+            float duration,
+            @Nullable float[] description,
+            float initialValue,
+            float wrap) {
         mType = CUBIC_STANDARD;
         setAnimationDescription(packToFloatArray(duration, type, description, initialValue, wrap));
     }
@@ -77,7 +81,7 @@
      * @param initialValue
      * @return
      */
-    public static float[] packToFloatArray(
+    public static @NonNull float[] packToFloatArray(
             float duration, int type, @Nullable float[] spec, float initialValue, float wrap) {
         int count = 0;
 
@@ -221,7 +225,7 @@
      *
      * @param description
      */
-    public void setAnimationDescription(float[] description) {
+    public void setAnimationDescription(@NonNull float[] description) {
         mSpec = description;
         mDuration = (mSpec.length == 0) ? 1 : mSpec[0];
         int len = 0;
@@ -242,7 +246,7 @@
         create(mType, description, 2, len);
     }
 
-    private void create(int type, float[] params, int offset, int len) {
+    private void create(int type, @Nullable float[] params, int offset, int len) {
         switch (type) {
             case CUBIC_STANDARD:
             case CUBIC_ACCELERATE:
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
index 90b65bf..06969cc 100644
--- 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
@@ -19,7 +19,7 @@
 
 /** Provides and interface to create easing functions */
 public class GeneralEasing extends Easing {
-    float[] mEasingData = new float[0];
+    @NonNull float[] mEasingData = new float[0];
     @NonNull Easing mEasingCurve = new CubicEasing(CUBIC_STANDARD);
 
     /**
@@ -27,12 +27,12 @@
      *
      * @param data
      */
-    public void setCurveSpecification(float[] data) {
+    public void setCurveSpecification(@NonNull float[] data) {
         mEasingData = data;
         createEngine();
     }
 
-    public float[] getCurveSpecification() {
+    public @NonNull float[] getCurveSpecification() {
         return mEasingData;
     }
 
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
index f540e70..f4579a2 100644
--- 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
@@ -22,11 +22,11 @@
 /** 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;
+    @NonNull private final double[] mT;
+    @NonNull private final double[][] mY;
+    @NonNull private final double[][] mTangent;
     private boolean mExtrapolate = true;
-    double[] mSlopeTemp;
+    @NonNull final double[] mSlopeTemp;
 
     /**
      * create a collection of curves
@@ -81,7 +81,7 @@
      * @param t
      * @param v
      */
-    public void getPos(double t, double[] v) {
+    public void getPos(double t, @NonNull double[] v) {
         final int n = mT.length;
         final int dim = mY[0].length;
         if (mExtrapolate) {
@@ -141,7 +141,7 @@
      * @param t
      * @param v
      */
-    public void getPos(double t, float[] v) {
+    public void getPos(double t, @NonNull float[] v) {
         final int n = mT.length;
         final int dim = mY[0].length;
         if (mExtrapolate) {
@@ -243,7 +243,7 @@
      * @param t
      * @param v
      */
-    public void getSlope(double t, double[] v) {
+    public void getSlope(double t, @NonNull double[] v) {
         final int n = mT.length;
         int dim = mY[0].length;
         if (t <= mT[0]) {
@@ -297,7 +297,7 @@
         return 0; // should never reach here
     }
 
-    public double[] getTimePoints() {
+    public @NonNull double[] getTimePoints() {
         return mT;
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicSpline.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicSpline.java
new file mode 100644
index 0000000..23a6643
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicSpline.java
@@ -0,0 +1,203 @@
+/*
+ * 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 android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/** This performs a spline interpolation in multiple dimensions */
+public class MonotonicSpline {
+    private static final String TAG = "MonotonicCurveFit";
+    private float[] mT;
+    private float[] mY;
+    private float[] mTangent;
+    private boolean mExtrapolate = true;
+    float[] mSlopeTemp;
+
+    /**
+     * create a collection of curves
+     *
+     * @param time the point along the curve
+     * @param y the parameter at those points
+     */
+    public MonotonicSpline(@Nullable float[] time, @NonNull float[] y) {
+        if (time == null) { // if time  is null assume even 0 to 1;
+            time = new float[y.length];
+            for (int i = 0; i < time.length; i++) {
+                time[i] = i / (float) (time.length - 1);
+            }
+        }
+        mT = time;
+        mY = y;
+        final int n = time.length;
+        final int dim = 1;
+        mSlopeTemp = new float[dim];
+        float[] slope = new float[n - 1]; // could optimize this out
+        float[] tangent = new float[n];
+        for (int i = 0; i < n - 1; i++) {
+            float dt = time[i + 1] - time[i];
+            slope[i] = (y[i + 1] - y[i]) / dt;
+            if (i == 0) {
+                tangent[i] = slope[i];
+            } else {
+                tangent[i] = (slope[i - 1] + slope[i]) * 0.5f;
+            }
+        }
+        tangent[n - 1] = slope[n - 2];
+
+        for (int i = 0; i < n - 1; i++) {
+            if (slope[i] == 0.) {
+                tangent[i] = 0f;
+                tangent[i + 1] = 0f;
+            } else {
+                float a = tangent[i] / slope[i];
+                float b = tangent[i + 1] / slope[i];
+                float h = (float) Math.hypot(a, b);
+                if (h > 9.0) {
+                    float t = 3f / h;
+                    tangent[i] = t * a * slope[i];
+                    tangent[i + 1] = t * b * slope[i];
+                }
+            }
+        }
+        mTangent = tangent;
+    }
+
+    public float[] getArray() {
+        return mY;
+    }
+
+    /**
+     * Get the position of all curves at time t
+     *
+     * @param t
+     * @return position at t
+     */
+    public float getPos(float t) {
+        final int n = mT.length;
+        float v;
+        if (mExtrapolate) {
+            if (t <= mT[0]) {
+                float slopeTemp = getSlope(mT[0]);
+                v = mY[0] + (t - mT[0]) * slopeTemp;
+
+                return v;
+            }
+            if (t >= mT[n - 1]) {
+                float slopeTemp = getSlope(mT[n - 1]);
+                v = mY[n - 1] + (t - mT[n - 1]) * slopeTemp;
+
+                return v;
+            }
+        } else {
+            if (t <= mT[0]) {
+                v = mY[0];
+
+                return v;
+            }
+            if (t >= mT[n - 1]) {
+                v = mY[n - 1];
+
+                return v;
+            }
+        }
+
+        for (int i = 0; i < n - 1; i++) {
+            if (t == mT[i]) {
+
+                v = mY[i];
+            }
+            if (t < mT[i + 1]) {
+                float h = mT[i + 1] - mT[i];
+                float x = (t - mT[i]) / h;
+
+                float y1 = mY[i];
+                float y2 = mY[i + 1];
+                float t1 = mTangent[i];
+                float t2 = mTangent[i + 1];
+                v = interpolate(h, x, y1, y2, t1, t2);
+
+                return v;
+            }
+        }
+        return 0f;
+    }
+
+    /**
+     * Get the slope of the curve at position t
+     *
+     * @param t
+     * @return slope at t
+     */
+    public float getSlope(float t) {
+        final int n = mT.length;
+        float v = 0;
+
+        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]) {
+                float h = mT[i + 1] - mT[i];
+                float x = (t - mT[i]) / h;
+                float y1 = mY[i];
+                float y2 = mY[i + 1];
+                float t1 = mTangent[i];
+                float t2 = mTangent[i + 1];
+                v = diff(h, x, y1, y2, t1, t2) / h;
+            }
+            break;
+        }
+        return v;
+    }
+
+    public float[] getTimePoints() {
+        return mT;
+    }
+
+    /** Cubic Hermite spline */
+    private static float interpolate(float h, float x, float y1, float y2, float t1, float t2) {
+        float x2 = x * x;
+        float 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 float diff(float h, float x, float y1, float y2, float t1, float t2) {
+        float 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;
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/SpringStopEngine.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/SpringStopEngine.java
new file mode 100644
index 0000000..03e4503
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/SpringStopEngine.java
@@ -0,0 +1,188 @@
+/*
+ * 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 contains the class to provide the logic for an animation to come to a stop using a spring
+ * model. String debug(String desc, float time); float getVelocity(float time); float
+ * getInterpolation(float time); float getVelocity(); boolean isStopped();
+ */
+public class SpringStopEngine {
+    double mDamping = 0.5f;
+
+    @SuppressWarnings("unused")
+    private static final double UNSET = Double.MAX_VALUE;
+
+    @SuppressWarnings("unused")
+    private boolean mInitialized = false;
+
+    private double mStiffness;
+    private double mTargetPos;
+
+    @SuppressWarnings("unused")
+    private double mLastVelocity;
+
+    private float mLastTime;
+    private float mPos;
+    private float mV;
+    private float mMass;
+    private float mStopThreshold;
+    private int mBoundaryMode = 0;
+
+    public String debug(String desc, float time) {
+        return null;
+    }
+
+    void log(String str) {
+        StackTraceElement s = new Throwable().getStackTrace()[1];
+        String line =
+                ".(" + s.getFileName() + ":" + s.getLineNumber() + ") " + s.getMethodName() + "() ";
+        System.out.println(line + str);
+    }
+
+    public SpringStopEngine() {}
+
+    public float getTargetValue() {
+        return (float) mTargetPos;
+    }
+
+    public void setInitialValue(float v) {
+        mPos = v;
+    }
+
+    public void setTargetValue(float v) {
+        mTargetPos = v;
+    }
+
+    public SpringStopEngine(float[] parameters) {
+        if (parameters[0] != 0) {
+            throw new RuntimeException(" parameter[0] should be 0");
+        }
+
+        springParameters(
+                1,
+                parameters[1],
+                parameters[2],
+                parameters[3],
+                Float.floatToRawIntBits(parameters[4]));
+    }
+
+    /**
+     * Config the spring starting conditions
+     *
+     * @param currentPos
+     * @param target
+     * @param currentVelocity
+     */
+    public void springStart(float currentPos, float target, float currentVelocity) {
+        mTargetPos = target;
+        mInitialized = false;
+        mPos = currentPos;
+        mLastVelocity = currentVelocity;
+        mLastTime = 0;
+    }
+
+    /**
+     * Config the spring parameters
+     *
+     * @param mass The mass of the spring
+     * @param stiffness The stiffness of the spring
+     * @param damping The dampening factor
+     * @param stopThreshold how low energy must you be to stop
+     * @param boundaryMode The boundary behaviour
+     */
+    public void springParameters(
+            float mass, float stiffness, float damping, float stopThreshold, int boundaryMode) {
+        mDamping = damping;
+        mInitialized = false;
+        mStiffness = stiffness;
+        mMass = mass;
+        mStopThreshold = stopThreshold;
+        mBoundaryMode = boundaryMode;
+        mLastTime = 0;
+    }
+
+    public float getVelocity(float time) {
+        return (float) mV;
+    }
+
+    public float get(float time) {
+        compute(time - mLastTime);
+        mLastTime = time;
+        if (isStopped()) {
+            mPos = (float) mTargetPos;
+        }
+        return (float) mPos;
+    }
+
+    public float getAcceleration() {
+        double k = mStiffness;
+        double c = mDamping;
+        double x = (mPos - mTargetPos);
+        return (float) (-k * x - c * mV) / mMass;
+    }
+
+    public float getVelocity() {
+        return 0;
+    }
+
+    public boolean isStopped() {
+        double x = (mPos - mTargetPos);
+        double k = mStiffness;
+        double v = mV;
+        double m = mMass;
+        double energy = v * v * m + k * x * x;
+        double max_def = Math.sqrt(energy / k);
+        return max_def <= mStopThreshold;
+    }
+
+    private void compute(double dt) {
+        if (dt <= 0) {
+            // Nothing to compute if there's no time difference
+            return;
+        }
+
+        double k = mStiffness;
+        double c = mDamping;
+        // Estimate how many time we should over sample based on the frequency and current sampling
+        int overSample = (int) (1 + 9 / (Math.sqrt(mStiffness / mMass) * dt * 4));
+        dt /= overSample;
+
+        for (int i = 0; i < overSample; i++) {
+            double x = (mPos - mTargetPos);
+            double a = (-k * x - c * mV) / mMass;
+            // This refinement of a simple coding of the acceleration increases accuracy
+            double avgV = mV + a * dt / 2; // pass 1 calculate the average velocity
+            double avgX = mPos + dt * avgV / 2 - mTargetPos; // pass 1 calculate the average pos
+            a = (-avgX * k - avgV * c) / mMass; //  calculate acceleration over that average pos
+
+            double dv = a * dt; //  calculate change in velocity
+            avgV = mV + dv / 2; //  average  velocity is current + half change
+            mV += (float) dv;
+            mPos += (float) (avgV * dt);
+            if (mBoundaryMode > 0) {
+                if (mPos < 0 && ((mBoundaryMode & 1) == 1)) {
+                    mPos = -mPos;
+                    mV = -mV;
+                }
+                if (mPos > 1 && ((mBoundaryMode & 2) == 2)) {
+                    mPos = 2 - mPos;
+                    mV = -mV;
+                }
+            }
+        }
+    }
+}
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
index c7be3ca..b1eb804 100644
--- 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
@@ -24,14 +24,14 @@
  */
 public class StepCurve extends Easing {
     //    private static final boolean DEBUG = false;
-    MonotonicCurveFit mCurveFit;
+    @NonNull private final MonotonicCurveFit mCurveFit;
 
-    public StepCurve(float[] params, int offset, int len) {
+    public StepCurve(@NonNull float[] params, int offset, int len) {
         mCurveFit = genSpline(params, offset, len);
     }
 
     @NonNull
-    private static MonotonicCurveFit genSpline(float[] values, int off, int arrayLen) {
+    private static MonotonicCurveFit genSpline(@NonNull float[] values, int off, int arrayLen) {
         int length = arrayLen * 3 - 2;
         int len = arrayLen - 1;
         double gap = 1.0 / len;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/touch/VelocityEasing.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/touch/VelocityEasing.java
index 3e24372..7e02bc9 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/touch/VelocityEasing.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/touch/VelocityEasing.java
@@ -212,7 +212,6 @@
                 mStage[1].setUp(peak_v, d1, t1, 0f, destination, t2 + t1);
                 mDuration = t2 + t1;
                 if (mDuration > maxTime) {
-                    System.out.println(" fail ");
                     return false;
                 }
             }
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
index 3fba8ac..4af79f3 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java
@@ -54,11 +54,11 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {}
+    public void apply(@NonNull RemoteContext context) {}
 
     @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@NonNull String indent) {
         return toString();
     }
 
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
index 79f2a8d..613e732 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java
@@ -50,7 +50,7 @@
 
     @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@NonNull String indent) {
         return toString();
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java b/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java
index 01672b4..745caa3 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java
@@ -54,13 +54,13 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         context.putObject(mId, this);
     }
 
     @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@NonNull String indent) {
         return toString();
     }
 
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 aaee9c5..2a3f3be 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java
@@ -141,4 +141,9 @@
         }
         return mDocument.getStats();
     }
+
+    public int hasSensorListeners(int[] ids) {
+
+        return 0;
+    }
 }
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 cc74b11..648f7bf 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
@@ -15,9 +15,14 @@
  */
 package com.android.internal.widget.remotecompose.player;
 
+import android.app.Application;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Color;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.TypedValue;
@@ -28,6 +33,7 @@
 import android.widget.ScrollView;
 
 import com.android.internal.widget.remotecompose.core.CoreDocument;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
 import com.android.internal.widget.remotecompose.core.operations.NamedVariable;
 import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior;
 import com.android.internal.widget.remotecompose.player.platform.RemoteComposeCanvas;
@@ -81,6 +87,7 @@
             mInner.setDocument(null);
         }
         mapColors();
+        setupSensors();
         mInner.setHapticEngine(
                 new CoreDocument.HapticEngine() {
 
@@ -543,4 +550,113 @@
     private void provideHapticFeedback(int type) {
         performHapticFeedback(sHapticTable[type % sHapticTable.length]);
     }
+
+    SensorManager mSensorManager;
+    Sensor mAcc = null, mGyro = null, mMag = null, mLight = null;
+    SensorEventListener mListener;
+
+    private void setupSensors() {
+
+        int minId = RemoteContext.ID_ACCELERATION_X;
+        int maxId = RemoteContext.ID_LIGHT;
+        int[] ids = new int[1 + maxId - minId];
+
+        int count = mInner.hasSensorListeners(ids);
+        mAcc = null;
+        mGyro = null;
+        mMag = null;
+        mLight = null;
+        if (count > 0) {
+            Application app = (Application) getContext().getApplicationContext();
+
+            mSensorManager = (SensorManager) app.getSystemService(Context.SENSOR_SERVICE);
+            for (int i = 0; i < count; i++) {
+                switch (ids[i]) {
+                    case RemoteContext.ID_ACCELERATION_X:
+                    case RemoteContext.ID_ACCELERATION_Y:
+                    case RemoteContext.ID_ACCELERATION_Z:
+                        if (mAcc == null) {
+                            mAcc = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+                        }
+                        break;
+                    case RemoteContext.ID_GYRO_ROT_X:
+                    case RemoteContext.ID_GYRO_ROT_Y:
+                    case RemoteContext.ID_GYRO_ROT_Z:
+                        if (mGyro == null) {
+                            mGyro = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
+                        }
+                        break;
+                    case RemoteContext.ID_MAGNETIC_X:
+                    case RemoteContext.ID_MAGNETIC_Y:
+                    case RemoteContext.ID_MAGNETIC_Z:
+                        if (mMag == null) {
+                            mMag = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
+                        }
+                        break;
+                    case RemoteContext.ID_LIGHT:
+                        if (mLight == null) {
+                            mLight = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
+                        }
+                }
+            }
+        }
+        registerListener();
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        unregisterListener();
+    }
+
+    public void registerListener() {
+        Sensor[] s = {mAcc, mGyro, mMag, mLight};
+        if (mListener != null) {
+            unregisterListener();
+        }
+        SensorEventListener listener =
+                new SensorEventListener() {
+                    @Override
+                    public void onSensorChanged(SensorEvent event) {
+                        if (event.sensor == mAcc) {
+                            mInner.setExternalFloat(
+                                    RemoteContext.ID_ACCELERATION_X, event.values[0]);
+                            mInner.setExternalFloat(
+                                    RemoteContext.ID_ACCELERATION_Y, event.values[1]);
+                            mInner.setExternalFloat(
+                                    RemoteContext.ID_ACCELERATION_Z, event.values[2]);
+                        } else if (event.sensor == mGyro) {
+                            mInner.setExternalFloat(RemoteContext.ID_GYRO_ROT_X, event.values[0]);
+                            mInner.setExternalFloat(RemoteContext.ID_GYRO_ROT_Y, event.values[1]);
+                            mInner.setExternalFloat(RemoteContext.ID_GYRO_ROT_Z, event.values[2]);
+                        } else if (event.sensor == mMag) {
+                            mInner.setExternalFloat(RemoteContext.ID_MAGNETIC_X, event.values[0]);
+                            mInner.setExternalFloat(RemoteContext.ID_MAGNETIC_Y, event.values[1]);
+                            mInner.setExternalFloat(RemoteContext.ID_MAGNETIC_Z, event.values[2]);
+                        } else if (event.sensor == mLight) {
+                            mInner.setExternalFloat(RemoteContext.ID_LIGHT, event.values[0]);
+                        }
+                    }
+
+                    @Override
+                    public void onAccuracyChanged(Sensor sensor, int accuracy) {}
+                };
+
+        Sensor[] sensors = {mAcc, mGyro, mMag, mLight};
+        for (int i = 0; i < sensors.length; i++) {
+            Sensor sensor = sensors[i];
+            if (sensor != null) {
+                mListener = listener;
+                mSensorManager.registerListener(
+                        mListener, sensor, SensorManager.SENSOR_DELAY_NORMAL);
+            }
+        }
+    }
+
+    public void unregisterListener() {
+        if (mListener != null && mSensorManager != null) {
+            mSensorManager.unregisterListener(mListener);
+        }
+        mListener = null;
+    }
 }
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 0b650a9..3c91cff 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
@@ -15,6 +15,7 @@
  */
 package com.android.internal.widget.remotecompose.player.platform;
 
+import android.annotation.NonNull;
 import android.graphics.Bitmap;
 import android.graphics.BlendMode;
 import android.graphics.Canvas;
@@ -247,7 +248,7 @@
     }
 
     @Override
-    public void getTextBounds(int textId, int start, int end, int flags, float[] bounds) {
+    public void getTextBounds(int textId, int start, int end, int flags, @NonNull float[] bounds) {
         String str = getText(textId);
         if (end == -1) {
             end = str.length();
@@ -420,7 +421,7 @@
      * @param paintData the list change to the paint
      */
     @Override
-    public void applyPaint(PaintBundle paintData) {
+    public void applyPaint(@NonNull PaintBundle paintData) {
         paintData.applyPaintChange(
                 (PaintContext) this,
                 new PaintChanges() {
@@ -576,8 +577,8 @@
 
                     @Override
                     public void setLinearGradient(
-                            int[] colors,
-                            float[] stops,
+                            @NonNull int[] colors,
+                            @NonNull float[] stops,
                             float startX,
                             float startY,
                             float endX,
@@ -596,8 +597,8 @@
 
                     @Override
                     public void setRadialGradient(
-                            int[] colors,
-                            float[] stops,
+                            @NonNull int[] colors,
+                            @NonNull float[] stops,
                             float centerX,
                             float centerY,
                             float radius,
@@ -614,7 +615,10 @@
 
                     @Override
                     public void setSweepGradient(
-                            int[] colors, float[] stops, float centerX, float centerY) {
+                            @NonNull int[] colors,
+                            @NonNull float[] stops,
+                            float centerX,
+                            float centerY) {
                         mPaint.setShader(new SweepGradient(centerX, centerY, colors, stops));
                     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPlatformServices.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPlatformServices.java
index f28e85a..ba8d83b 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPlatformServices.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPlatformServices.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.player.platform;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.graphics.Bitmap;
 import android.graphics.Path;
 import android.graphics.PathIterator;
@@ -31,7 +33,7 @@
     private static final String LOG_TAG = "RemoteCompose";
 
     @Override
-    public byte[] imageToByteArray(Object image) {
+    public byte[] imageToByteArray(@NonNull Object image) {
         if (image instanceof Bitmap) {
             // let's create a bitmap
             ByteArrayOutputStream byteArrayBitmapStream = new ByteArrayOutputStream();
@@ -42,7 +44,7 @@
     }
 
     @Override
-    public int getImageWidth(Object image) {
+    public int getImageWidth(@NonNull Object image) {
         if (image instanceof Bitmap) {
             return ((Bitmap) image).getWidth();
         }
@@ -50,7 +52,7 @@
     }
 
     @Override
-    public int getImageHeight(Object image) {
+    public int getImageHeight(@NonNull Object image) {
         if (image instanceof Bitmap) {
             return ((Bitmap) image).getHeight();
         }
@@ -58,7 +60,8 @@
     }
 
     @Override
-    public float[] pathToFloatArray(Object path) {
+    @Nullable
+    public float[] pathToFloatArray(@NonNull Object path) {
         //        if (path is RemotePath) {
         //            return path.createFloatArray()
         //        }
@@ -88,7 +91,7 @@
         }
     }
 
-    private float[] androidPathToFloatArray(Path path) {
+    private @NonNull float[] androidPathToFloatArray(@NonNull Path path) {
         PathIterator i = path.getPathIterator();
         int estimatedSize = 0;
 
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 7a7edba..77c2514 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
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.player.platform;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
@@ -22,11 +24,15 @@
 import com.android.internal.widget.remotecompose.core.RemoteContext;
 import com.android.internal.widget.remotecompose.core.TouchListener;
 import com.android.internal.widget.remotecompose.core.VariableSupport;
+import com.android.internal.widget.remotecompose.core.operations.BitmapData;
 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.utilities.ArrayAccess;
 import com.android.internal.widget.remotecompose.core.operations.utilities.DataMap;
 
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
 import java.util.HashMap;
 
 /**
@@ -53,7 +59,7 @@
     ///////////////////////////////////////////////////////////////////////////////////////////////
 
     @Override
-    public void loadPathData(int instanceId, float[] floatPath) {
+    public void loadPathData(int instanceId, @NonNull float[] floatPath) {
         if (!mRemoteComposeState.containsId(instanceId)) {
             mRemoteComposeState.cacheData(instanceId, floatPath);
         }
@@ -74,12 +80,12 @@
     HashMap<String, VarName> mVarNameHashMap = new HashMap<>();
 
     @Override
-    public void loadVariableName(String varName, int varId, int varType) {
+    public void loadVariableName(@NonNull String varName, int varId, int varType) {
         mVarNameHashMap.put(varName, new VarName(varName, varId, varType));
     }
 
     @Override
-    public void setNamedStringOverride(String stringName, String value) {
+    public void setNamedStringOverride(@NonNull String stringName, @NonNull String value) {
         if (mVarNameHashMap.get(stringName) != null) {
             int id = mVarNameHashMap.get(stringName).mId;
             overrideText(id, value);
@@ -87,7 +93,7 @@
     }
 
     @Override
-    public void clearNamedStringOverride(String stringName) {
+    public void clearNamedStringOverride(@NonNull String stringName) {
         if (mVarNameHashMap.get(stringName) != null) {
             int id = mVarNameHashMap.get(stringName).mId;
             clearDataOverride(id);
@@ -96,7 +102,7 @@
     }
 
     @Override
-    public void setNamedIntegerOverride(String stringName, int value) {
+    public void setNamedIntegerOverride(@NonNull String stringName, int value) {
         if (mVarNameHashMap.get(stringName) != null) {
             int id = mVarNameHashMap.get(stringName).mId;
             overrideInt(id, value);
@@ -104,7 +110,7 @@
     }
 
     @Override
-    public void clearNamedIntegerOverride(String integerName) {
+    public void clearNamedIntegerOverride(@NonNull String integerName) {
         if (mVarNameHashMap.get(integerName) != null) {
             int id = mVarNameHashMap.get(integerName).mId;
             clearIntegerOverride(id);
@@ -118,18 +124,18 @@
      * @param colorName name of color
      * @param color
      */
-    public void setNamedColorOverride(String colorName, int color) {
+    public void setNamedColorOverride(@NonNull String colorName, int color) {
         int id = mVarNameHashMap.get(colorName).mId;
         mRemoteComposeState.overrideColor(id, color);
     }
 
     @Override
-    public void addCollection(int id, ArrayAccess collection) {
+    public void addCollection(int id, @NonNull ArrayAccess collection) {
         mRemoteComposeState.addCollection(id, collection);
     }
 
     @Override
-    public void putDataMap(int id, DataMap map) {
+    public void putDataMap(int id, @NonNull DataMap map) {
         mRemoteComposeState.putDataMap(id, map);
     }
 
@@ -139,7 +145,7 @@
     }
 
     @Override
-    public void runAction(int id, String metadata) {
+    public void runAction(int id, @NonNull String metadata) {
         mDocument.performClick(id);
     }
 
@@ -152,21 +158,66 @@
     /**
      * Decode a byte array into an image and cache it using the given imageId
      *
-     * @param width with of image to be loaded
+     * @param encoding how the data is encoded 0 = png, 1 = raw, 2 = url
+     * @param type the type of the data 0 = RGBA 8888, 1 = 888, 2 = 8 gray
+     * @param width with of image to be loaded largest dimension is 32767
      * @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) {
+    public void loadBitmap(
+            int imageId, short encoding, short type, int width, int height, @NonNull byte[] data) {
         if (!mRemoteComposeState.containsId(imageId)) {
-            Bitmap image = BitmapFactory.decodeByteArray(bitmap, 0, bitmap.length);
+            Bitmap image = null;
+            switch (encoding) {
+                case BitmapData.ENCODING_INLINE:
+                    switch (type) {
+                        case BitmapData.TYPE_PNG_8888:
+                            image = BitmapFactory.decodeByteArray(data, 0, data.length);
+                            break;
+                        case BitmapData.TYPE_RAW8888:
+                            image = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+                            int[] idata = new int[data.length / 4];
+                            for (int i = 0; i < idata.length; i++) {
+                                int p = i * 4;
+                                idata[i] =
+                                        (data[p] << 24)
+                                                | (data[p + 1] << 16)
+                                                | (data[p + 2] << 8)
+                                                | data[p + 3];
+                            }
+                            image.setPixels(idata, 0, width, 0, 0, width, height);
+                            break;
+                        case BitmapData.TYPE_RAW8:
+                            image = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+                            int[] bdata = new int[data.length / 4];
+                            for (int i = 0; i < bdata.length; i++) {
+
+                                bdata[i] = 0x1010101 * data[i];
+                            }
+                            image.setPixels(bdata, 0, width, 0, 0, width, height);
+                            break;
+                    }
+                    break;
+                case BitmapData.ENCODING_FILE:
+                    image = BitmapFactory.decodeFile(new String(data));
+                    break;
+                case BitmapData.ENCODING_URL:
+                    try {
+                        image = BitmapFactory.decodeStream(new URL(new String(data)).openStream());
+                    } catch (MalformedURLException e) {
+                        throw new RuntimeException(e);
+                    } catch (IOException e) {
+                        throw new RuntimeException(e);
+                    }
+            }
             mRemoteComposeState.cacheData(imageId, image);
         }
     }
 
     @Override
-    public void loadText(int id, String text) {
+    public void loadText(int id, @NonNull String text) {
         if (!mRemoteComposeState.containsId(id)) {
             mRemoteComposeState.cacheData(id, text);
         } else {
@@ -225,12 +276,12 @@
     }
 
     @Override
-    public void loadAnimatedFloat(int id, FloatExpression animatedFloat) {
+    public void loadAnimatedFloat(int id, @NonNull FloatExpression animatedFloat) {
         mRemoteComposeState.cacheData(id, animatedFloat);
     }
 
     @Override
-    public void loadShader(int id, ShaderData value) {
+    public void loadShader(int id, @NonNull ShaderData value) {
         mRemoteComposeState.cacheData(id, value);
     }
 
@@ -240,7 +291,7 @@
     }
 
     @Override
-    public void putObject(int id, Object value) {
+    public void putObject(int id, @NonNull Object value) {
         mRemoteComposeState.updateObject(id, value);
     }
 
@@ -260,7 +311,7 @@
     }
 
     @Override
-    public void listensTo(int id, VariableSupport variableSupport) {
+    public void listensTo(int id, @NonNull VariableSupport variableSupport) {
         mRemoteComposeState.listenToVar(id, variableSupport);
     }
 
@@ -270,6 +321,7 @@
     }
 
     @Override
+    @Nullable
     public ShaderData getShader(int id) {
         return (ShaderData) mRemoteComposeState.getFromId(id);
     }
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 fdd9aad..41ed017 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
@@ -15,6 +15,7 @@
  */
 package com.android.internal.widget.remotecompose.player.platform;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Paint;
@@ -23,13 +24,17 @@
 /** Implementation for the click handling */
 class ClickAreaView extends View {
     private int mId;
-    private String mMetadata;
+    private final String mMetadata;
     Paint mPaint = new Paint();
 
     private boolean mDebug;
 
     ClickAreaView(
-            Context context, boolean debug, int id, String contentDescription, String metadata) {
+            Context context,
+            boolean debug,
+            int id,
+            @Nullable String contentDescription,
+            String metadata) {
         super(context);
         this.mId = id;
         this.mMetadata = metadata;
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 b54ed8a..8f55f8a 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
@@ -26,6 +26,7 @@
 import android.widget.FrameLayout;
 
 import com.android.internal.widget.remotecompose.core.CoreDocument;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
 import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior;
 import com.android.internal.widget.remotecompose.core.operations.Theme;
 import com.android.internal.widget.remotecompose.player.RemoteComposeDocument;
@@ -194,6 +195,20 @@
         }
     }
 
+    public int hasSensorListeners(int[] ids) {
+        int count = 0;
+        for (int id = RemoteContext.ID_ACCELERATION_X; id <= RemoteContext.ID_LIGHT; id++) {
+            if (mARContext.mRemoteComposeState.hasListener(id)) {
+                ids[count++] = id;
+            }
+        }
+        return count;
+    }
+
+    public void setExternalFloat(int id, float value) {
+        mARContext.loadFloat(id, value);
+    }
+
     public interface ClickCallbacks {
         void click(int id, String metadata);
     }
@@ -344,7 +359,9 @@
         mARContext.setAnimationEnabled(true);
         mARContext.currentTime = System.currentTimeMillis();
         mARContext.setDebug(mDebug);
+        float density = getContext().getResources().getDisplayMetrics().density;
         mARContext.useCanvas(canvas);
+        mARContext.setDensity(density);
         mARContext.mWidth = getWidth();
         mARContext.mHeight = getHeight();
         mDocument.paint(mARContext, mTheme);