Update to ToT RemoteCompose

Bug: 339721781
Flag: EXEMPT External Libraries
Test: in GoB
Change-Id: I1ce49374511e11f0865554a147ab375e0d73070e
diff --git a/core/java/com/android/internal/widget/remotecompose/accessibility/AndroidPlatformSemanticNodeApplier.java b/core/java/com/android/internal/widget/remotecompose/accessibility/AndroidPlatformSemanticNodeApplier.java
new file mode 100644
index 0000000..410e021
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/accessibility/AndroidPlatformSemanticNodeApplier.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.accessibility;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Rect;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.semantics.AccessibilitySemantics;
+import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent;
+import com.android.internal.widget.remotecompose.core.semantics.CoreSemantics;
+
+import java.util.List;
+
+public class AndroidPlatformSemanticNodeApplier
+        implements SemanticNodeApplier<AccessibilityNodeInfo, Component, AccessibilitySemantics> {
+
+    private static final String ROLE_DESCRIPTION_KEY = "AccessibilityNodeInfo.roleDescription";
+
+    @Override
+    public void applyComponent(
+            @NonNull
+                    RemoteComposeDocumentAccessibility<Component, AccessibilitySemantics>
+                            remoteComposeAccessibility,
+            AccessibilityNodeInfo nodeInfo,
+            Component component,
+            List<AccessibilitySemantics> semantics) {
+        if (component instanceof AccessibleComponent) {
+            applyContentDescription(
+                    ((AccessibleComponent) component).getContentDescriptionId(),
+                    nodeInfo,
+                    remoteComposeAccessibility);
+
+            applyRole(((AccessibleComponent) component).getRole(), nodeInfo);
+        }
+
+        applySemantics(remoteComposeAccessibility, nodeInfo, semantics);
+
+        float[] locationInWindow = new float[2];
+        component.getLocationInWindow(locationInWindow);
+        Rect bounds =
+                new Rect(
+                        (int) locationInWindow[0],
+                        (int) locationInWindow[1],
+                        (int) (locationInWindow[0] + component.getWidth()),
+                        (int) (locationInWindow[1] + component.getHeight()));
+        //noinspection deprecation
+        nodeInfo.setBoundsInParent(bounds);
+        nodeInfo.setBoundsInScreen(bounds);
+
+        if (component instanceof AccessibleComponent) {
+            applyContentDescription(
+                    ((AccessibleComponent) component).getContentDescriptionId(),
+                    nodeInfo,
+                    remoteComposeAccessibility);
+
+            applyText(
+                    ((AccessibleComponent) component).getTextId(),
+                    nodeInfo,
+                    remoteComposeAccessibility);
+
+            applyRole(((AccessibleComponent) component).getRole(), nodeInfo);
+        }
+
+        applySemantics(remoteComposeAccessibility, nodeInfo, semantics);
+
+        if (nodeInfo.getText() == null && nodeInfo.getContentDescription() == null) {
+            nodeInfo.setContentDescription("");
+        }
+    }
+
+    public void applySemantics(
+            RemoteComposeDocumentAccessibility<Component, AccessibilitySemantics>
+                    remoteComposeAccessibility,
+            AccessibilityNodeInfo nodeInfo,
+            List<AccessibilitySemantics> semantics) {
+        for (AccessibilitySemantics semantic : semantics) {
+            if (semantic.isInterestingForSemantics()) {
+                if (semantic instanceof CoreSemantics) {
+                    applyCoreSemantics(
+                            remoteComposeAccessibility, nodeInfo, (CoreSemantics) semantic);
+                } else if (semantic instanceof AccessibleComponent) {
+                    AccessibleComponent s = (AccessibleComponent) semantic;
+
+                    applyContentDescription(
+                            s.getContentDescriptionId(), nodeInfo, remoteComposeAccessibility);
+
+                    applyRole(s.getRole(), nodeInfo);
+
+                    applyText(s.getTextId(), nodeInfo, remoteComposeAccessibility);
+
+                    if (s.isClickable()) {
+                        nodeInfo.setClickable(true);
+                        nodeInfo.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK);
+                    }
+                }
+            }
+        }
+    }
+
+    private void applyCoreSemantics(
+            RemoteComposeDocumentAccessibility<Component, AccessibilitySemantics>
+                    remoteComposeAccessibility,
+            AccessibilityNodeInfo nodeInfo,
+            CoreSemantics semantics) {
+        applyContentDescription(
+                semantics.getContentDescriptionId(), nodeInfo, remoteComposeAccessibility);
+
+        applyRole(semantics.getRole(), nodeInfo);
+
+        applyText(semantics.getTextId(), nodeInfo, remoteComposeAccessibility);
+
+        applyStateDescription(
+                semantics.getStateDescriptionId(), nodeInfo, remoteComposeAccessibility);
+
+        nodeInfo.setEnabled(semantics.mEnabled);
+    }
+
+    void applyRole(@Nullable AccessibleComponent.Role role, AccessibilityNodeInfo nodeInfo) {
+        if (role != null) {
+            nodeInfo.getExtras().putCharSequence(ROLE_DESCRIPTION_KEY, role.getDescription());
+        }
+    }
+
+    void applyContentDescription(
+            @Nullable Integer contentDescriptionId,
+            AccessibilityNodeInfo nodeInfo,
+            RemoteComposeDocumentAccessibility<Component, AccessibilitySemantics>
+                    remoteComposeAccessibility) {
+        if (contentDescriptionId != null) {
+            nodeInfo.setContentDescription(
+                    remoteComposeAccessibility.stringValue(contentDescriptionId));
+        }
+    }
+
+    void applyText(
+            @Nullable Integer textId,
+            AccessibilityNodeInfo nodeInfo,
+            RemoteComposeDocumentAccessibility<Component, AccessibilitySemantics>
+                    remoteComposeAccessibility) {
+        if (textId != null) {
+            nodeInfo.setText(remoteComposeAccessibility.stringValue(textId));
+        }
+    }
+
+    void applyStateDescription(
+            @Nullable Integer stateDescriptionId,
+            AccessibilityNodeInfo nodeInfo,
+            RemoteComposeDocumentAccessibility<Component, AccessibilitySemantics>
+                    remoteComposeAccessibility) {
+        if (stateDescriptionId != null) {
+            nodeInfo.setStateDescription(
+                    remoteComposeAccessibility.stringValue(stateDescriptionId));
+        }
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/accessibility/CoreDocumentAccessibility.java b/core/java/com/android/internal/widget/remotecompose/accessibility/CoreDocumentAccessibility.java
new file mode 100644
index 0000000..66a7f02
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/accessibility/CoreDocumentAccessibility.java
@@ -0,0 +1,182 @@
+/*
+ * 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.accessibility;
+
+import android.annotation.Nullable;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.os.Bundle;
+
+import com.android.internal.widget.remotecompose.core.CoreDocument;
+import com.android.internal.widget.remotecompose.core.operations.layout.ClickModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.RootLayoutComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentModifiers;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ModifierOperation;
+import com.android.internal.widget.remotecompose.core.semantics.AccessibilitySemantics;
+import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent;
+import com.android.internal.widget.remotecompose.core.semantics.CoreSemantics;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Java Player implementation of the {@link RemoteComposeDocumentAccessibility} interface. Each item
+ * in the semantic tree is a {@link Component} from the remote Compose UI. Each Component can have a
+ * list of modifiers that must be tagged with {@link AccessibilitySemantics} either incidentally
+ * (see {@link ClickModifierOperation}) or explicitly (see {@link CoreSemantics}).
+ */
+public class CoreDocumentAccessibility
+        implements RemoteComposeDocumentAccessibility<Component, AccessibilitySemantics> {
+    private final CoreDocument mDocument;
+
+    private final Rect mMissingBounds = new Rect(0, 0, 1, 1);
+
+    public CoreDocumentAccessibility(CoreDocument document) {
+        this.mDocument = document;
+    }
+
+    @Nullable
+    @Override
+    public Integer getComponentIdAt(PointF point) {
+        return RootId;
+    }
+
+    @Override
+    public @Nullable Component findComponentById(int virtualViewId) {
+        RootLayoutComponent root = mDocument.getRootLayoutComponent();
+
+        if (root == null || virtualViewId == -1) {
+            return root;
+        }
+
+        return componentStream(root)
+                .filter(op -> op.getComponentId() == virtualViewId)
+                .findFirst()
+                .orElse(null);
+    }
+
+    @Override
+    public List<CoreSemantics.Mode> mergeMode(Component component) {
+        if (!(component instanceof LayoutComponent)) {
+            return Collections.singletonList(CoreSemantics.Mode.SET);
+        }
+
+        return ((LayoutComponent) component)
+                .getComponentModifiers().getList().stream()
+                        .filter(i -> i instanceof AccessibleComponent)
+                        .map(i -> ((AccessibleComponent) i).getMode())
+                        .filter(Objects::nonNull)
+                        .collect(Collectors.toList());
+    }
+
+    @Override
+    public boolean performAction(Component component, int action, Bundle arguments) {
+        if (action == ACTION_CLICK) {
+            mDocument.performClick(component.getComponentId());
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    @Nullable
+    @Override
+    public String stringValue(int id) {
+        Object value = mDocument.getRemoteComposeState().getFromId(id);
+        return value != null ? String.valueOf(value) : null;
+    }
+
+    @Override
+    public List<AccessibilitySemantics> semanticModifiersForComponent(Component component) {
+        if (!(component instanceof LayoutComponent)) {
+            return Collections.emptyList();
+        }
+
+        List<ModifierOperation> modifiers =
+                ((LayoutComponent) component).getComponentModifiers().getList();
+
+        return modifiers.stream()
+                .filter(
+                        it ->
+                                it instanceof AccessibilitySemantics
+                                        && ((AccessibilitySemantics) it)
+                                                .isInterestingForSemantics())
+                .map(i -> (AccessibilitySemantics) i)
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public List<Integer> semanticallyRelevantChildComponents(Component component) {
+        return componentStream(component)
+                .filter(i -> i.getComponentId() != component.getComponentId())
+                .filter(CoreDocumentAccessibility::isInteresting)
+                .map(Component::getComponentId)
+                .collect(Collectors.toList());
+    }
+
+    static Stream<Component> componentStream(Component root) {
+        return Stream.concat(
+                Stream.of(root),
+                root.mList.stream()
+                        .flatMap(
+                                op -> {
+                                    if (op instanceof Component) {
+                                        return componentStream((Component) op);
+                                    } else {
+                                        return Stream.empty();
+                                    }
+                                }));
+    }
+
+    static Stream<ModifierOperation> modifiersStream(Component component) {
+        return component.mList.stream()
+                .filter(it -> it instanceof ComponentModifiers)
+                .flatMap(it -> ((ComponentModifiers) it).getList().stream());
+    }
+
+    static boolean isInteresting(Component component) {
+        boolean interesting =
+                isContainerWithSemantics(component)
+                        || modifiersStream(component)
+                                .anyMatch(CoreDocumentAccessibility::isModifierWithSemantics);
+
+        return interesting && component.isVisible();
+    }
+
+    static boolean isModifierWithSemantics(ModifierOperation modifier) {
+        return modifier instanceof AccessibilitySemantics
+                && ((AccessibilitySemantics) modifier).isInterestingForSemantics();
+    }
+
+    static boolean isContainerWithSemantics(Component component) {
+        if (component instanceof AccessibilitySemantics) {
+            return ((AccessibilitySemantics) component).isInterestingForSemantics();
+        }
+
+        if (!(component instanceof LayoutComponent)) {
+            return false;
+        }
+
+        return ((LayoutComponent) component)
+                .getComponentModifiers().getList().stream()
+                        .anyMatch(CoreDocumentAccessibility::isModifierWithSemantics);
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeTouchHelper.java b/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeTouchHelper.java
new file mode 100644
index 0000000..c9ad28a
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeTouchHelper.java
@@ -0,0 +1,163 @@
+/*
+ * 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.accessibility;
+
+import static com.android.internal.widget.remotecompose.accessibility.RemoteComposeDocumentAccessibility.RootId;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.PointF;
+import android.os.Bundle;
+import android.util.IntArray;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import com.android.internal.widget.ExploreByTouchHelper;
+import com.android.internal.widget.remotecompose.core.CoreDocument;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.semantics.AccessibilitySemantics;
+import com.android.internal.widget.remotecompose.core.semantics.CoreSemantics.Mode;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.Stack;
+
+public class PlatformRemoteComposeTouchHelper<N, C, S> extends ExploreByTouchHelper {
+    private final RemoteComposeDocumentAccessibility<C, S> mRemoteDocA11y;
+
+    private final SemanticNodeApplier<AccessibilityNodeInfo, C, S> mApplier;
+
+    public PlatformRemoteComposeTouchHelper(
+            View host,
+            RemoteComposeDocumentAccessibility<C, S> remoteDocA11y,
+            SemanticNodeApplier<AccessibilityNodeInfo, C, S> applier) {
+        super(host);
+        this.mRemoteDocA11y = remoteDocA11y;
+        this.mApplier = applier;
+    }
+
+    public static PlatformRemoteComposeTouchHelper<
+                    AccessibilityNodeInfo, Component, AccessibilitySemantics>
+            forRemoteComposePlayer(View player, @NonNull CoreDocument coreDocument) {
+        return new PlatformRemoteComposeTouchHelper<>(
+                player,
+                new CoreDocumentAccessibility(coreDocument),
+                new AndroidPlatformSemanticNodeApplier());
+    }
+
+    /**
+     * Gets the virtual view ID at a given location on the screen.
+     *
+     * <p>This method is called by the Accessibility framework to determine which virtual view, if
+     * any, is located at a specific point on the screen. It uses the {@link
+     * RemoteComposeDocumentAccessibility#getComponentIdAt(PointF)} method to find the ID of the
+     * component at the given coordinates.
+     *
+     * @param x The x-coordinate of the location in pixels.
+     * @param y The y-coordinate of the location in pixels.
+     * @return The ID of the virtual view at the given location, or {@link #INVALID_ID} if no
+     *     virtual view is found at that location.
+     */
+    @Override
+    protected int getVirtualViewAt(float x, float y) {
+        Integer root = mRemoteDocA11y.getComponentIdAt(new PointF(x, y));
+
+        if (root == null) {
+            return INVALID_ID;
+        }
+
+        return root;
+    }
+
+    /**
+     * Populates a list with the visible virtual view IDs.
+     *
+     * <p>This method is called by the accessibility framework to retrieve the IDs of all visible
+     * virtual views in the accessibility hierarchy. It traverses the hierarchy starting from the
+     * root node (RootId) and adds the ID of each visible view to the provided list.
+     *
+     * @param virtualViewIds The list to be populated with the visible virtual view IDs.
+     */
+    @Override
+    protected void getVisibleVirtualViews(IntArray virtualViewIds) {
+        Stack<Integer> toVisit = new Stack<>();
+        Set<Integer> visited = new HashSet<>();
+
+        toVisit.push(RootId);
+
+        while (!toVisit.isEmpty()) {
+            Integer componentId = toVisit.remove(0);
+
+            if (visited.add(componentId)) {
+                virtualViewIds.add(componentId);
+
+                C component = mRemoteDocA11y.findComponentById(componentId);
+
+                if (component != null) {
+                    boolean allSet =
+                            mRemoteDocA11y.mergeMode(component).stream()
+                                    .allMatch(i -> i == Mode.SET);
+
+                    if (allSet) {
+                        List<Integer> childViews =
+                                mRemoteDocA11y.semanticallyRelevantChildComponents(component);
+
+                        toVisit.addAll(childViews);
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public void onPopulateNodeForVirtualView(
+            int virtualViewId, @NonNull AccessibilityNodeInfo node) {
+        C component = mRemoteDocA11y.findComponentById(virtualViewId);
+
+        List<Mode> mode = mRemoteDocA11y.mergeMode(component);
+
+        if (mode.contains(Mode.MERGE)) {
+            List<Integer> childViews =
+                    mRemoteDocA11y.semanticallyRelevantChildComponents(component);
+
+            for (Integer childView : childViews) {
+                onPopulateNodeForVirtualView(childView, node);
+            }
+        }
+
+        List<S> semantics = mRemoteDocA11y.semanticModifiersForComponent(component);
+        mApplier.applyComponent(mRemoteDocA11y, node, component, semantics);
+    }
+
+    @Override
+    protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
+        // TODO
+    }
+
+    @Override
+    protected boolean onPerformActionForVirtualView(
+            int virtualViewId, int action, @Nullable Bundle arguments) {
+        C component = mRemoteDocA11y.findComponentById(virtualViewId);
+
+        if (component != null) {
+            return mRemoteDocA11y.performAction(component, action, arguments);
+        } else {
+            return false;
+        }
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/accessibility/RemoteComposeDocumentAccessibility.java b/core/java/com/android/internal/widget/remotecompose/accessibility/RemoteComposeDocumentAccessibility.java
new file mode 100644
index 0000000..14977be
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/accessibility/RemoteComposeDocumentAccessibility.java
@@ -0,0 +1,100 @@
+/*
+ * 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.accessibility;
+
+import android.annotation.Nullable;
+import android.graphics.PointF;
+import android.os.Bundle;
+import android.view.View;
+
+import com.android.internal.widget.remotecompose.core.semantics.CoreSemantics;
+
+import java.util.List;
+
+/**
+ * Interface for interacting with the accessibility features of a remote Compose UI. This interface
+ * provides methods to perform actions, retrieve state, and query the accessibility tree of the
+ * remote Compose UI.
+ *
+ * @param <C> The type of component in the remote Compose UI.
+ * @param <S> The type representing semantic modifiers applied to components.
+ */
+public interface RemoteComposeDocumentAccessibility<C, S> {
+    // Matches ExploreByTouchHelper.HOST_ID
+    Integer RootId = View.NO_ID;
+
+    // androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_CLICK
+    int ACTION_CLICK = 0x00000010;
+
+    /**
+     * Performs the specified action on the given component.
+     *
+     * @param component The component on which to perform the action.
+     * @param action The action to perform.
+     * @param arguments Optional arguments for the action.
+     * @return {@code true} if the action was performed successfully, {@code false} otherwise.
+     */
+    boolean performAction(C component, int action, Bundle arguments);
+
+    /**
+     * Retrieves the string value associated with the given ID.
+     *
+     * @param id The ID to retrieve the string value for.
+     * @return The string value associated with the ID, or {@code null} if no such value exists.
+     */
+    @Nullable
+    String stringValue(int id);
+
+    /**
+     * Retrieves a list of child view IDs semantically contained within the given component/virtual
+     * view. These may later be hidden from accessibility services by properties, but should contain
+     * only possibly semantically relevant virtual views.
+     *
+     * @param component The component to retrieve child view IDs from, or [RootId] for the top
+     *     level.
+     * @return A list of integer IDs representing the child views of the component.
+     */
+    List<Integer> semanticallyRelevantChildComponents(C component);
+
+    /**
+     * Retrieves the semantic modifiers associated with a given component.
+     *
+     * @param component The component for which to retrieve semantic modifiers.
+     * @return A list of semantic modifiers applicable to the component.
+     */
+    List<S> semanticModifiersForComponent(C component);
+
+    /**
+     * Gets all applied merge modes of the given component. A Merge mode is one of Set, Merge or
+     * Clear and describes how to apply and combine hierarchical semantics.
+     *
+     * @param component The component to merge the mode for.
+     * @return A list of merged modes, potentially conflicting but to be resolved by the caller.
+     */
+    List<CoreSemantics.Mode> mergeMode(C component);
+
+    /**
+     * Finds a component by its ID.
+     *
+     * @param id the ID of the component to find
+     * @return the component with the given ID, or {@code null} if no such component exists
+     */
+    @Nullable
+    C findComponentById(int id);
+
+    @Nullable
+    Integer getComponentIdAt(PointF point);
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/accessibility/RemoteComposeTouchHelper.java b/core/java/com/android/internal/widget/remotecompose/accessibility/RemoteComposeTouchHelper.java
new file mode 100644
index 0000000..4ff7892
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/accessibility/RemoteComposeTouchHelper.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.accessibility;
+
+import android.annotation.NonNull;
+import android.view.View;
+
+import com.android.internal.widget.remotecompose.core.CoreDocument;
+
+public class RemoteComposeTouchHelper {
+    public static View.AccessibilityDelegate forRemoteComposePlayer(
+            View player, @NonNull CoreDocument coreDocument) {
+        return new PlatformRemoteComposeTouchHelper<>(
+                player,
+                new CoreDocumentAccessibility(coreDocument),
+                new AndroidPlatformSemanticNodeApplier());
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/accessibility/SemanticNodeApplier.java b/core/java/com/android/internal/widget/remotecompose/accessibility/SemanticNodeApplier.java
new file mode 100644
index 0000000..4368329
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/accessibility/SemanticNodeApplier.java
@@ -0,0 +1,43 @@
+/*
+ * 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.accessibility;
+
+import java.util.List;
+
+/**
+ * An interface for applying semantic information to a semantics node.
+ *
+ * <p>Implementations of this interface are responsible for taking a node represented by [nodeInfo]
+ * and applying a list of [semantics] (representing accessible actions and properties) to it. This
+ * process might involve: - Modifying the node's properties (e.g., content description, clickable
+ * state). - Adding a child node to represent a specific semantic element. - Performing any other
+ * action necessary to make the node semantically meaningful and accessible to assistive
+ * technologies.
+ *
+ * @param <N> The type representing information about the node. This could be an Androidx
+ *     `AccessibilityNodeInfoCompat`, or potentially a platform `AccessibilityNodeInfo`.
+ * @param <C> The type of component in the remote Compose UI.
+ * @param <S> The type representing a single semantic property or action.
+ */
+public interface SemanticNodeApplier<N, C, S> {
+    void applyComponent(
+            RemoteComposeDocumentAccessibility<C, S> remoteComposeAccessibility,
+            N nodeInfo,
+            C component,
+            List<S> semantics);
+
+    String VIRTUAL_VIEW_ID_KEY = "VirtualViewId";
+}
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 370289a..5bc3bca 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
@@ -18,6 +18,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 
+import com.android.internal.annotations.VisibleForTesting;
 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;
@@ -28,7 +29,6 @@
 import com.android.internal.widget.remotecompose.core.operations.layout.Component;
 import com.android.internal.widget.remotecompose.core.operations.layout.ComponentEnd;
 import com.android.internal.widget.remotecompose.core.operations.layout.ComponentStartOperation;
-import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponent;
 import com.android.internal.widget.remotecompose.core.operations.layout.LoopEnd;
 import com.android.internal.widget.remotecompose.core.operations.layout.LoopOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.OperationsListEnd;
@@ -38,6 +38,7 @@
 import com.android.internal.widget.remotecompose.core.operations.layout.TouchUpModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentModifiers;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ScrollModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
 
 import java.util.ArrayList;
@@ -53,13 +54,14 @@
 public class CoreDocument {
 
     private static final boolean DEBUG = false;
+    private static final int DOCUMENT_API_LEVEL = 2;
 
     @NonNull ArrayList<Operation> mOperations = new ArrayList<>();
 
     @Nullable RootLayoutComponent mRootLayoutComponent = null;
 
     @NonNull RemoteComposeState mRemoteComposeState = new RemoteComposeState();
-    @NonNull TimeVariables mTimeVariables = new TimeVariables();
+    @VisibleForTesting @NonNull public TimeVariables mTimeVariables = new TimeVariables();
     // Semantic version of the document
     @NonNull Version mVersion = new Version(0, 1, 0);
 
@@ -86,6 +88,11 @@
 
     private int mLastId = 1; // last component id when inflating the file
 
+    /** Returns a version number that is monotonically increasing. */
+    public static int getDocumentApiLevel() {
+        return DOCUMENT_API_LEVEL;
+    }
+
     @Nullable
     public String getContentDescription() {
         return mContentDescription;
@@ -565,6 +572,7 @@
         TouchUpModifierOperation currentTouchUpModifier = null;
         TouchCancelModifierOperation currentTouchCancelModifier = null;
         LoopOperation currentLoop = null;
+        ScrollModifierOperation currentScrollModifier = null;
 
         mLastId = -1;
         for (Operation o : operations) {
@@ -579,8 +587,8 @@
                     mLastId = component.getComponentId();
                 }
             } else if (o instanceof ComponentEnd) {
-                if (currentComponent instanceof LayoutComponent) {
-                    ((LayoutComponent) currentComponent).inflate();
+                if (currentComponent != null) {
+                    currentComponent.inflate();
                 }
                 components.remove(components.size() - 1);
                 if (!components.isEmpty()) {
@@ -602,6 +610,9 @@
             } else if (o instanceof TouchCancelModifierOperation) {
                 currentTouchCancelModifier = (TouchCancelModifierOperation) o;
                 ops = currentTouchCancelModifier.getList();
+            } else if (o instanceof ScrollModifierOperation) {
+                currentScrollModifier = (ScrollModifierOperation) o;
+                ops = currentScrollModifier.getList();
             } else if (o instanceof OperationsListEnd) {
                 ops = currentComponent.getList();
                 if (currentClickModifier != null) {
@@ -616,6 +627,9 @@
                 } else if (currentTouchCancelModifier != null) {
                     ops.add(currentTouchCancelModifier);
                     currentTouchCancelModifier = null;
+                } else if (currentScrollModifier != null) {
+                    ops.add(currentScrollModifier);
+                    currentScrollModifier = null;
                 }
             } else if (o instanceof LoopOperation) {
                 currentLoop = (LoopOperation) o;
@@ -881,7 +895,7 @@
         }
         if (mRootLayoutComponent != null) {
             for (Component component : mAppliedTouchOperations) {
-                component.onTouchUp(context, this, x, y, true);
+                component.onTouchUp(context, this, x, y, dx, dy, true);
             }
             mAppliedTouchOperations.clear();
         }
@@ -1039,7 +1053,13 @@
                                 || context.getTheme() == Theme.UNSPECIFIED;
             }
             if (apply) {
-                op.apply(context);
+                if (op.isDirty() || op instanceof PaintOperation) {
+                    if (op.isDirty() && op instanceof VariableSupport) {
+                        op.markNotDirty();
+                        ((VariableSupport) op).updateVariables(context);
+                    }
+                    op.apply(context);
+                }
             }
         }
         if (context.getPaintContext().doesNeedsRepaint()
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 6f6a0a8..150ebd0 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Operation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Operation.java
@@ -20,6 +20,8 @@
 /** Base interface for RemoteCompose operations */
 public abstract class Operation {
 
+    private static final boolean ENABLE_DIRTY_FLAG_OPTIMIZATION = true;
+
     /** add the operation to the buffer */
     public abstract void write(@NonNull WireBuffer buffer);
 
@@ -33,4 +35,30 @@
     /** Debug utility to display an operation + indentation */
     @NonNull
     public abstract String deepToString(@NonNull String indent);
+
+    private boolean mDirty = true;
+
+    /** Mark the operation as "dirty" to indicate it will need to be re-executed. */
+    public void markDirty() {
+        mDirty = true;
+    }
+
+    /** Mark the operation as "not dirty" */
+    public void markNotDirty() {
+        if (ENABLE_DIRTY_FLAG_OPTIMIZATION) {
+            mDirty = false;
+        }
+    }
+
+    /**
+     * Returns true if the operation is marked as "dirty"
+     *
+     * @return true if dirty
+     */
+    public boolean isDirty() {
+        if (ENABLE_DIRTY_FLAG_OPTIMIZATION) {
+            return mDirty;
+        }
+        return true;
+    }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/OperationInterface.java b/core/java/com/android/internal/widget/remotecompose/core/OperationInterface.java
index 741303a..06beffc 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/OperationInterface.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/OperationInterface.java
@@ -33,4 +33,14 @@
     /** Debug utility to display an operation + indentation */
     @NonNull
     String deepToString(@NonNull String indent);
+
+    /**
+     * Returns true if the operation is marked as "dirty"
+     *
+     * @return true if dirty
+     */
+    boolean isDirty();
+
+    /** Mark the operation as "dirty" to indicate it will need to be re-executed. */
+    void markNotDirty();
 }
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 006fe3a..04e490f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Operations.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
@@ -98,8 +98,10 @@
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HostActionOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HostNamedActionOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.MarqueeModifierOperation;
 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.RippleModifierOperation;
 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;
@@ -110,6 +112,7 @@
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ZIndexModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.utilities.IntMap;
+import com.android.internal.widget.remotecompose.core.semantics.CoreSemantics;
 import com.android.internal.widget.remotecompose.core.types.BooleanConstant;
 import com.android.internal.widget.remotecompose.core.types.IntegerConstant;
 import com.android.internal.widget.remotecompose.core.types.LongConstant;
@@ -126,6 +129,9 @@
     public static final int CLICK_AREA = 64;
     public static final int ROOT_CONTENT_BEHAVIOR = 65;
     public static final int ROOT_CONTENT_DESCRIPTION = 103;
+    // TODO reorder before submitting
+    public static final int ACCESSIBILITY_SEMANTICS = 250;
+    //    public static final int ACCESSIBILITY_CUSTOM_ACTION = 251;
 
     ////////////////////////////////////////
     // Draw commands
@@ -223,6 +229,8 @@
     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 MODIFIER_MARQUEE = 228;
+    public static final int MODIFIER_RIPPLE = 229;
 
     public static final int LOOP_START = 215;
     public static final int LOOP_END = 216;
@@ -327,6 +335,8 @@
         map.put(MODIFIER_ZINDEX, ZIndexModifierOperation::read);
         map.put(MODIFIER_GRAPHICS_LAYER, GraphicsLayerModifierOperation::read);
         map.put(MODIFIER_SCROLL, ScrollModifierOperation::read);
+        map.put(MODIFIER_MARQUEE, MarqueeModifierOperation::read);
+        map.put(MODIFIER_RIPPLE, RippleModifierOperation::read);
 
         map.put(OPERATIONS_LIST_END, OperationsListEnd::read);
 
@@ -362,5 +372,8 @@
         map.put(PATH_TWEEN, PathTween::read);
         map.put(PATH_CREATE, PathCreate::read);
         map.put(PATH_ADD, PathAppend::read);
+
+        map.put(ACCESSIBILITY_SEMANTICS, CoreSemantics::read);
+        //        map.put(ACCESSIBILITY_CUSTOM_ACTION, CoreSemantics::read);
     }
 }
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 3a5d68d..0ae7a94 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
@@ -15,7 +15,6 @@
  */
 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;
@@ -81,6 +80,7 @@
 import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponentContent;
 import com.android.internal.widget.remotecompose.core.operations.layout.LoopEnd;
 import com.android.internal.widget.remotecompose.core.operations.layout.LoopOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.OperationsListEnd;
 import com.android.internal.widget.remotecompose.core.operations.layout.RootLayoutComponent;
 import com.android.internal.widget.remotecompose.core.operations.layout.managers.BoxLayout;
 import com.android.internal.widget.remotecompose.core.operations.layout.managers.CanvasLayout;
@@ -92,8 +92,10 @@
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.BorderModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ClipRectModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.GraphicsLayerModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.MarqueeModifierOperation;
 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.RippleModifierOperation;
 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;
@@ -1610,7 +1612,7 @@
      * create and animation based on description and return as an array of floats. see
      * addAnimatedFloat
      *
-     * @param duration the duration of the aimation
+     * @param duration the duration of the animation in seconds
      * @param type the type of animation
      * @param spec the parameters of the animation if any
      * @param initialValue the initial value if it animates to a start
@@ -1699,6 +1701,9 @@
         float notchMax = this.reserveFloatVariable();
         float touchExpressionDirection =
                 direction != 0 ? RemoteContext.FLOAT_TOUCH_POS_X : RemoteContext.FLOAT_TOUCH_POS_Y;
+
+        ScrollModifierOperation.apply(mBuffer, direction, positionId, max, notchMax);
+
         this.addTouchExpression(
                 positionId,
                 0f,
@@ -1707,20 +1712,13 @@
                 0f,
                 3,
                 new float[] {
-                    touchExpressionDirection,
-                    -1,
-                    // TODO: remove this CONTINUOUS_SEC hack...
-                    MUL,
-                    RemoteContext.FLOAT_CONTINUOUS_SEC,
-                    0f,
-                    MUL,
-                    ADD
+                    touchExpressionDirection, -1, MUL,
                 },
                 TouchExpression.STOP_NOTCHES_EVEN,
                 new float[] {notches, notchMax},
                 null);
 
-        ScrollModifierOperation.apply(mBuffer, direction, positionId, max, notchMax);
+        OperationsListEnd.apply(mBuffer);
     }
 
     /**
@@ -1786,6 +1784,38 @@
         ZIndexModifierOperation.apply(mBuffer, value);
     }
 
+    /** Add a ripple effect on touch down as a modifier */
+    public void addModifierRipple() {
+        RippleModifierOperation.apply(mBuffer);
+    }
+
+    /**
+     * Add a marquee modifier
+     *
+     * @param iterations
+     * @param animationMode
+     * @param repeatDelayMillis
+     * @param initialDelayMillis
+     * @param spacing
+     * @param velocity
+     */
+    public void addModifierMarquee(
+            int iterations,
+            int animationMode,
+            float repeatDelayMillis,
+            float initialDelayMillis,
+            float spacing,
+            float velocity) {
+        MarqueeModifierOperation.apply(
+                mBuffer,
+                iterations,
+                animationMode,
+                repeatDelayMillis,
+                initialDelayMillis,
+                spacing,
+                velocity);
+    }
+
     /**
      * Add a graphics layer
      *
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 f5f9e21..11e58ba 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
@@ -48,6 +48,10 @@
     private final IntMap<DataMap> mDataMapMap = new IntMap<>();
     private final IntMap<Object> mObjectMap = new IntMap<>();
 
+    // path information
+    private final IntMap<Object> mPathMap = new IntMap<>();
+    private final IntMap<float[]> mPathData = new IntMap<>();
+
     private final boolean[] mColorOverride = new boolean[MAX_COLORS];
     @NonNull private final IntMap<ArrayAccess> mCollectionMap = new IntMap<>();
 
@@ -131,12 +135,44 @@
         }
     }
 
-    private final IntMap<float[]> mPathData = new IntMap<>();
-
-    public void putPathData(int id, float[] data) {
-        mPathData.put(id, data);
+    /**
+     * Get the path asociated with the Data
+     *
+     * @param id
+     * @return
+     */
+    public Object getPath(int id) {
+        return mPathMap.get(id);
     }
 
+    /**
+     * Cache a path object. Object will be cleared if you update path data.
+     *
+     * @param id number asociated with path
+     * @param path the path object typically Android Path
+     */
+    public void putPath(int id, Object path) {
+        mPathMap.put(id, path);
+    }
+
+    /**
+     * The path data the Array of floats that is asoicated with the path It also removes the current
+     * path object.
+     *
+     * @param id the integer asociated with the data and path
+     * @param data the array of floats that represents the path
+     */
+    public void putPathData(int id, float[] data) {
+        mPathData.put(id, data);
+        mPathMap.remove(id);
+    }
+
+    /**
+     * Get the path data asociated with the id
+     *
+     * @param id number that represents the path
+     * @return path data
+     */
     public float[] getPathData(int id) {
         return mPathData.get(id);
     }
@@ -283,7 +319,7 @@
         ArrayList<VariableSupport> v = mVarListeners.get(id);
         if (v != null && mRemoteContext != null) {
             for (VariableSupport c : v) {
-                c.updateVariables(mRemoteContext);
+                c.markDirty();
             }
         }
     }
@@ -426,9 +462,6 @@
      * @return
      */
     public int getOpsToUpdate(@NonNull RemoteContext context) {
-        for (VariableSupport vs : mAllVarListeners) {
-            vs.updateVariables(context);
-        }
         if (mVarListeners.get(RemoteContext.ID_CONTINUOUS_SEC) != null) {
             return 1;
         }
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 6eb8463..003acb7 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
@@ -214,6 +214,13 @@
      */
     public abstract void hapticEffect(int type);
 
+    /** Set the repaint flag. This will trigger a repaint of the current document. */
+    public void needsRepaint() {
+        if (mPaintContext != null) {
+            mPaintContext.needsRepaint();
+        }
+    }
+
     /**
      * The context can be used in a few different mode, allowing operations to skip being executed:
      * - UNSET : all operations will get executed - DATA : only operations dealing with DATA (eg
diff --git a/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java b/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java
index 14aed2f..0ed6005 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java
@@ -29,9 +29,7 @@
      *
      * @param context
      */
-    public void updateTime(@NonNull RemoteContext context) {
-        LocalDateTime dateTime =
-                LocalDateTime.now(ZoneId.systemDefault()); // TODO, pass in a timezone explicitly?
+    public void updateTime(@NonNull RemoteContext context, ZoneId zoneId, LocalDateTime dateTime) {
         // This define the time in the format
         // seconds run from Midnight=0 quantized to seconds hour 0..3599
         // minutes run from Midnight=0 quantized to minutes 0..1439
@@ -48,8 +46,7 @@
         float sec = currentSeconds + dateTime.getNano() * 1E-9f;
         int day_week = dateTime.getDayOfWeek().getValue();
 
-        ZoneId zone = ZoneId.systemDefault();
-        OffsetDateTime offsetDateTime = dateTime.atZone(zone).toOffsetDateTime();
+        OffsetDateTime offsetDateTime = dateTime.atZone(zoneId).toOffsetDateTime();
         ZoneOffset offset = offsetDateTime.getOffset();
 
         context.loadFloat(RemoteContext.ID_OFFSET_TO_UTC, offset.getTotalSeconds());
@@ -61,4 +58,16 @@
         context.loadFloat(RemoteContext.ID_DAY_OF_MONTH, month);
         context.loadFloat(RemoteContext.ID_WEEK_DAY, day_week);
     }
+
+    /**
+     * This class populates all time variables in the system
+     *
+     * @param context
+     */
+    public void updateTime(@NonNull RemoteContext context) {
+        ZoneId zone = ZoneId.systemDefault();
+        LocalDateTime dateTime = LocalDateTime.now(zone);
+
+        updateTime(context, zone, dateTime);
+    }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/TouchListener.java b/core/java/com/android/internal/widget/remotecompose/core/TouchListener.java
index 3dda678..611ba97 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/TouchListener.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/TouchListener.java
@@ -15,10 +15,34 @@
  */
 package com.android.internal.widget.remotecompose.core;
 
+/** Interface used by objects to register for touch events */
 public interface TouchListener {
+    /**
+     * Called when touch down happens
+     *
+     * @param context The players context
+     * @param x the x location of the down touch
+     * @param y the y location of the down touch
+     */
     void touchDown(RemoteContext context, float x, float y);
 
+    /**
+     * called on touch up
+     *
+     * @param context the players context
+     * @param x the x location
+     * @param y the y location
+     * @param dx the x velocity when the touch up happened
+     * @param dy the y valocity when the touch up happened
+     */
     void touchUp(RemoteContext context, float x, float y, float dx, float dy);
 
+    /**
+     * Drag event (occur between down and up)
+     *
+     * @param context the players context
+     * @param x the x coord of the drag
+     * @param y the y coord of the drag
+     */
     void touchDrag(RemoteContext context, float x, float y);
 }
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 e9fa897..1f3e290 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/VariableSupport.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/VariableSupport.java
@@ -36,4 +36,7 @@
      * @param context
      */
     void updateVariables(@NonNull RemoteContext context);
+
+    /** Mark the operation as dirty to indicate that the variables it references are out of date. */
+    void markDirty();
 }
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 27ba652..784897b 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
@@ -45,15 +45,39 @@
     short mType;
     short mEncoding;
     @NonNull final byte[] mBitmap;
+
+    /** The max size of width or height */
     public static final int MAX_IMAGE_DIMENSION = 8000;
+
+    /** The data is encoded in the file (default) */
     public static final short ENCODING_INLINE = 0;
+
+    /** The data is encoded in the url */
     public static final short ENCODING_URL = 1;
+
+    /** The data is encoded as a reference to file */
     public static final short ENCODING_FILE = 2;
+
+    /** The data is encoded as PNG_8888 (default) */
     public static final short TYPE_PNG_8888 = 0;
+
+    /** The data is encoded as PNG */
     public static final short TYPE_PNG = 1;
+
+    /** The data is encoded as RAW 8 bit */
     public static final short TYPE_RAW8 = 2;
+
+    /** The data is encoded as RAW 8888 bit */
     public static final short TYPE_RAW8888 = 3;
 
+    /**
+     * create a bitmap structure
+     *
+     * @param imageId the id to store the image
+     * @param width the width of the image
+     * @param height the height of the image
+     * @param bitmap the data
+     */
     public BitmapData(int imageId, int width, int height, @NonNull byte[] bitmap) {
         this.mImageId = imageId;
         this.mImageWidth = width;
@@ -61,10 +85,20 @@
         this.mBitmap = bitmap;
     }
 
+    /**
+     * The width of the image
+     *
+     * @return the width
+     */
     public int getWidth() {
         return mImageWidth;
     }
 
+    /**
+     * The height of the image
+     *
+     * @return the height
+     */
     public int getHeight() {
         return mImageHeight;
     }
@@ -80,6 +114,11 @@
         return "BITMAP DATA " + mImageId;
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
@@ -94,6 +133,15 @@
         return OP_CODE;
     }
 
+    /**
+     * Add the image to the document
+     *
+     * @param buffer document to write to
+     * @param imageId the id the image will be stored under
+     * @param width the width of the image
+     * @param height the height of the image
+     * @param bitmap the data used to store/encode the image
+     */
     public static void apply(
             @NonNull WireBuffer buffer,
             int imageId,
@@ -107,6 +155,17 @@
         buffer.writeBuffer(bitmap);
     }
 
+    /**
+     * Add the image to the document (using the ehanced encoding)
+     *
+     * @param buffer document to write to
+     * @param imageId the id the image will be stored under
+     * @param type the type of image
+     * @param width the width of the image
+     * @param encoding the encoding
+     * @param height the height of the image
+     * @param bitmap the data used to store/encode the image
+     */
     public static void apply(
             @NonNull WireBuffer buffer,
             int imageId,
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 5e5e565..efd31af 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
@@ -24,11 +24,12 @@
 import com.android.internal.widget.remotecompose.core.WireBuffer;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent;
 
 import java.util.List;
 
 /** Add a click area to the document */
-public class ClickArea extends Operation implements RemoteComposeOperation {
+public class ClickArea extends Operation implements RemoteComposeOperation, AccessibleComponent {
     private static final int OP_CODE = Operations.CLICK_AREA;
     private static final String CLASS_NAME = "ClickArea";
     int mId;
@@ -113,6 +114,11 @@
         return indent + toString();
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
@@ -127,6 +133,21 @@
         return OP_CODE;
     }
 
+    @Override
+    public Integer getContentDescriptionId() {
+        return mContentDescription;
+    }
+
+    /**
+     * @param buffer
+     * @param id
+     * @param contentDescription
+     * @param left
+     * @param top
+     * @param right
+     * @param bottom
+     * @param metadata
+     */
     public static void apply(
             @NonNull WireBuffer buffer,
             int id,
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java
index 2fe56d3..b55f25c 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java
@@ -83,6 +83,11 @@
         operations.add(op);
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java
index defa656..5a495d5 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java
@@ -28,8 +28,8 @@
 
 /** Support clip with a rectangle */
 public class ClipRect extends DrawBase4 {
-    public static final int OP_CODE = Operations.CLIP_RECT;
-    public static final String CLASS_NAME = "ClipRect";
+    private static final int OP_CODE = Operations.CLIP_RECT;
+    private static final String CLASS_NAME = "ClipRect";
 
     /**
      * Read this operation and add it to the list of operations
@@ -51,6 +51,11 @@
         return OP_CODE;
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
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 d86576d..6802015 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
@@ -61,6 +61,11 @@
         return "ColorConstant[" + mColorId + "] = " + Utils.colorInt(mColor) + "";
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
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 66f128f..b385ecd 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
@@ -199,6 +199,11 @@
                 + ")";
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
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 19c219b..3e85236 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
@@ -31,8 +31,8 @@
 import java.util.List;
 
 public class ComponentValue extends Operation implements SerializableToString {
-    public static final int OP_CODE = Operations.COMPONENT_VALUE;
-    public static final String CLASS_NAME = "ComponentValue";
+    private static final int OP_CODE = Operations.COMPONENT_VALUE;
+    private static final String CLASS_NAME = "ComponentValue";
 
     public static final int WIDTH = 0;
     public static final int HEIGHT = 1;
@@ -50,6 +50,11 @@
         return OP_CODE;
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
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 e888074..ff85721 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
@@ -129,6 +129,11 @@
         operations.add(data);
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Data Operations", OP_CODE, CLASS_NAME)
                 .description("Encode a collection of name id pairs")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java
index 3f95f02..fd1f410 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java
@@ -26,8 +26,9 @@
 
 import java.util.List;
 
+/** Draw an Arc command the specified arc, will be scaled to fit inside the specified oval. */
 public class DrawArc extends DrawBase6 {
-    public static final int OP_CODE = Operations.DRAW_ARC;
+    private static final int OP_CODE = Operations.DRAW_ARC;
     private static final String CLASS_NAME = "DrawArc";
 
     /**
@@ -114,8 +115,20 @@
                         "Sweep angle (in degrees) measured clockwise");
     }
 
-    public DrawArc(float v1, float v2, float v3, float v4, float v5, float v6) {
-        super(v1, v2, v3, v4, v5, v6);
+    /**
+     * Create Draw Arc command Draw the specified arc, which will be scaled to fit inside the
+     * specified oval.
+     *
+     * @param left the left side of the oval
+     * @param top the top of the oval
+     * @param right the right side of the oval
+     * @param bottom the bottom of the oval
+     * @param startAngle Starting angle (in degrees) where the arc begins
+     * @param sweepAngle Sweep angle (in degrees) measured clockwise
+     */
+    public DrawArc(
+            float left, float top, float right, float bottom, float startAngle, float sweepAngle) {
+        super(left, top, right, bottom, startAngle, sweepAngle);
         mName = "DrawArc";
     }
 
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 6c288a35..64c2730 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
@@ -145,6 +145,11 @@
         return null;
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "DrawBase6";
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java
index 69f5cc5..cdb527d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java
@@ -118,6 +118,11 @@
         operations.add(op);
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java
index 66646d7..638fe14 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java
@@ -24,11 +24,12 @@
 import com.android.internal.widget.remotecompose.core.WireBuffer;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent;
 
 import java.util.List;
 
 /** Operation to draw a given cached bitmap */
-public class DrawBitmapInt extends PaintOperation {
+public class DrawBitmapInt extends PaintOperation implements AccessibleComponent {
     private static final int OP_CODE = Operations.DRAW_BITMAP_INT;
     private static final String CLASS_NAME = "DrawBitmapInt";
     int mImageId;
@@ -106,6 +107,16 @@
                 + ";";
     }
 
+    @Override
+    public Integer getContentDescriptionId() {
+        return mContentDescId;
+    }
+
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
@@ -170,6 +181,11 @@
         operations.add(op);
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Draw Operations", OP_CODE, CLASS_NAME)
                 .description("Draw a bitmap using integer coordinates")
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 1701486..d6467c9 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
@@ -27,11 +27,13 @@
 import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
 import com.android.internal.widget.remotecompose.core.operations.utilities.ImageScaling;
+import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent;
 
 import java.util.List;
 
 /** Operation to draw a given cached bitmap */
-public class DrawBitmapScaled extends PaintOperation implements VariableSupport {
+public class DrawBitmapScaled extends PaintOperation
+        implements VariableSupport, AccessibleComponent {
     private static final int OP_CODE = Operations.DRAW_BITMAP_SCALED;
     private static final String CLASS_NAME = "DrawBitmapScaled";
     int mImageId;
@@ -191,6 +193,16 @@
                 + Utils.floatToString(mScaleFactor, mOutScaleFactor);
     }
 
+    @Override
+    public Integer getContentDescriptionId() {
+        return mContentDescId;
+    }
+
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java
index e6aecdb..735e262 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java
@@ -50,6 +50,11 @@
         return OP_CODE;
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java
index 04f3264..f3a190d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java
@@ -52,6 +52,11 @@
         return OP_CODE;
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java
index 0a50042..a009874 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java
@@ -50,6 +50,11 @@
         return OP_CODE;
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java
index 41b8243..398cf48 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java
@@ -62,6 +62,11 @@
         operations.add(op);
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java
index 7e22550..38477ad 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java
@@ -51,6 +51,11 @@
         return OP_CODE;
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawSector.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawSector.java
index 7616df0..51ece77 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawSector.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawSector.java
@@ -27,7 +27,7 @@
 import java.util.List;
 
 public class DrawSector extends DrawBase6 {
-    public static final int OP_CODE = Operations.DRAW_SECTOR;
+    private static final int OP_CODE = Operations.DRAW_SECTOR;
     private static final String CLASS_NAME = "DrawSector";
 
     /**
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java
index 2c5d790..8adba1d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java
@@ -121,6 +121,11 @@
         operations.add(op);
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java
index 7de52b8..f839922 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java
@@ -130,6 +130,11 @@
         operations.add(op);
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java
index 18d9fdf..86f3c99 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java
@@ -98,6 +98,11 @@
         operations.add(op);
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "DrawTextOnPath";
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java
index b83e4c2..d4d4a5e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java
@@ -108,6 +108,11 @@
         operations.add(op);
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "DrawTweenPath";
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 7dd435a..e04e691 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
@@ -51,6 +51,11 @@
         return "FloatConstant[" + mTextId + "] = " + mValue;
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
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 3d92e12..c1872fd 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
@@ -139,6 +139,11 @@
         }
     }
 
+    // Keep track of the last computed value when we are animated,
+    // e.g. if FloatAnimation or Spring is used, so that we can
+    // ask for a repaint.
+    float mLastAnimatedValue = Float.NaN;
+
     @Override
     public void apply(@NonNull RemoteContext context) {
         updateVariables(context);
@@ -146,12 +151,23 @@
         if (Float.isNaN(mLastChange)) {
             mLastChange = t;
         }
+        float lastComputedValue;
         if (mFloatAnimation != null && !Float.isNaN(mLastCalculatedValue)) {
             float f = mFloatAnimation.get(t - mLastChange);
             context.loadFloat(mId, f);
+            lastComputedValue = f;
+            if (lastComputedValue != mLastAnimatedValue) {
+                mLastAnimatedValue = lastComputedValue;
+                context.needsRepaint();
+            }
         } else if (mSpring != null) {
             float f = mSpring.get(t - mLastChange);
             context.loadFloat(mId, f);
+            lastComputedValue = f;
+            if (lastComputedValue != mLastAnimatedValue) {
+                mLastAnimatedValue = lastComputedValue;
+                context.needsRepaint();
+            }
         } else {
             float v =
                     mExp.eval(context.getCollectionsAccess(), mPreCalcValue, mPreCalcValue.length);
@@ -205,6 +221,11 @@
                 + ")";
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
@@ -236,6 +257,9 @@
         buffer.writeInt(id);
 
         int len = value.length;
+        if (len > MAX_EXPRESSION_SIZE) {
+            throw new RuntimeException(AnimatedFloatExpression.toString(value, null) + " to long");
+        }
         if (animation != null) {
             len |= (animation.length << 16);
         }
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 04e4346..656dc09 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
@@ -39,7 +39,7 @@
     private static final int OP_CODE = Operations.HEADER;
     private static final String CLASS_NAME = "Header";
     public static final int MAJOR_VERSION = 0;
-    public static final int MINOR_VERSION = 1;
+    public static final int MINOR_VERSION = 2;
     public static final int PATCH_VERSION = 0;
 
     int mMajorVersion;
@@ -115,6 +115,11 @@
         return toString();
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
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 67274af..f04f30d 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
@@ -136,6 +136,11 @@
         return "IntegerExpression[" + mId + "] = (" + s + ")";
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
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 aed597a..044430d 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
@@ -26,6 +26,7 @@
 
 import java.util.List;
 
+/** The restore previous matrix command */
 public class MatrixRestore extends PaintOperation {
     private static final int OP_CODE = Operations.MATRIX_RESTORE;
     private static final String CLASS_NAME = "MatrixRestore";
@@ -54,6 +55,11 @@
         return "MatrixRestore";
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java
index fece143..57f5a0e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java
@@ -26,8 +26,9 @@
 
 import java.util.List;
 
+/** The rotate the rendering command */
 public class MatrixRotate extends DrawBase3 {
-    public static final int OP_CODE = Operations.MATRIX_ROTATE;
+    private static final int OP_CODE = Operations.MATRIX_ROTATE;
     private static final String CLASS_NAME = "MatrixRotate";
 
     /**
@@ -57,6 +58,11 @@
         return OP_CODE;
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
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 7eb7b3f..aec316a 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
@@ -26,6 +26,7 @@
 
 import java.util.List;
 
+/** The save the matrix state command */
 public class MatrixSave extends PaintOperation {
     private static final int OP_CODE = Operations.MATRIX_SAVE;
     private static final String CLASS_NAME = "MatrixSave";
@@ -52,6 +53,11 @@
         operations.add(op);
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java
index 49bdd1b..07f965f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java
@@ -26,9 +26,10 @@
 
 import java.util.List;
 
+/** Scale the rendering matrix command */
 public class MatrixScale extends DrawBase4 {
-    public static final int OP_CODE = Operations.MATRIX_SCALE;
-    public static final String CLASS_NAME = "MatrixScale";
+    private static final int OP_CODE = Operations.MATRIX_SCALE;
+    private static final String CLASS_NAME = "MatrixScale";
 
     /**
      * Read this operation and add it to the list of operations
@@ -50,6 +51,11 @@
         return OP_CODE;
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java
index 54b6fd1..b31492d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java
@@ -27,9 +27,10 @@
 
 import java.util.List;
 
+/** Skew the matrix command */
 public class MatrixSkew extends DrawBase2 {
-    public static final int OP_CODE = Operations.MATRIX_SKEW;
-    public static final String CLASS_NAME = "MatrixSkew";
+    private static final int OP_CODE = Operations.MATRIX_SKEW;
+    private static final String CLASS_NAME = "MatrixSkew";
 
     /**
      * Read this operation and add it to the list of operations
@@ -51,6 +52,11 @@
         return OP_CODE;
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java
index b57d83b..11fa040 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java
@@ -26,9 +26,10 @@
 
 import java.util.List;
 
+/** translate the matrix command */
 public class MatrixTranslate extends DrawBase2 {
-    public static final int OP_CODE = Operations.MATRIX_TRANSLATE;
-    public static final String CLASS_NAME = "MatrixTranslate";
+    private static final int OP_CODE = Operations.MATRIX_TRANSLATE;
+    private static final String CLASS_NAME = "MatrixTranslate";
 
     /**
      * Read this operation and add it to the list of operations
@@ -50,6 +51,11 @@
         return OP_CODE;
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
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 3c82f2b..dde632e 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
@@ -64,6 +64,11 @@
                 + mVarType;
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
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 3c0a842..daf2c55 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
@@ -61,6 +61,11 @@
         return "PaintData " + "\"" + mPaintData + "\"";
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
@@ -92,6 +97,11 @@
         operations.add(data);
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Data Operations", OP_CODE, CLASS_NAME)
                 .description("Encode a Paint ")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java
index 2b00001..7ff879e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathAppend.java
@@ -109,6 +109,11 @@
     public static final float CLOSE_NAN = Utils.asNan(CLOSE);
     public static final float DONE_NAN = Utils.asNan(DONE);
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
@@ -162,11 +167,12 @@
     }
 
     @Override
-    public void paint(PaintContext context) {}
+    public void paint(PaintContext context) {
+        apply(context.getContext());
+    }
 
     @Override
     public void apply(@NonNull RemoteContext context) {
-        updateVariables(context);
         float[] data = context.getPathData(mInstanceId);
         float[] out = mOutputPath;
         if (data != null) {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java
index b62f36b..75562cd 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathCreate.java
@@ -48,6 +48,7 @@
 
     @Override
     public void updateVariables(@NonNull RemoteContext context) {
+
         for (int i = 0; i < mFloatPath.length; i++) {
             float v = mFloatPath[i];
             if (Utils.isVariable(v)) {
@@ -81,7 +82,19 @@
     @NonNull
     @Override
     public String toString() {
-        return "PathCreate[" + mInstanceId + "] = " + "\"" + deepToString(" ") + "\"";
+        return "PathCreate["
+                + mInstanceId
+                + "] = "
+                + "\""
+                + deepToString(" ")
+                + "\"["
+                + Utils.idStringFromNan(mFloatPath[1])
+                + "] "
+                + mOutputPath[1]
+                + " ["
+                + Utils.idStringFromNan(mFloatPath[2])
+                + "] "
+                + mOutputPath[2];
     }
 
     public static final int MOVE = 10;
@@ -99,6 +112,11 @@
     public static final float CLOSE_NAN = Utils.asNan(CLOSE);
     public static final float DONE_NAN = Utils.asNan(DONE);
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
@@ -197,7 +215,9 @@
     }
 
     @Override
-    public void paint(PaintContext context) {}
+    public void paint(PaintContext context) {
+        apply(context.getContext());
+    }
 
     @Override
     public void apply(@NonNull RemoteContext context) {
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 4ec5436..85a01fc 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
@@ -38,6 +38,7 @@
     int mInstanceId;
     float[] mFloatPath;
     float[] mOutputPath;
+    private boolean mPathChanged = true;
 
     PathData(int instanceId, float[] floatPath) {
         mInstanceId = instanceId;
@@ -50,7 +51,11 @@
         for (int i = 0; i < mFloatPath.length; i++) {
             float v = mFloatPath[i];
             if (Utils.isVariable(v)) {
+                float tmp = mOutputPath[i];
                 mOutputPath[i] = Float.isNaN(v) ? context.getFloat(Utils.idFromNan(v)) : v;
+                if (tmp != mOutputPath[i]) {
+                    mPathChanged = true;
+                }
             } else {
                 mOutputPath[i] = v;
             }
@@ -107,6 +112,11 @@
     public static final float CLOSE_NAN = Utils.asNan(CLOSE);
     public static final float DONE_NAN = Utils.asNan(DONE);
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
@@ -216,6 +226,9 @@
 
     @Override
     public void apply(@NonNull RemoteContext context) {
-        context.loadPathData(mInstanceId, mOutputPath);
+        if (mPathChanged) {
+            context.loadPathData(mInstanceId, mOutputPath);
+        }
+        mPathChanged = false;
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathTween.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathTween.java
index a6fa680..65adfea 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/PathTween.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathTween.java
@@ -80,6 +80,11 @@
                 + floatToString(mTween, mTweenOut);
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
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 aaa7176..55dd882 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
@@ -200,6 +200,11 @@
         return toString();
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
@@ -239,6 +244,11 @@
         operations.add(rootContentBehavior);
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Protocol Operations", OP_CODE, CLASS_NAME)
                 .description("Describes the behaviour of the root")
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 e92daa3..ad86e0f 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
@@ -24,11 +24,13 @@
 import com.android.internal.widget.remotecompose.core.WireBuffer;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation;
+import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent;
 
 import java.util.List;
 
 /** Describe a content description for the document */
-public class RootContentDescription extends Operation implements RemoteComposeOperation {
+public class RootContentDescription extends Operation
+        implements RemoteComposeOperation, AccessibleComponent {
     private static final int OP_CODE = Operations.ROOT_CONTENT_DESCRIPTION;
     private static final String CLASS_NAME = "RootContentDescription";
     int mContentDescription;
@@ -43,6 +45,11 @@
     }
 
     @Override
+    public boolean isInterestingForSemantics() {
+        return mContentDescription != 0;
+    }
+
+    @Override
     public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mContentDescription);
     }
@@ -64,6 +71,16 @@
         return toString();
     }
 
+    @Override
+    public Integer getContentDescriptionId() {
+        return mContentDescription;
+    }
+
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
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 e2502fe..8e4098e 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
@@ -198,6 +198,11 @@
         }
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
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 3f679bf..d48de37 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
@@ -54,6 +54,11 @@
         return "TextData[" + mTextId + "] = \"" + Utils.trimString(mText, 10) + "\"";
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java
index 4d01e0c..cc0ff02 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
@@ -122,6 +122,11 @@
         }
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
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 3ec6f01..dceb8b6 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
@@ -79,6 +79,11 @@
         }
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
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 9c0ee53..823b706 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
@@ -72,6 +72,11 @@
         context.listensTo(mIndex, this);
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
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 5b0c38f..d695615 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
@@ -53,6 +53,11 @@
         return "TextMerge[" + mTextId + "] = [" + mSrcId1 + " ] + [ " + mSrcId2 + "]";
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
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 e329c38d..6c9105d 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
@@ -72,6 +72,11 @@
         return indent + toString();
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
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 e2e20bc..f42abfc 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
@@ -21,6 +21,7 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.SHORT;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
@@ -30,6 +31,7 @@
 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.Component;
+import com.android.internal.widget.remotecompose.core.operations.layout.RootLayoutComponent;
 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.touch.VelocityEasing;
@@ -80,14 +82,41 @@
     int mTouchEffects;
     float mVelocityId;
 
+    /** Stop with some deceleration */
     public static final int STOP_GENTLY = 0;
+
+    /** Stop only at the start or end */
     public static final int STOP_ENDS = 2;
+
+    /** Stop on touch up */
     public static final int STOP_INSTANTLY = 1;
+
+    /** Stop at evenly spaced notches */
     public static final int STOP_NOTCHES_EVEN = 3;
+
+    /** Stop at a collection points described in percents of the range */
     public static final int STOP_NOTCHES_PERCENTS = 4;
+
+    /** Stop at a collectiond of point described in abslute cordnates */
     public static final int STOP_NOTCHES_ABSOLUTE = 5;
+
+    /** Jump to the absloute poition of the point */
     public static final int STOP_ABSOLUTE_POS = 6;
 
+    /**
+     * create a touch expression
+     *
+     * @param id The float id the value is output to
+     * @param exp the expression (containing TOUCH_* )
+     * @param defValue the default value
+     * @param min the minimum value
+     * @param max the maximum value
+     * @param touchEffects the type of touch mode
+     * @param velocityId the valocity (not used)
+     * @param stopMode the behavour on touch oup
+     * @param stopSpec the paraameters that affect the touch up behavour
+     * @param easingSpec the easing parameters for coming to a stop
+     */
     public TouchExpression(
             int id,
             float[] exp,
@@ -129,7 +158,6 @@
 
     @Override
     public void updateVariables(RemoteContext context) {
-
         if (mPreCalcValue == null || mPreCalcValue.length != mSrcExp.length) {
             mPreCalcValue = new float[mSrcExp.length];
         }
@@ -192,7 +220,9 @@
         if (Float.isNaN(mDefValue)) {
             context.listensTo(Utils.idFromNan(mDefValue), this);
         }
-        context.addTouchListener(this);
+        if (mComponent == null) {
+            context.addTouchListener(this);
+        }
         for (float v : mSrcExp) {
             if (Float.isNaN(v)
                     && !AnimatedFloatExpression.isMathOperator(v)
@@ -332,9 +362,25 @@
 
     float mScrLeft, mScrRight, mScrTop, mScrBottom;
 
-    @Override
-    public void apply(RemoteContext context) {
-        Component comp = context.mLastComponent;
+    @Nullable Component mComponent;
+
+    /**
+     * Set the component the touch expression is in (if any)
+     * @param component the component, or null if outside
+     */
+    public void setComponent(@Nullable Component component) {
+        mComponent = component;
+        if (mComponent != null) {
+            try {
+                RootLayoutComponent root = mComponent.getRoot();
+                root.setHasTouchListeners(true);
+            } catch (Exception e) {
+            }
+        }
+    }
+
+    private void updateBounds() {
+        Component comp = mComponent;
         if (comp != null) {
             float x = comp.getX();
             float y = comp.getY();
@@ -351,7 +397,11 @@
             mScrRight = w + x;
             mScrBottom = h + y;
         }
-        updateVariables(context);
+    }
+
+    @Override
+    public void apply(RemoteContext context) {
+        updateBounds();
         if (mUnmodified) {
             mCurrentValue = mOutDefValue;
             context.loadFloat(mId, wrap(mCurrentValue));
@@ -371,6 +421,7 @@
                 mEasingToStop = false;
             }
             crossNotchCheck(context);
+            context.needsRepaint();
             return;
         }
         if (mTouchDown) {
@@ -395,11 +446,11 @@
 
     @Override
     public void touchDown(RemoteContext context, float x, float y) {
-
         if (!(x >= mScrLeft && x <= mScrRight && y >= mScrTop && y <= mScrBottom)) {
             Utils.log("NOT IN WINDOW " + x + ", " + y + " " + mScrLeft + ", " + mScrTop);
             return;
         }
+        mEasingToStop = false;
         mTouchDown = true;
         mUnmodified = false;
         if (mMode == 0) {
@@ -407,6 +458,7 @@
             mDownTouchValue =
                     mExp.eval(context.getCollectionsAccess(), mPreCalcValue, mPreCalcValue.length);
         }
+        context.needsRepaint();
     }
 
     @Override
@@ -441,6 +493,7 @@
         float time = mMaxTime * Math.abs(dest - value) / (2 * mMaxVelocity);
         mEasyTouch.config(value, dest, slope, time, mMaxAcceleration, mMaxVelocity, null);
         mEasingToStop = true;
+        context.needsRepaint();
     }
 
     @Override
@@ -449,7 +502,7 @@
             return;
         }
         apply(context);
-        context.getDocument().getRootLayoutComponent().needsRepaint();
+        context.needsRepaint();
     }
 
     @Override
@@ -494,6 +547,12 @@
 
     // ===================== static ======================
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
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 0f84059..1c24160 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
@@ -23,8 +23,23 @@
 
 /** Operations representing actions on the document */
 public interface ActionOperation {
+    /**
+     * Serialize the string
+     *
+     * @param indent padding to display
+     * @param serializer append the string
+     */
     void serializeToString(int indent, @NonNull StringSerializer serializer);
 
+    /**
+     * Run the action
+     *
+     * @param context remote context
+     * @param document document
+     * @param component component
+     * @param x the x location of the action
+     * @param y the y location of the action
+     */
     void runAction(
             @NonNull RemoteContext context,
             @NonNull CoreDocument document,
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 19f4c2b..652ab2b 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
@@ -20,6 +20,7 @@
 import com.android.internal.widget.remotecompose.core.operations.utilities.easing.FloatAnimation;
 import com.android.internal.widget.remotecompose.core.operations.utilities.easing.GeneralEasing;
 
+/** Value animation for layouts */
 public class AnimatableValue {
     boolean mIsVariable = false;
     int mId = 0;
@@ -34,6 +35,11 @@
     int mMotionEasingType = GeneralEasing.CUBIC_STANDARD;
     FloatAnimation mMotionEasing;
 
+    /**
+     * Value to animate
+     *
+     * @param value value
+     */
     public AnimatableValue(float value) {
         if (Utils.isVariable(value)) {
             mId = Utils.idFromNan(value);
@@ -43,10 +49,21 @@
         }
     }
 
+    /**
+     * Get the value
+     *
+     * @return the value
+     */
     public float getValue() {
         return mValue;
     }
 
+    /**
+     * Evaluate going through FloatAnimation if needed
+     *
+     * @param context the paint context
+     * @return the current value
+     */
     public float evaluate(PaintContext context) {
         if (!mIsVariable) {
             return mValue;
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 121b180..34b7a23 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
@@ -41,6 +41,11 @@
         super(parent, componentId, animationId, x, y, width, height);
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "CanvasContent";
@@ -77,6 +82,11 @@
         operations.add(new CanvasContent(componentId, 0, 0, 0, 0, null, -1));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .field(INT, "COMPONENT_ID", "unique id for this component")
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 34c4249..dcf1d25 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,6 +16,7 @@
 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;
@@ -33,13 +34,15 @@
 import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
 import com.android.internal.widget.remotecompose.core.operations.utilities.easing.Easing;
 import com.android.internal.widget.remotecompose.core.operations.utilities.easing.FloatAnimation;
+import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent;
+import com.android.internal.widget.remotecompose.core.semantics.CoreSemantics;
 
 import java.util.ArrayList;
 import java.util.List;
 
 /** Represents a click modifier + actions */
 public class ClickModifierOperation extends PaintOperation
-        implements ModifierOperation, DecoratorComponent, ClickHandler {
+        implements ModifierOperation, DecoratorComponent, ClickHandler, AccessibleComponent {
     private static final int OP_CODE = Operations.MODIFIER_CLICK;
 
     long mAnimateRippleStart = 0;
@@ -54,6 +57,22 @@
 
     @NonNull PaintBundle mPaint = new PaintBundle();
 
+    @Override
+    public boolean isClickable() {
+        return true;
+    }
+
+    @Nullable
+    @Override
+    public Role getRole() {
+        return Role.BUTTON;
+    }
+
+    @Override
+    public CoreSemantics.Mode getMode() {
+        return CoreSemantics.Mode.MERGE;
+    }
+
     public void animateRipple(float x, float y) {
         mAnimateRippleStart = System.currentTimeMillis();
         mAnimateRippleX = x;
@@ -80,6 +99,10 @@
 
     @Override
     public void apply(@NonNull RemoteContext context) {
+        RootLayoutComponent root = context.getDocument().getRootLayoutComponent();
+        if (root != null) {
+            root.setHasTouchListeners(true);
+        }
         for (Operation op : mList) {
             if (op instanceof TextData) {
                 op.apply(context);
@@ -136,7 +159,8 @@
     }
 
     @Override
-    public void layout(@NonNull RemoteContext context, float width, float height) {
+    public void layout(
+            @NonNull RemoteContext context, Component component, float width, float height) {
         mWidth = width;
         mHeight = height;
     }
@@ -173,6 +197,11 @@
         context.hapticEffect(3);
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "ClickModifier";
@@ -192,6 +221,11 @@
         operations.add(new ClickModifierOperation());
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", OP_CODE, name())
                 .description(
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 faa259f..e95dfda 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
@@ -28,6 +28,7 @@
 import com.android.internal.widget.remotecompose.core.WireBuffer;
 import com.android.internal.widget.remotecompose.core.operations.ComponentValue;
 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.animation.AnimateMeasure;
 import com.android.internal.widget.remotecompose.core.operations.layout.animation.AnimationSpec;
 import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
@@ -119,6 +120,17 @@
         mHeight = value;
     }
 
+    @Override
+    public void apply(@NonNull RemoteContext context) {
+        for (Operation op : mList) {
+            if (op instanceof VariableSupport && op.isDirty()) {
+                op.markNotDirty();
+                ((VariableSupport) op).updateVariables(context);
+            }
+        }
+        super.apply(context);
+    }
+
     /**
      * Utility function to update variables referencing this component dimensions
      *
@@ -233,14 +245,6 @@
         if (!mComponentValues.isEmpty()) {
             updateComponentValues(context);
         }
-        for (Operation o : mList) {
-            if (o instanceof Component) {
-                ((Component) o).updateVariables(context);
-            }
-            if (o instanceof VariableSupport) {
-                o.apply(context);
-            }
-        }
         context.mLastComponent = prev;
     }
 
@@ -248,14 +252,40 @@
         mComponentValues.add(v);
     }
 
-    public float intrinsicWidth() {
+    /**
+     * Returns the intrinsic width of the layout
+     *
+     * @param context
+     * @return the width in pixels
+     */
+    public float intrinsicWidth(@Nullable RemoteContext context) {
         return getWidth();
     }
 
-    public float intrinsicHeight() {
+    /**
+     * Returns the intrinsic height of the layout
+     *
+     * @param context
+     * @return the height in pixels
+     */
+    public float intrinsicHeight(@Nullable RemoteContext context) {
         return getHeight();
     }
 
+    /**
+     * This function is called after a component is created, with its mList initialized. This let
+     * the component a chance to do some post-initialization work on its children ops.
+     */
+    public void inflate() {
+        for (Operation op : mList) {
+            if (op instanceof TouchExpression) {
+                // Make sure to set the component of a touch expression that belongs to us!
+                TouchExpression touchExpression = (TouchExpression) op;
+                touchExpression.setComponent(this);
+            }
+        }
+    }
+
     public enum Visibility {
         GONE,
         VISIBLE,
@@ -409,11 +439,23 @@
             if (op instanceof TouchHandler) {
                 ((TouchHandler) op).onTouchDown(context, document, this, cx, cy);
             }
+            if (op instanceof TouchExpression) {
+                TouchExpression touchExpression = (TouchExpression) op;
+                touchExpression.updateVariables(context);
+                touchExpression.touchDown(context, cx, cy);
+                document.appliedTouchOperation(this);
+            }
         }
     }
 
     public void onTouchUp(
-            RemoteContext context, CoreDocument document, float x, float y, boolean force) {
+            RemoteContext context,
+            CoreDocument document,
+            float x,
+            float y,
+            float dx,
+            float dy,
+            boolean force) {
         if (!force && !contains(x, y)) {
             return;
         }
@@ -421,10 +463,15 @@
         float cy = y - getScrollY();
         for (Operation op : mList) {
             if (op instanceof Component) {
-                ((Component) op).onTouchUp(context, document, cx, cy, force);
+                ((Component) op).onTouchUp(context, document, cx, cy, dx, dy, force);
             }
             if (op instanceof TouchHandler) {
-                ((TouchHandler) op).onTouchUp(context, document, this, cx, cy);
+                ((TouchHandler) op).onTouchUp(context, document, this, cx, cy, dx, dy);
+            }
+            if (op instanceof TouchExpression) {
+                TouchExpression touchExpression = (TouchExpression) op;
+                touchExpression.updateVariables(context);
+                touchExpression.touchUp(context, cx, cy, dx, dy);
             }
         }
     }
@@ -443,6 +490,11 @@
             if (op instanceof TouchHandler) {
                 ((TouchHandler) op).onTouchCancel(context, document, this, cx, cy);
             }
+            if (op instanceof TouchExpression) {
+                TouchExpression touchExpression = (TouchExpression) op;
+                touchExpression.updateVariables(context);
+                touchExpression.touchUp(context, cx, cy, 0, 0);
+            }
         }
     }
 
@@ -460,6 +512,11 @@
             if (op instanceof TouchHandler) {
                 ((TouchHandler) op).onTouchDrag(context, document, this, cx, cy);
             }
+            if (op instanceof TouchExpression) {
+                TouchExpression touchExpression = (TouchExpression) op;
+                touchExpression.updateVariables(context);
+                touchExpression.touchDrag(context, x, y);
+            }
         }
     }
 
@@ -641,6 +698,9 @@
     }
 
     public void paintingComponent(@NonNull PaintContext context) {
+        if (!mComponentValues.isEmpty()) {
+            updateComponentValues(context.getContext());
+        }
         if (mPreTranslate != null) {
             mPreTranslate.paint(context);
         }
@@ -652,7 +712,15 @@
             debugBox(this, context);
         }
         for (Operation op : mList) {
-            op.apply(context.getContext());
+            if (op.isDirty() && op instanceof VariableSupport) {
+                ((VariableSupport) op).updateVariables(context.getContext());
+                op.markNotDirty();
+            }
+            if (op instanceof PaintOperation) {
+                ((PaintOperation) op).paint(context);
+            } else {
+                op.apply(context.getContext());
+            }
         }
         context.restore();
         context.getContext().mLastComponent = prev;
@@ -661,7 +729,7 @@
     public boolean applyAnimationAsNeeded(@NonNull PaintContext context) {
         if (context.isAnimationEnabled() && mAnimateMeasure != null) {
             mAnimateMeasure.apply(context);
-            needsRepaint();
+            context.needsRepaint();
             return true;
         }
         return false;
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 396644c..5da0663 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
@@ -49,6 +49,11 @@
         return (indent != null ? indent : "") + toString();
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "ComponentEnd";
@@ -81,6 +86,11 @@
         operations.add(new ComponentEnd());
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .description(
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 a85ae27..4349b31 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
@@ -157,6 +157,11 @@
         }
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "ComponentStart";
@@ -198,6 +203,11 @@
         operations.add(new ComponentStart(type, componentId, width, height));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .description(
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 d617007..9ca2f2e 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
@@ -24,5 +24,13 @@
  * measured. Eg borders, background, clips, etc.
  */
 public interface DecoratorComponent {
-    void layout(@NonNull RemoteContext context, float width, float height);
+    /**
+     * Layout the decorator
+     *
+     * @param context
+     * @param component the associated component
+     * @param width horizontal dimension in pixels
+     * @param height vertical dimension in pixels
+     */
+    void layout(@NonNull RemoteContext context, Component component, 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 7b0e4a2..e25392c 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
@@ -21,6 +21,8 @@
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.OperationInterface;
 import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
 import com.android.internal.widget.remotecompose.core.operations.BitmapData;
 import com.android.internal.widget.remotecompose.core.operations.FloatExpression;
 import com.android.internal.widget.remotecompose.core.operations.MatrixRestore;
@@ -36,6 +38,7 @@
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.PaddingModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ScrollModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ZIndexModifierOperation;
 
@@ -54,6 +57,12 @@
     protected float mPaddingTop = 0f;
     protected float mPaddingBottom = 0f;
 
+    float mScrollX = 0f;
+    float mScrollY = 0f;
+
+    @Nullable protected ScrollDelegate mHorizontalScrollDelegate = null;
+    @Nullable protected ScrollDelegate mVerticalScrollDelegate = null;
+
     @NonNull protected ComponentModifiers mComponentModifiers = new ComponentModifiers();
 
     @NonNull
@@ -111,6 +120,7 @@
     // Should be removed after ImageLayout is in
     private static final boolean USE_IMAGE_TEMP_FIX = true;
 
+    @Override
     public void inflate() {
         ArrayList<TextData> data = new ArrayList<>();
         ArrayList<Operation> supportedOperations = new ArrayList<>();
@@ -144,6 +154,7 @@
                         if (!canvasContent.mList.isEmpty()) {
                             mContent.mList.clear();
                             mChildrenComponents.add(canvasContent);
+                            canvasContent.inflate();
                         }
                     } else {
                         content.getData(data);
@@ -155,6 +166,9 @@
                 if (op instanceof ComponentVisibilityOperation) {
                     ((ComponentVisibilityOperation) op).setParent(this);
                 }
+                if (op instanceof ScrollModifierOperation) {
+                    ((ScrollModifierOperation) op).inflate(this);
+                }
                 mComponentModifiers.add((ModifierOperation) op);
             } else if (op instanceof TextData) {
                 data.add((TextData) op);
@@ -162,6 +176,9 @@
                     || (op instanceof PaintData)
                     || (op instanceof FloatExpression)) {
                 supportedOperations.add(op);
+                if (op instanceof TouchExpression) {
+                    ((TouchExpression) op).setComponent(this);
+                }
             } else {
                 // nothing
             }
@@ -186,8 +203,6 @@
         mPaddingRight = 0f;
         mPaddingBottom = 0f;
 
-        boolean applyHorizontalMargin = true;
-        boolean applyVerticalMargin = true;
         for (OperationInterface op : mComponentModifiers.getList()) {
             if (op instanceof PaddingModifierOperation) {
                 // We are accumulating padding modifiers to compute the margin
@@ -209,6 +224,14 @@
                 mZIndexModifier = (ZIndexModifierOperation) op;
             } else if (op instanceof GraphicsLayerModifierOperation) {
                 mGraphicsLayerModifier = (GraphicsLayerModifierOperation) op;
+            } else if (op instanceof ScrollDelegate) {
+                ScrollDelegate scrollDelegate = (ScrollDelegate) op;
+                if (scrollDelegate.handlesHorizontalScroll()) {
+                    mHorizontalScrollDelegate = scrollDelegate;
+                }
+                if (scrollDelegate.handlesVerticalScroll()) {
+                    mVerticalScrollDelegate = scrollDelegate;
+                }
             }
         }
         if (mWidthModifier == null) {
@@ -217,8 +240,8 @@
         if (mHeightModifier == null) {
             mHeightModifier = new HeightModifierOperation(DimensionModifierOperation.Type.WRAP);
         }
-        setWidth(computeModifierDefinedWidth());
-        setHeight(computeModifierDefinedHeight());
+        setWidth(computeModifierDefinedWidth(null));
+        setHeight(computeModifierDefinedHeight(null));
     }
 
     @NonNull
@@ -228,13 +251,36 @@
     }
 
     @Override
+    public void getLocationInWindow(@NonNull float[] value) {
+        value[0] += mX + mPaddingLeft;
+        value[1] += mY + mPaddingTop;
+        if (mParent != null) {
+            mParent.getLocationInWindow(value);
+        }
+    }
+
+    @Override
     public float getScrollX() {
-        return mComponentModifiers.getScrollX();
+        if (mHorizontalScrollDelegate != null) {
+            return mHorizontalScrollDelegate.getScrollX(mScrollX);
+        }
+        return mScrollX;
+    }
+
+    public void setScrollX(float value) {
+        mScrollX = value;
     }
 
     @Override
     public float getScrollY() {
-        return mComponentModifiers.getScrollY();
+        if (mVerticalScrollDelegate != null) {
+            return mVerticalScrollDelegate.getScrollY(mScrollY);
+        }
+        return mScrollY;
+    }
+
+    public void setScrollY(float value) {
+        mScrollY = value;
     }
 
     @Override
@@ -279,10 +325,18 @@
             ArrayList<Component> sorted = new ArrayList<Component>(mChildrenComponents);
             sorted.sort((a, b) -> (int) (a.getZIndex() - b.getZIndex()));
             for (Component child : sorted) {
+                if (child.isDirty() && child instanceof VariableSupport) {
+                    child.updateVariables(context.getContext());
+                    child.markNotDirty();
+                }
                 child.paint(context);
             }
         } else {
             for (Component child : mChildrenComponents) {
+                if (child.isDirty() && child instanceof VariableSupport) {
+                    child.updateVariables(context.getContext());
+                    child.markNotDirty();
+                }
                 child.paint(context);
             }
         }
@@ -295,11 +349,15 @@
     }
 
     /** Traverse the modifiers to compute indicated dimension */
-    public float computeModifierDefinedWidth() {
+    public float computeModifierDefinedWidth(@Nullable RemoteContext context) {
         float s = 0f;
         float e = 0f;
         float w = 0f;
         for (OperationInterface c : mComponentModifiers.getList()) {
+            if (context != null && c.isDirty() && c instanceof VariableSupport) {
+                ((VariableSupport) c).updateVariables(context);
+                c.markNotDirty();
+            }
             if (c instanceof WidthModifierOperation) {
                 WidthModifierOperation o = (WidthModifierOperation) c;
                 if (o.getType() == DimensionModifierOperation.Type.EXACT
@@ -339,11 +397,15 @@
     }
 
     /** Traverse the modifiers to compute indicated dimension */
-    public float computeModifierDefinedHeight() {
+    public float computeModifierDefinedHeight(@Nullable RemoteContext context) {
         float t = 0f;
         float b = 0f;
         float h = 0f;
         for (OperationInterface c : mComponentModifiers.getList()) {
+            if (context != null && c.isDirty() && c instanceof VariableSupport) {
+                ((VariableSupport) c).updateVariables(context);
+                c.markNotDirty();
+            }
             if (c instanceof HeightModifierOperation) {
                 HeightModifierOperation o = (HeightModifierOperation) c;
                 if (o.getType() == DimensionModifierOperation.Type.EXACT
@@ -383,6 +445,11 @@
     }
 
     @NonNull
+    public ComponentModifiers getComponentModifiers() {
+        return mComponentModifiers;
+    }
+
+    @NonNull
     public ArrayList<Component> getChildrenComponents() {
         return mChildrenComponents;
     }
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 20e4688..9bfbe6a 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
@@ -41,6 +41,11 @@
         super(parent, componentId, animationId, x, y, width, height);
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "LayoutContent";
@@ -77,6 +82,11 @@
         operations.add(new LayoutComponentContent(componentId, 0, 0, 0, 0, null, -1));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .field(INT, "COMPONENT_ID", "unique id for this component")
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 df960e4..505656e 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
@@ -32,8 +32,8 @@
         implements ModifierOperation, DecoratorComponent {
 
     String mOperationName;
-    float mWidth = 0;
-    float mHeight = 0;
+    protected float mWidth = 0;
+    protected float mHeight = 0;
 
     private final float[] mLocationInWindow = new float[2];
 
@@ -71,7 +71,7 @@
     public void paint(PaintContext context) {}
 
     @Override
-    public void layout(RemoteContext context, float width, float height) {
+    public void layout(RemoteContext context, Component component, float width, float height) {
         mWidth = width;
         mHeight = height;
     }
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 d88f711..3d389e5 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
@@ -49,6 +49,11 @@
         return (indent != null ? indent : "") + toString();
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "LoopEnd";
@@ -77,6 +82,11 @@
         operations.add(new LoopEnd());
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Operations", id(), name()).description("End tag for loops");
     }
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 83a2f0e..1b85681 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
@@ -117,7 +117,7 @@
             for (float i = mFromOut; i < mUntilOut; i += mStepOut) {
                 context.getContext().loadFloat(mIndexVariableId, i);
                 for (Operation op : mList) {
-                    if (op instanceof VariableSupport) {
+                    if (op instanceof VariableSupport && op.isDirty()) {
                         ((VariableSupport) op).updateVariables(context.getContext());
                     }
                     op.apply(context.getContext());
@@ -126,6 +126,11 @@
         }
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "Loop";
@@ -154,6 +159,11 @@
         operations.add(new LoopOperation(indexId, from, step, until));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Operations", OP_CODE, name())
                 .description("Loop. This operation execute" + " a list of action in a loop")
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 99b7e68..12a673d 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
@@ -49,6 +49,11 @@
         return (indent != null ? indent : "") + toString();
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "ListEnd";
@@ -77,6 +82,11 @@
         operations.add(new OperationsListEnd());
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .description("End tag for list of operations.");
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 fd16287..11c0f3f 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
@@ -192,6 +192,11 @@
         }
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "RootLayout";
@@ -222,6 +227,11 @@
         operations.add(new RootLayoutComponent(componentId, 0, 0, 0, 0, null, -1));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .field(INT, "COMPONENT_ID", "unique id for this component")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ScrollDelegate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ScrollDelegate.java
new file mode 100644
index 0000000..7ef9766
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ScrollDelegate.java
@@ -0,0 +1,58 @@
+/*
+ * 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;
+
+/**
+ * Represent scroll delegates components.
+ *
+ * <p>Components have scroll X & Y properties. We can inject a scroll delegate as a modifier (e.g. a
+ * scrollView, a marquee...) to control the value of those properties.
+ */
+public interface ScrollDelegate {
+
+    /**
+     * Returns the horizontal scroll value
+     *
+     * @param currentValue the current value
+     * @return the value set by the delegate
+     */
+    float getScrollX(float currentValue);
+
+    /**
+     * Returns the vertical scroll value
+     *
+     * @param currentValue the current value
+     * @return the value set by the delegate
+     */
+    float getScrollY(float currentValue);
+
+    /**
+     * Returns true if the delegate can handle horizontal scroll
+     *
+     * @return true if the delegate handles horizontal scrolling
+     */
+    boolean handlesHorizontalScroll();
+
+    /**
+     * Returns true if the delegate can handle vertical scroll
+     *
+     * @return true if the delegate handles vertical scrolling
+     */
+    boolean handlesVerticalScroll();
+
+    /** Reset the delegate (e.g. the content of the component has changed) */
+    void reset();
+}
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 3185bb5..4977a15 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
@@ -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.Operations;
@@ -60,7 +62,13 @@
 
     @Override
     public void onTouchUp(
-            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+            RemoteContext context,
+            CoreDocument document,
+            Component component,
+            float x,
+            float y,
+            float dx,
+            float dy) {
         // nothing
     }
 
@@ -76,6 +84,12 @@
         // nothing
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
+    @NonNull
     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 d98911f..8c51f2e 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
@@ -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.Operations;
@@ -62,7 +64,13 @@
 
     @Override
     public void onTouchUp(
-            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+            RemoteContext context,
+            CoreDocument document,
+            Component component,
+            float x,
+            float y,
+            float dx,
+            float dy) {
         // nothing
     }
 
@@ -78,6 +86,12 @@
         // nothing
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
+    @NonNull
     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 ac9dd90..607060e 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
@@ -41,9 +41,17 @@
      * @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
+     * @param dx
+     * @param dy
      */
     void onTouchUp(
-            RemoteContext context, CoreDocument document, Component component, float x, float y);
+            RemoteContext context,
+            CoreDocument document,
+            Component component,
+            float x,
+            float y,
+            float dx,
+            float dy);
 
     /**
      * callback for a touch move event
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 f6cb375..a12c356 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
@@ -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.Operations;
@@ -60,7 +62,13 @@
 
     @Override
     public void onTouchUp(
-            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+            RemoteContext context,
+            CoreDocument document,
+            Component component,
+            float x,
+            float y,
+            float dx,
+            float dy) {
         applyActions(context, document, component, x, y, true);
     }
 
@@ -76,6 +84,12 @@
         // nothing
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
+    @NonNull
     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 b343099..2af3c73 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
@@ -120,7 +120,7 @@
                 h -= pop.getTop() + pop.getBottom();
             }
             if (op instanceof DecoratorComponent) {
-                ((DecoratorComponent) op).layout(context.getContext(), w, h);
+                ((DecoratorComponent) op).layout(context.getContext(), mComponent, w, h);
             }
         }
 
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 b230b09..6dff4a8 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
@@ -137,6 +137,11 @@
         return (indent != null ? indent : "") + toString();
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "AnimationSpec";
@@ -224,6 +229,11 @@
         operations.add(op);
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .description("define the animation")
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 01cd7cc..8076cb1 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
@@ -119,8 +119,9 @@
             size.setHeight(Math.max(size.getHeight(), m.getH()));
         }
         // add padding
-        size.setWidth(Math.max(size.getWidth(), computeModifierDefinedWidth()));
-        size.setHeight(Math.max(size.getHeight(), computeModifierDefinedHeight()));
+        size.setWidth(Math.max(size.getWidth(), computeModifierDefinedWidth(context.getContext())));
+        size.setHeight(
+                Math.max(size.getHeight(), computeModifierDefinedHeight(context.getContext())));
     }
 
     @Override
@@ -172,6 +173,11 @@
         }
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "BoxLayout";
@@ -219,6 +225,11 @@
                         verticalPositioning));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .description(
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 665db26..0091a47 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
@@ -72,6 +72,11 @@
         return "CANVAS";
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "CanvasLayout";
@@ -104,6 +109,11 @@
         operations.add(new CanvasLayout(null, componentId, animationId));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .description("Canvas implementation. Encapsulate draw operations.\n\n")
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 5b9ee0f..249e84a 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
@@ -24,6 +24,7 @@
 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.layout.Component;
@@ -169,11 +170,11 @@
     }
 
     @Override
-    public float intrinsicHeight() {
-        float height = computeModifierDefinedHeight();
+    public float intrinsicHeight(@NonNull RemoteContext context) {
+        float height = computeModifierDefinedHeight(context);
         float componentHeights = 0f;
         for (Component c : mChildrenComponents) {
-            componentHeights += c.intrinsicHeight();
+            componentHeights += c.intrinsicHeight(context);
         }
         return Math.max(height, componentHeights);
     }
@@ -341,6 +342,11 @@
         DebugLog.e();
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "ColumnLayout";
@@ -392,6 +398,11 @@
                         spacedBy));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .description(
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 6a15b7f..a5edaa8 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
@@ -61,19 +61,19 @@
     }
 
     @Override
-    public float intrinsicHeight() {
-        float height = computeModifierDefinedHeight();
+    public float intrinsicHeight(@Nullable RemoteContext context) {
+        float height = computeModifierDefinedHeight(context);
         for (Component c : mChildrenComponents) {
-            height = Math.max(c.intrinsicHeight(), height);
+            height = Math.max(c.intrinsicHeight(context), height);
         }
         return height;
     }
 
     @Override
-    public float intrinsicWidth() {
-        float width = computeModifierDefinedWidth();
+    public float intrinsicWidth(@Nullable RemoteContext context) {
+        float width = computeModifierDefinedWidth(context);
         for (Component c : mChildrenComponents) {
-            width = Math.max(c.intrinsicWidth(), width);
+            width = Math.max(c.intrinsicWidth(context), width);
         }
         return width;
     }
@@ -132,16 +132,17 @@
             @NonNull MeasurePass measure) {
         boolean hasWrap = true;
 
-        float measuredWidth = Math.min(maxWidth, computeModifierDefinedWidth());
-        float measuredHeight = Math.min(maxHeight, computeModifierDefinedHeight());
+        float measuredWidth = Math.min(maxWidth, computeModifierDefinedWidth(context.getContext()));
+        float measuredHeight =
+                Math.min(maxHeight, computeModifierDefinedHeight(context.getContext()));
         float insetMaxWidth = maxWidth - mPaddingLeft - mPaddingRight;
         float insetMaxHeight = maxHeight - mPaddingTop - mPaddingBottom;
 
         if (mWidthModifier.isIntrinsicMin()) {
-            maxWidth = intrinsicWidth();
+            maxWidth = intrinsicWidth(context.getContext());
         }
         if (mHeightModifier.isIntrinsicMin()) {
-            maxHeight = intrinsicHeight();
+            maxHeight = intrinsicHeight(context.getContext());
         }
 
         boolean hasHorizontalWrap = mWidthModifier.isWrap();
@@ -180,7 +181,8 @@
         if (isInHorizontalFill()) {
             measuredWidth = maxWidth;
         } else if (mWidthModifier.hasWeight()) {
-            measuredWidth = Math.max(measuredWidth, computeModifierDefinedWidth());
+            measuredWidth =
+                    Math.max(measuredWidth, computeModifierDefinedWidth(context.getContext()));
         } else {
             measuredWidth = Math.max(measuredWidth, minWidth);
             measuredWidth = Math.min(measuredWidth, maxWidth);
@@ -188,7 +190,8 @@
         if (isInVerticalFill()) { // todo: potential npe -- bbade@
             measuredHeight = maxHeight;
         } else if (mHeightModifier.hasWeight()) {
-            measuredHeight = Math.max(measuredHeight, computeModifierDefinedHeight());
+            measuredHeight =
+                    Math.max(measuredHeight, computeModifierDefinedHeight(context.getContext()));
         } else {
             measuredHeight = Math.max(measuredHeight, minHeight);
             measuredHeight = Math.min(measuredHeight, maxHeight);
@@ -224,7 +227,9 @@
                 computeSize(context, 0f, measuredWidth, 0, h, measure);
                 mComponentModifiers.setVerticalScrollDimension(measuredHeight, h);
             } else {
-                computeSize(context, 0f, measuredWidth, 0f, measuredHeight, measure);
+                float maxChildWidth = measuredWidth - mPaddingLeft - mPaddingRight;
+                float maxChildHeight = measuredHeight - mPaddingTop - mPaddingBottom;
+                computeSize(context, 0f, maxChildWidth, 0f, maxChildHeight, measure);
             }
         }
 
@@ -258,7 +263,7 @@
         super.layout(context, measure);
         ComponentMeasure self = measure.get(this);
 
-        mComponentModifiers.layout(context, self.getW(), self.getH());
+        mComponentModifiers.layout(context, this, self.getW(), self.getH());
         for (Component c : mChildrenComponents) {
             c.layout(context, measure);
         }
@@ -275,7 +280,7 @@
         super.layout(context, measure);
         ComponentMeasure self = measure.get(this);
 
-        mComponentModifiers.layout(context, self.getW(), self.getH());
+        mComponentModifiers.layout(context, this, self.getW(), self.getH());
         this.mNeedsMeasure = false;
     }
 }
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 0ec820b..37b9a68 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
@@ -24,6 +24,7 @@
 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.layout.Component;
@@ -167,11 +168,11 @@
     }
 
     @Override
-    public float intrinsicWidth() {
-        float width = computeModifierDefinedWidth();
+    public float intrinsicWidth(@Nullable RemoteContext context) {
+        float width = computeModifierDefinedWidth(context);
         float componentWidths = 0f;
         for (Component c : mChildrenComponents) {
-            componentWidths += c.intrinsicWidth();
+            componentWidths += c.intrinsicWidth(context);
         }
         return Math.max(width, componentWidths);
     }
@@ -344,6 +345,11 @@
         DebugLog.e();
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "RowLayout";
@@ -395,6 +401,11 @@
                         spacedBy));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .description(
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 8e7f538..910205e 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
@@ -34,11 +34,13 @@
 import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size;
 import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
 import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent;
 
 import java.util.List;
 
 /** Text component, referencing a text id */
-public class TextLayout extends LayoutManager implements ComponentStartOperation, VariableSupport {
+public class TextLayout extends LayoutManager
+        implements ComponentStartOperation, VariableSupport, AccessibleComponent {
 
     private static final boolean DEBUG = false;
     private int mTextId = -1;
@@ -57,6 +59,12 @@
 
     @Nullable private String mCachedString = "";
 
+    @Nullable
+    @Override
+    public Integer getTextId() {
+        return mTextId;
+    }
+
     @Override
     public void registerListening(@NonNull RemoteContext context) {
         if (mTextId != -1) {
@@ -92,6 +100,13 @@
         }
         mTextW = -1;
         mTextH = -1;
+
+        if (mHorizontalScrollDelegate != null) {
+            mHorizontalScrollDelegate.reset();
+        }
+        if (mVerticalScrollDelegate != null) {
+            mVerticalScrollDelegate.reset();
+        }
         invalidateMeasure();
     }
 
@@ -175,6 +190,11 @@
         int length = mCachedString.length();
         if (mTextW > mWidth) {
             context.save();
+            context.clipRect(
+                    mPaddingLeft,
+                    mPaddingTop,
+                    mWidth - mPaddingLeft - mPaddingRight,
+                    mHeight - mPaddingTop - mPaddingBottom);
             context.translate(getScrollX(), getScrollY());
             context.drawTextRun(mTextId, 0, length, 0, 0, mTextX, mTextY, false);
             context.restore();
@@ -285,15 +305,20 @@
     }
 
     @Override
-    public float intrinsicHeight() {
+    public float intrinsicHeight(@Nullable RemoteContext context) {
         return mTextH;
     }
 
     @Override
-    public float intrinsicWidth() {
+    public float intrinsicWidth(@Nullable RemoteContext context) {
         return mTextW;
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "TextLayout";
@@ -361,6 +386,11 @@
                         textAlign));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .description("Text layout implementation.\n\n")
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 5df16c5..b4240d0 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
@@ -25,6 +25,7 @@
 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.Component;
 import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
 import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
 
@@ -98,7 +99,8 @@
     }
 
     @Override
-    public void layout(@NonNull RemoteContext context, float width, float height) {
+    public void layout(
+            @NonNull RemoteContext context, Component component, float width, float height) {
         this.mWidth = width;
         this.mHeight = height;
     }
@@ -109,6 +111,11 @@
         return "BackgroundModifierOperation(" + mWidth + " x " + mHeight + ")";
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
@@ -182,6 +189,11 @@
         context.restorePaint();
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Modifier Operations", OP_CODE, CLASS_NAME)
                 .description("define the Background Modifier")
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 bfadd2f..df30d9f 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
@@ -25,6 +25,7 @@
 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.Component;
 import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
 import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
 
@@ -124,7 +125,8 @@
     }
 
     @Override
-    public void layout(@NonNull RemoteContext context, float width, float height) {
+    public void layout(
+            @NonNull RemoteContext context, Component component, float width, float height) {
         this.mWidth = width;
         this.mHeight = height;
     }
@@ -155,6 +157,11 @@
                 + ")";
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
@@ -240,6 +247,11 @@
         context.restorePaint();
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Modifier Operations", OP_CODE, CLASS_NAME)
                 .description("define the Border Modifier")
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 d0af872..b27fb920 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
@@ -23,6 +23,7 @@
 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.Component;
 import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
 
 import java.util.List;
@@ -40,7 +41,8 @@
     }
 
     @Override
-    public void layout(@NonNull RemoteContext context, float width, float height) {
+    public void layout(
+            @NonNull RemoteContext context, Component component, float width, float height) {
         this.mWidth = width;
         this.mHeight = height;
     }
@@ -55,6 +57,11 @@
         apply(buffer);
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
@@ -83,6 +90,11 @@
         operations.add(new ClipRectModifierOperation());
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description("Draw the specified round-rect");
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 d11f26f..d2ba13f 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
@@ -21,6 +21,7 @@
 import com.android.internal.widget.remotecompose.core.PaintContext;
 import com.android.internal.widget.remotecompose.core.PaintOperation;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
 import com.android.internal.widget.remotecompose.core.WireBuffer;
 import com.android.internal.widget.remotecompose.core.operations.MatrixRestore;
 import com.android.internal.widget.remotecompose.core.operations.MatrixSave;
@@ -86,6 +87,10 @@
         float tx = 0f;
         float ty = 0f;
         for (ModifierOperation op : mList) {
+            if (op.isDirty() && op instanceof VariableSupport) {
+                ((VariableSupport) op).updateVariables(context.getContext());
+                op.markNotDirty();
+            }
             if (op instanceof PaddingModifierOperation) {
                 PaddingModifierOperation pop = (PaddingModifierOperation) op;
                 context.translate(pop.getLeft(), pop.getTop());
@@ -109,7 +114,8 @@
     }
 
     @Override
-    public void layout(@NonNull RemoteContext context, float width, float height) {
+    public void layout(
+            @NonNull RemoteContext context, Component component, float width, float height) {
         float w = width;
         float h = height;
         for (ModifierOperation op : mList) {
@@ -119,9 +125,9 @@
                 h -= pop.getTop() + pop.getBottom();
             }
             if (op instanceof ClickModifierOperation) {
-                ((DecoratorComponent) op).layout(context, width, height);
+                ((DecoratorComponent) op).layout(context, component, width, height);
             } else if (op instanceof DecoratorComponent) {
-                ((DecoratorComponent) op).layout(context, w, h);
+                ((DecoratorComponent) op).layout(context, component, w, h);
             }
         }
     }
@@ -156,10 +162,16 @@
 
     @Override
     public void onTouchUp(
-            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+            RemoteContext context,
+            CoreDocument document,
+            Component component,
+            float x,
+            float y,
+            float dx,
+            float dy) {
         for (ModifierOperation op : mList) {
             if (op instanceof TouchHandler) {
-                ((TouchHandler) op).onTouchUp(context, document, component, x, y);
+                ((TouchHandler) op).onTouchUp(context, document, component, x, y, dx, dy);
             }
         }
     }
@@ -208,32 +220,6 @@
         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) {
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 1e6ccfc..c377b75 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
@@ -90,6 +90,11 @@
         operations.add(new ComponentVisibilityOperation(valueId));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", OP_CODE, "ComponentVisibility")
                 .description(
@@ -125,5 +130,6 @@
     }
 
     @Override
-    public void layout(@NonNull RemoteContext context, float width, float height) {}
+    public void layout(
+            @NonNull RemoteContext context, Component component, float width, float height) {}
 }
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 4252309..15c2f46 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
@@ -27,6 +27,7 @@
 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.AnimatableValue;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
 import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
 
 import java.util.List;
@@ -202,6 +203,12 @@
         return "GraphicsLayerModifierOperation(" + mScaleX + ", " + mScaleY + ")";
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -306,5 +313,5 @@
     }
 
     @Override
-    public void layout(RemoteContext context, float width, float height) {}
+    public void layout(RemoteContext context, Component component, float width, float height) {}
 }
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 692b526..ec078a9 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
@@ -32,6 +32,11 @@
     private static final int OP_CODE = Operations.MODIFIER_HEIGHT;
     public static final String CLASS_NAME = "HeightModifierOperation";
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
@@ -94,6 +99,11 @@
         return "HEIGHT";
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Modifier Operations", OP_CODE, CLASS_NAME)
                 .description("define the animation")
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 333e281..2e9d661 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
@@ -99,6 +99,11 @@
         operations.add(new HostActionOperation(actionId));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", OP_CODE, "HostAction")
                 .description("Host action. This operation represents a host action")
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 f9a4270..49ef58e 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
@@ -125,6 +125,11 @@
         operations.add(new HostNamedActionOperation(textId, type, valueId));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", OP_CODE, "HostNamedAction")
                 .description("Host Named action. This operation represents a host action")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/MarqueeModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/MarqueeModifierOperation.java
new file mode 100644
index 0000000..8b6d497
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/MarqueeModifierOperation.java
@@ -0,0 +1,216 @@
+/*
+ * 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.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;
+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.Component;
+import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.ScrollDelegate;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+import java.util.List;
+
+/** Represents a Marquee modifier. */
+public class MarqueeModifierOperation extends DecoratorModifierOperation implements ScrollDelegate {
+    private static final int OP_CODE = Operations.MODIFIER_MARQUEE;
+    public static final String CLASS_NAME = "MarqueeModifierOperation";
+
+    int mIterations;
+    int mAnimationMode;
+    float mRepeatDelayMillis;
+    float mInitialDelayMillis;
+    float mSpacing;
+    float mVelocity;
+
+    private float mComponentWidth;
+    private float mComponentHeight;
+    private float mContentWidth;
+    private float mContentHeight;
+
+    public MarqueeModifierOperation(
+            int iterations,
+            int animationMode,
+            float repeatDelayMillis,
+            float initialDelayMillis,
+            float spacing,
+            float velocity) {
+        this.mIterations = iterations;
+        this.mAnimationMode = animationMode;
+        this.mRepeatDelayMillis = repeatDelayMillis;
+        this.mInitialDelayMillis = initialDelayMillis;
+        this.mSpacing = spacing;
+        this.mVelocity = velocity;
+    }
+
+    public void setContentWidth(float value) {
+        mContentWidth = value;
+    }
+
+    public void setContentHeight(float value) {
+        mContentHeight = value;
+    }
+
+    @Override
+    public float getScrollX(float currentValue) {
+        return mScrollX;
+    }
+
+    @Override
+    public float getScrollY(float currentValue) {
+        return 0;
+    }
+
+    @Override
+    public boolean handlesHorizontalScroll() {
+        return true;
+    }
+
+    @Override
+    public boolean handlesVerticalScroll() {
+        return false;
+    }
+
+    /**
+     * Reset the modifier
+     */
+    public void reset() {
+        mLastTime = 0;
+        mScrollX = 0f;
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        apply(
+                buffer,
+                mIterations,
+                mAnimationMode,
+                mRepeatDelayMillis,
+                mInitialDelayMillis,
+                mSpacing,
+                mVelocity);
+    }
+
+    // @Override
+    public void serializeToString(int indent, StringSerializer serializer) {
+        serializer.append(indent, "MARQUEE = [" + mIterations + "]");
+    }
+
+    @NonNull
+    @Override
+    public String deepToString(@NonNull String indent) {
+        return (indent != null ? indent : "") + toString();
+    }
+
+    private long mLastTime = 0;
+    private long mStartTime = 0;
+
+    private float mScrollX = 0f;
+
+    @Override
+    public void paint(PaintContext context) {
+        long currentTime = System.currentTimeMillis();
+        if (mLastTime == 0) {
+            mLastTime = currentTime;
+            mStartTime = mLastTime + (long) mInitialDelayMillis;
+            context.needsRepaint();
+        }
+        if (mContentWidth > mComponentWidth && currentTime - mStartTime > mInitialDelayMillis) {
+            float density = context.getContext().getDensity(); // in dp
+            float delta = mContentWidth - mComponentWidth;
+            float duration = delta / (density * mVelocity);
+            float elapsed = ((System.currentTimeMillis() - mStartTime) / 1000f);
+            elapsed = (elapsed % duration) / duration;
+            float offset =
+                    (1f + (float) Math.sin(elapsed * 2 * Math.PI - Math.PI / 2f)) / 2f * -delta;
+
+            mScrollX = offset;
+            context.needsRepaint();
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "MarqueeModifierOperation(" + mIterations + ")";
+    }
+
+    public static String name() {
+        return CLASS_NAME;
+    }
+
+    public static int id() {
+        return OP_CODE;
+    }
+
+    public static void apply(
+            WireBuffer buffer,
+            int iterations,
+            int animationMode,
+            float repeatDelayMillis,
+            float initialDelayMillis,
+            float spacing,
+            float velocity) {
+        buffer.start(OP_CODE);
+        buffer.writeInt(iterations);
+        buffer.writeInt(animationMode);
+        buffer.writeFloat(repeatDelayMillis);
+        buffer.writeFloat(initialDelayMillis);
+        buffer.writeFloat(spacing);
+        buffer.writeFloat(velocity);
+    }
+
+    public static void read(WireBuffer buffer, List<Operation> operations) {
+        int iterations = buffer.readInt();
+        int animationMode = buffer.readInt();
+        float repeatDelayMillis = buffer.readFloat();
+        float initialDelayMillis = buffer.readFloat();
+        float spacing = buffer.readFloat();
+        float velocity = buffer.readFloat();
+        operations.add(
+                new MarqueeModifierOperation(
+                        iterations,
+                        animationMode,
+                        repeatDelayMillis,
+                        initialDelayMillis,
+                        spacing,
+                        velocity));
+    }
+
+    public static void documentation(DocumentationBuilder doc) {
+        doc.operation("Modifier Operations", OP_CODE, CLASS_NAME)
+                .description("specify a Marquee Modifier")
+                .field(FLOAT, "value", "");
+    }
+
+    @Override
+    public void layout(RemoteContext context, Component component, float width, float height) {
+        mComponentWidth = width;
+        mComponentHeight = height;
+        if (component instanceof LayoutComponent) {
+            LayoutComponent layoutComponent = (LayoutComponent) component;
+            setContentWidth(layoutComponent.intrinsicWidth(context));
+            setContentHeight(layoutComponent.intrinsicHeight(context));
+        }
+    }
+}
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 69c4e9a..4271947 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
@@ -26,6 +26,7 @@
 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.utilities.StringSerializer;
 
 import java.util.List;
@@ -90,6 +91,12 @@
         return "OffsetModifierOperation(" + mX + ", " + mY + ")";
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -123,5 +130,5 @@
     }
 
     @Override
-    public void layout(RemoteContext context, float width, float height) {}
+    public void layout(RemoteContext context, Component component, float width, float height) {}
 }
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 545df64..bcfbdd6 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
@@ -113,6 +113,11 @@
                 + ")";
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
@@ -150,6 +155,11 @@
         operations.add(new PaddingModifierOperation(left, top, right, bottom));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Modifier Operations", OP_CODE, CLASS_NAME)
                 .description("define the Padding Modifier")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java
new file mode 100644
index 0000000..fe074e4
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RippleModifierOperation.java
@@ -0,0 +1,192 @@
+/*
+ * 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 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.paint.PaintBundle;
+import com.android.internal.widget.remotecompose.core.operations.utilities.ColorUtils;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+import com.android.internal.widget.remotecompose.core.operations.utilities.easing.Easing;
+import com.android.internal.widget.remotecompose.core.operations.utilities.easing.FloatAnimation;
+
+import java.util.List;
+
+/** Represents a ripple effect */
+public class RippleModifierOperation extends DecoratorModifierOperation implements TouchHandler {
+    private static final int OP_CODE = Operations.MODIFIER_RIPPLE;
+
+    long mAnimateRippleStart = 0;
+    float mAnimateRippleX = 0f;
+    float mAnimateRippleY = 0f;
+    int mAnimateRippleDuration = 1000;
+
+    float mWidth = 0;
+    float mHeight = 0;
+
+    @NonNull public float[] locationInWindow = new float[2];
+
+    @NonNull PaintBundle mPaint = new PaintBundle();
+
+    /**
+     * Animate the ripple effect
+     *
+     * @param x
+     * @param y
+     */
+    public void animateRipple(float x, float y) {
+        mAnimateRippleStart = System.currentTimeMillis();
+        mAnimateRippleX = x;
+        mAnimateRippleY = y;
+    }
+
+    @Override
+    public void write(@NonNull WireBuffer buffer) {
+        apply(buffer);
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return "RippleModifier";
+    }
+
+    @Override
+    public void apply(@NonNull RemoteContext context) {
+        RootLayoutComponent root = context.getDocument().getRootLayoutComponent();
+        if (root != null) {
+            root.setHasTouchListeners(true);
+        }
+    }
+
+    @NonNull
+    @Override
+    public String deepToString(@NonNull String indent) {
+        return (indent != null ? indent : "") + toString();
+    }
+
+    @Override
+    public void paint(@NonNull PaintContext context) {
+        if (mAnimateRippleStart == 0) {
+            return;
+        }
+        context.needsRepaint();
+
+        float progress = (System.currentTimeMillis() - mAnimateRippleStart);
+        progress /= (float) mAnimateRippleDuration;
+        if (progress > 1f) {
+            mAnimateRippleStart = 0;
+        }
+        progress = Math.min(1f, progress);
+        context.save();
+        context.savePaint();
+        mPaint.reset();
+
+        FloatAnimation anim1 =
+                new FloatAnimation(Easing.CUBIC_STANDARD, 1f, null, Float.NaN, Float.NaN);
+        anim1.setInitialValue(0f);
+        anim1.setTargetValue(1f);
+        float tween = anim1.get(progress);
+
+        FloatAnimation anim2 =
+                new FloatAnimation(Easing.CUBIC_STANDARD, 0.5f, null, Float.NaN, Float.NaN);
+        anim2.setInitialValue(0f);
+        anim2.setTargetValue(1f);
+        float tweenRadius = anim2.get(progress);
+
+        int startColor = ColorUtils.createColor(250, 250, 250, 180);
+        int endColor = ColorUtils.createColor(200, 200, 200, 0);
+        int paintedColor = Utils.interpolateColor(startColor, endColor, tween);
+
+        float radius = Math.max(mWidth, mHeight) * tweenRadius;
+        mPaint.setColor(paintedColor);
+        context.applyPaint(mPaint);
+        context.clipRect(0f, 0f, mWidth, mHeight);
+        context.drawCircle(mAnimateRippleX, mAnimateRippleY, radius);
+        context.restorePaint();
+        context.restore();
+    }
+
+    @Override
+    public void layout(
+            @NonNull RemoteContext context, Component component, float width, float height) {
+        mWidth = width;
+        mHeight = height;
+    }
+
+    @Override
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
+        serializer.append(indent, "RIPPLE_MODIFIER");
+    }
+
+    @NonNull
+    public static String name() {
+        return "RippleModifier";
+    }
+
+    public static void apply(@NonNull WireBuffer buffer) {
+        buffer.start(OP_CODE);
+    }
+
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
+        operations.add(new RippleModifierOperation());
+    }
+
+    public static void documentation(@NonNull DocumentationBuilder doc) {
+        doc.operation("Layout Operations", OP_CODE, name())
+                .description(
+                        "Ripple modifier. This modifier will do a ripple animation on touch down");
+    }
+
+    @Override
+    public void onTouchDown(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+        locationInWindow[0] = 0f;
+        locationInWindow[1] = 0f;
+        component.getLocationInWindow(locationInWindow);
+        animateRipple(x - locationInWindow[0], y - locationInWindow[1]);
+        context.hapticEffect(3);
+    }
+
+    @Override
+    public void onTouchUp(
+            RemoteContext context,
+            CoreDocument document,
+            Component component,
+            float x,
+            float y,
+            float dx,
+            float dy) {}
+
+    @Override
+    public void onTouchDrag(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {}
+
+    @Override
+    public void onTouchCancel(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {}
+}
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 681501d..4c1f04e 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
@@ -26,6 +26,7 @@
 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.DrawBase4;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
 import com.android.internal.widget.remotecompose.core.operations.layout.DecoratorComponent;
 import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
 
@@ -57,6 +58,11 @@
         return OP_CODE;
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
@@ -67,6 +73,11 @@
         apply(buffer, v1, v2, v3, v4);
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Modifier Operations", id(), "RoundedClipRectModifierOperation")
                 .description("clip with rectangle")
@@ -107,7 +118,8 @@
     }
 
     @Override
-    public void layout(@NonNull RemoteContext context, float width, float height) {
+    public void layout(
+            @NonNull RemoteContext context, Component component, 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
index 0b66320..a5f79ee 100644
--- 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
@@ -24,18 +24,24 @@
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
 import com.android.internal.widget.remotecompose.core.WireBuffer;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.operations.TouchExpression;
 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.DecoratorComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.ListActionsOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.RootLayoutComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.ScrollDelegate;
 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 {
+public class ScrollModifierOperation extends ListActionsOperation
+        implements TouchHandler, DecoratorComponent, ScrollDelegate, VariableSupport {
     private static final int OP_CODE = Operations.MODIFIER_SCROLL;
     public static final String CLASS_NAME = "ScrollModifierOperation";
 
@@ -43,9 +49,6 @@
     private final float mMax;
     private final float mNotchMax;
 
-    float mWidth = 0;
-    float mHeight = 0;
-
     int mDirection;
 
     float mTouchDownX;
@@ -63,13 +66,44 @@
     float mHostDimension;
     float mContentDimension;
 
+    private TouchExpression mTouchExpression;
+
     public ScrollModifierOperation(int direction, float position, float max, float notchMax) {
+        super("SCROLL_MODIFIER");
         this.mDirection = direction;
         this.mPositionExpression = position;
         this.mMax = max;
         this.mNotchMax = notchMax;
     }
 
+    /**
+     * Inflate the operation
+     *
+     * @param component
+     */
+    public void inflate(Component component) {
+        for (Operation op : mList) {
+            if (op instanceof TouchExpression) {
+                mTouchExpression = (TouchExpression) op;
+                mTouchExpression.setComponent(component);
+            }
+        }
+    }
+
+    @Override
+    public void registerListening(@NonNull RemoteContext context) {
+        if (mTouchExpression != null) {
+            mTouchExpression.registerListening(context);
+        }
+    }
+
+    @Override
+    public void updateVariables(@NonNull RemoteContext context) {
+        if (mTouchExpression != null) {
+            mTouchExpression.updateVariables(context);
+        }
+    }
+
     public boolean isVerticalScroll() {
         return mDirection == 0;
     }
@@ -113,6 +147,12 @@
 
     @Override
     public void paint(PaintContext context) {
+        for (Operation op : mList) {
+            op.apply(context.getContext());
+        }
+        if (mTouchExpression == null) {
+            return;
+        }
         float position =
                 context.getContext()
                         .mRemoteComposeState
@@ -130,6 +170,12 @@
         return "ScrollModifierOperation(" + mDirection + ")";
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -167,7 +213,7 @@
     }
 
     @Override
-    public void layout(RemoteContext context, float width, float height) {
+    public void layout(RemoteContext context, Component component, float width, float height) {
         mWidth = width;
         mHeight = height;
         if (mDirection == 0) { // VERTICAL
@@ -186,18 +232,36 @@
         mTouchDownY = y;
         mInitialScrollX = mScrollX;
         mInitialScrollY = mScrollY;
+        if (mTouchExpression != null) {
+            mTouchExpression.updateVariables(context);
+            mTouchExpression.touchDown(context, x + mScrollX, y + mScrollY);
+        }
         document.appliedTouchOperation(component);
     }
 
     @Override
     public void onTouchUp(
-            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+            RemoteContext context,
+            CoreDocument document,
+            Component component,
+            float x,
+            float y,
+            float dx,
+            float dy) {
+        if (mTouchExpression != null) {
+            mTouchExpression.updateVariables(context);
+            mTouchExpression.touchUp(context, x + mScrollX, y + mScrollY, dx, dy);
+        }
         // If not using touch expression, should add velocity decay here
     }
 
     @Override
     public void onTouchDrag(
             RemoteContext context, CoreDocument document, Component component, float x, float y) {
+        if (mTouchExpression != null) {
+            mTouchExpression.updateVariables(context);
+            mTouchExpression.touchDrag(context, x + mScrollX, y + mScrollY);
+        }
         float dx = x - mTouchDownX;
         float dy = y - mTouchDownY;
 
@@ -229,4 +293,35 @@
     public float getContentDimension() {
         return mContentDimension;
     }
+
+    @Override
+    public float getScrollX(float currentValue) {
+        if (mDirection == 1) {
+            return mScrollX;
+        }
+        return 0f;
+    }
+
+    @Override
+    public float getScrollY(float currentValue) {
+        if (mDirection == 0) {
+            return mScrollY;
+        }
+        return 0f;
+    }
+
+    @Override
+    public boolean handlesHorizontalScroll() {
+        return mDirection == 1;
+    }
+
+    @Override
+    public boolean handlesVerticalScroll() {
+        return mDirection == 0;
+    }
+
+    @Override
+    public void reset() {
+        // nothing here for now
+    }
 }
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 b96d3cc..b6977a0 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
@@ -73,7 +73,6 @@
     @Override
     public void runAction(
             RemoteContext context, CoreDocument document, Component component, float x, float y) {
-        System.out.println("OVERRIDE " + mTargetValueId + " TO " + mValue);
         context.overrideFloat(mTargetValueId, mValue);
     }
 
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
index d81b7ff..766271a 100644
--- 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
@@ -101,6 +101,11 @@
         operations.add(new ValueFloatExpressionChangeActionOperation(valueId, value));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", OP_CODE, "ValueIntegerExpressionChangeActionOperation")
                 .description(
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 fb13b42..60166a7 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
@@ -99,6 +99,11 @@
         operations.add(new ValueIntegerChangeActionOperation(valueId, value));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", OP_CODE, "ValueIntegerChangeActionOperation")
                 .description(
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 0fe88ad..5025080 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
@@ -101,6 +101,11 @@
         operations.add(new ValueIntegerExpressionChangeActionOperation(valueId, value));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", OP_CODE, "ValueIntegerExpressionChangeActionOperation")
                 .description(
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 a8d3b87..8093bb3 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
@@ -103,6 +103,11 @@
         operations.add(new ValueStringChangeActionOperation(valueId, value));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", OP_CODE, "ValueStringChangeActionOperation")
                 .description(
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 f6d743f..0530598 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
@@ -32,6 +32,11 @@
     private static final int OP_CODE = Operations.MODIFIER_WIDTH;
     public static final String CLASS_NAME = "WidthModifierOperation";
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return CLASS_NAME;
@@ -94,6 +99,11 @@
         return "WIDTH";
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Modifier Operations", OP_CODE, CLASS_NAME)
                 .description("define the animation")
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 96ed2cd..35de33a 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
@@ -26,6 +26,7 @@
 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.utilities.StringSerializer;
 
 import java.util.List;
@@ -45,7 +46,7 @@
         return mCurrentValue;
     }
 
-    public void setmValue(float value) {
+    public void setValue(float value) {
         this.mValue = value;
     }
 
@@ -79,6 +80,12 @@
         return "ZIndexModifierOperation(" + mValue + ")";
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -109,5 +116,5 @@
     }
 
     @Override
-    public void layout(RemoteContext context, float width, float height) {}
+    public void layout(RemoteContext context, Component component, float width, float height) {}
 }
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 a568747..7e46701 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
@@ -20,58 +20,147 @@
 
 import com.android.internal.widget.remotecompose.core.operations.utilities.easing.MonotonicSpline;
 
+import java.util.Random;
+
 /** high performance floating point expression evaluator used in animation */
 public class AnimatedFloatExpression {
     @NonNull static IntMap<String> sNames = new IntMap<>();
+
+    /** The START POINT in the float NaN space for operators */
     public static final int OFFSET = 0x310_000;
+
+    /** ADD operator */
     public static final float ADD = asNan(OFFSET + 1);
+
+    /** SUB operator */
     public static final float SUB = asNan(OFFSET + 2);
+
+    /** MUL operator */
     public static final float MUL = asNan(OFFSET + 3);
+
+    /** DIV operator */
     public static final float DIV = asNan(OFFSET + 4);
+
+    /** MOD operator */
     public static final float MOD = asNan(OFFSET + 5);
+
+    /** MIN operator */
     public static final float MIN = asNan(OFFSET + 6);
+
+    /** MAX operator */
     public static final float MAX = asNan(OFFSET + 7);
+
+    /** POW operator */
     public static final float POW = asNan(OFFSET + 8);
+
+    /** SQRT operator */
     public static final float SQRT = asNan(OFFSET + 9);
+
+    /** ABS operator */
     public static final float ABS = asNan(OFFSET + 10);
+
+    /** SIGN operator */
     public static final float SIGN = asNan(OFFSET + 11);
+
+    /** COPY_SIGN operator */
     public static final float COPY_SIGN = asNan(OFFSET + 12);
+
+    /** EXP operator */
     public static final float EXP = asNan(OFFSET + 13);
+
+    /** FLOOR operator */
     public static final float FLOOR = asNan(OFFSET + 14);
+
+    /** LOG operator */
     public static final float LOG = asNan(OFFSET + 15);
+
+    /** LN operator */
     public static final float LN = asNan(OFFSET + 16);
+
+    /** ROUND operator */
     public static final float ROUND = asNan(OFFSET + 17);
+
+    /** SIN operator */
     public static final float SIN = asNan(OFFSET + 18);
+
+    /** COS operator */
     public static final float COS = asNan(OFFSET + 19);
+
+    /** TAN operator */
     public static final float TAN = asNan(OFFSET + 20);
+
+    /** ASIN operator */
     public static final float ASIN = asNan(OFFSET + 21);
+
+    /** ACOS operator */
     public static final float ACOS = asNan(OFFSET + 22);
 
+    /** ATAN operator */
     public static final float ATAN = asNan(OFFSET + 23);
 
+    /** ATAN2 operator */
     public static final float ATAN2 = asNan(OFFSET + 24);
+
+    /** MAD operator */
     public static final float MAD = asNan(OFFSET + 25);
+
+    /** IFELSE operator */
     public static final float IFELSE = asNan(OFFSET + 26);
 
+    /** CLAMP operator */
     public static final float CLAMP = asNan(OFFSET + 27);
+
+    /** CBRT operator */
     public static final float CBRT = asNan(OFFSET + 28);
+
+    /** DEG operator */
     public static final float DEG = asNan(OFFSET + 29);
+
+    /** RAD operator */
     public static final float RAD = asNan(OFFSET + 30);
+
+    /** CEIL operator */
     public static final float CEIL = asNan(OFFSET + 31);
 
     // Array ops
+    /** A DEREF operator */
     public static final float A_DEREF = asNan(OFFSET + 32);
+
+    /** Array MAX operator */
     public static final float A_MAX = asNan(OFFSET + 33);
+
+    /** Array MIN operator */
     public static final float A_MIN = asNan(OFFSET + 34);
+
+    /** A_SUM operator */
     public static final float A_SUM = asNan(OFFSET + 35);
+
+    /** A_AVG operator */
     public static final float A_AVG = asNan(OFFSET + 36);
+
+    /** A_LEN operator */
     public static final float A_LEN = asNan(OFFSET + 37);
+
+    /** A_SPLINE operator */
     public static final float A_SPLINE = asNan(OFFSET + 38);
 
-    public static final int LAST_OP = OFFSET + 38;
+    /** RAND Random number 0..1 */
+    public static final float RAND = asNan(OFFSET + 39);
 
-    public static final float VAR1 = asNan(OFFSET + 39);
-    public static final float VAR2 = asNan(OFFSET + 40);
+    /** RAND_SEED operator */
+    public static final float RAND_SEED = asNan(OFFSET + 40);
+
+    /** LAST valid operator */
+    public static final int LAST_OP = OFFSET + 40;
+
+    /** VAR1 operator */
+    public static final float VAR1 = asNan(OFFSET + 41);
+
+    /** VAR2 operator */
+    public static final float VAR2 = asNan(OFFSET + 42);
+
+    /** VAR2 operator */
+    public static final float VAR3 = asNan(OFFSET + 43);
 
     // TODO CLAMP, CBRT, DEG, RAD, EXPM1, CEIL, FLOOR
     //    private static final float FP_PI = (float) Math.PI;
@@ -83,6 +172,7 @@
     @NonNull float[] mVar = new float[0];
     @Nullable CollectionsAccess mCollectionsAccess;
     IntMap<MonotonicSpline> mSplineMap = new IntMap<>();
+    private Random mRandom;
 
     private float getSplineValue(int arrayId, float pos) {
         MonotonicSpline fit = mSplineMap.get(arrayId);
@@ -151,10 +241,11 @@
     /**
      * Evaluate a float expression
      *
-     * @param ca
-     * @param exp
-     * @param var
-     * @return
+     * @param ca Access to float array collections
+     * @param exp the expressions
+     * @param len the length of the expression array
+     * @param var variables if the expression contains VAR tags
+     * @return the value the expression evaluated to
      */
     public float eval(
             @NonNull CollectionsAccess ca, @NonNull float[] exp, int len, @NonNull float... var) {
@@ -183,9 +274,10 @@
     /**
      * Evaluate a float expression
      *
-     * @param ca
-     * @param exp
-     * @return
+     * @param ca The access to float arrays
+     * @param exp the expression
+     * @param len the length of the expression sections
+     * @return the value the expression evaluated to
      */
     public float eval(@NonNull CollectionsAccess ca, @NonNull float[] exp, int len) {
         System.arraycopy(exp, 0, mLocalStack, 0, len);
@@ -304,6 +396,8 @@
         sNames.put(k++, "A_AVG");
         sNames.put(k++, "A_LEN");
         sNames.put(k++, "A_SPLINE");
+        sNames.put(k++, "RAND");
+        sNames.put(k++, "RAND_SEED");
 
         sNames.put(k++, "a[0]");
         sNames.put(k++, "a[1]");
@@ -518,9 +612,12 @@
     private static final int OP_A_AVG = OFFSET + 36;
     private static final int OP_A_LEN = OFFSET + 37;
     private static final int OP_A_SPLINE = OFFSET + 38;
-    private static final int OP_FIRST_VAR = OFFSET + 39;
-    private static final int OP_SECOND_VAR = OFFSET + 40;
-    private static final int OP_THIRD_VAR = OFFSET + 41;
+    private static final int OP_RAND = OFFSET + 39;
+    private static final int OP_RAND_SEED = OFFSET + 40;
+
+    private static final int OP_FIRST_VAR = OFFSET + 41;
+    private static final int OP_SECOND_VAR = OFFSET + 42;
+    private static final int OP_THIRD_VAR = OFFSET + 43;
 
     int opEval(int sp, int id) {
         float[] array;
@@ -708,6 +805,26 @@
                 mStack[sp - 1] = getSplineValue(id, mStack[sp]);
                 return sp - 1;
 
+            case OP_RAND:
+                if (mRandom == null) {
+                    mRandom = new Random();
+                }
+                mStack[sp + 1] = mRandom.nextFloat();
+                return sp + 1;
+
+            case OP_RAND_SEED:
+                float seed = mStack[sp];
+                if (seed == 0) {
+                    mRandom = new Random();
+                } else {
+                    if (mRandom == null) {
+                        mRandom = new Random(Float.floatToRawIntBits(seed));
+                    } else {
+                        mRandom.setSeed(Float.floatToRawIntBits(seed));
+                    }
+                }
+                return sp - 1;
+
             case OP_FIRST_VAR:
                 mStack[sp] = mVar[0];
                 return sp;
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 182d36a..69de535 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
@@ -23,17 +23,45 @@
  * FloatArrayAccess, ListAccess, MapAccess
  */
 public interface ArrayAccess {
+    /**
+     * Get a value as a float for an index
+     *
+     * @param index position in the collection
+     * @return
+     */
     float getFloatValue(int index);
 
+    /**
+     * If the objects have id's return the id
+     *
+     * @param index index of the object
+     * @return id or -1 if no id is available
+     */
     default int getId(int index) {
         return 0;
     }
 
+    /**
+     * Get the backing array of float if available for float arrays
+     *
+     * @return
+     */
     @Nullable
     float[] getFloats();
 
+    /**
+     * Get the length of the collection
+     *
+     * @return length of the collection
+     */
     int getLength();
 
+    /**
+     * Get the value as an integer if available
+     *
+     * @param index the position in the collection
+     * @return it value as and integer
+     */
     default int getIntValue(int index) {
         return (int) getFloatValue(index);
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibilityModifier.java b/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibilityModifier.java
new file mode 100644
index 0000000..cd8b7b8
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibilityModifier.java
@@ -0,0 +1,23 @@
+/*
+ * 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.semantics;
+
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ModifierOperation;
+
+/** A Modifier that provides semantic info. */
+public interface AccessibilityModifier extends ModifierOperation, AccessibleComponent {
+    int getOpCode();
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibilitySemantics.java b/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibilitySemantics.java
new file mode 100644
index 0000000..291ad47
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibilitySemantics.java
@@ -0,0 +1,34 @@
+/*
+ * 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.semantics;
+
+/** Marker interface for a Component or Modifier that is relevant for Semantics. */
+public interface AccessibilitySemantics {
+
+    /**
+     * Determines if this element is interesting for semantic analysis.
+     *
+     * <p>This method is used to filter elements during semantic analysis. By default, all elements
+     * are considered interesting. Subclasses can override this method to exclude specific elements
+     * from semantic analysis.
+     *
+     * @return {@code true} if this element is interesting for semantic analysis, {@code false}
+     *     otherwise.
+     */
+    default boolean isInterestingForSemantics() {
+        return true;
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibleComponent.java b/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibleComponent.java
new file mode 100644
index 0000000..e07fc4d
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibleComponent.java
@@ -0,0 +1,73 @@
+/*
+ * 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.semantics;
+
+import android.annotation.Nullable;
+
+public interface AccessibleComponent extends AccessibilitySemantics {
+    default @Nullable Integer getContentDescriptionId() {
+        return null;
+    }
+
+    default @Nullable Integer getTextId() {
+        return null;
+    }
+
+    default @Nullable Role getRole() {
+        return null;
+    }
+
+    default boolean isClickable() {
+        return false;
+    }
+
+    default CoreSemantics.Mode getMode() {
+        return CoreSemantics.Mode.SET;
+    }
+
+    // Our master list
+    // https://developer.android.com/reference/kotlin/androidx/compose/ui/semantics/Role
+    enum Role {
+        BUTTON("Button"),
+        CHECKBOX("Checkbox"),
+        SWITCH("Switch"),
+        RADIO_BUTTON("RadioButton"),
+        TAB("Tab"),
+        IMAGE("Image"),
+        DROPDOWN_LIST("DropdownList"),
+        PICKER("Picker"),
+        CAROUSEL("Carousel"),
+        UNKNOWN(null);
+
+        @Nullable private final String mDescription;
+
+        Role(@Nullable String description) {
+            this.mDescription = description;
+        }
+
+        @Nullable
+        public String getDescription() {
+            return mDescription;
+        }
+
+        public static Role fromInt(int i) {
+            if (i < UNKNOWN.ordinal()) {
+                return Role.values()[i];
+            }
+            return Role.UNKNOWN;
+        }
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/semantics/CoreSemantics.java b/core/java/com/android/internal/widget/remotecompose/core/semantics/CoreSemantics.java
new file mode 100644
index 0000000..b8166e6
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/semantics/CoreSemantics.java
@@ -0,0 +1,148 @@
+/*
+ * 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.semantics;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+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.operations.utilities.StringSerializer;
+
+import java.util.List;
+
+/** Implementation of the most common semantics used in typical Android apps. */
+public class CoreSemantics extends Operation implements AccessibilityModifier {
+    public int mContentDescriptionId = 0;
+    public @Nullable Role mRole = null;
+    public int mTextId = 0;
+    public int mStateDescriptionId = 0;
+    public boolean mEnabled = true;
+    public Mode mMode = Mode.SET;
+    public boolean mClickable = false;
+
+    @Override
+    public int getOpCode() {
+        return Operations.ACCESSIBILITY_SEMANTICS;
+    }
+
+    @Nullable
+    @Override
+    public Role getRole() {
+        return mRole;
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        buffer.writeInt(mContentDescriptionId);
+        buffer.writeByte((mRole != null) ? mRole.ordinal() : -1);
+        buffer.writeInt(mTextId);
+        buffer.writeInt(mStateDescriptionId);
+        buffer.writeByte(mMode.ordinal());
+        buffer.writeBoolean(mEnabled);
+        buffer.writeBoolean(mClickable);
+    }
+
+    private void read(WireBuffer buffer) {
+        mContentDescriptionId = buffer.readInt();
+        mRole = Role.fromInt(buffer.readByte());
+        mTextId = buffer.readInt();
+        mStateDescriptionId = buffer.readInt();
+        mMode = Mode.values()[buffer.readByte()];
+        mEnabled = buffer.readBoolean();
+        mClickable = buffer.readBoolean();
+    }
+
+    @Override
+    public void apply(RemoteContext context) {
+        // Handled via touch helper
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("SEMANTICS");
+        if (mRole != null) {
+            builder.append(" ");
+            builder.append(mRole);
+        }
+        if (mContentDescriptionId > 0) {
+            builder.append(" contentDescription=");
+            builder.append(mContentDescriptionId);
+        }
+        if (mTextId > 0) {
+            builder.append(" text=");
+            builder.append(mTextId);
+        }
+        if (mStateDescriptionId > 0) {
+            builder.append(" stateDescription=");
+            builder.append(mStateDescriptionId);
+        }
+        if (!mEnabled) {
+            builder.append(" disabled");
+        }
+        if (mClickable) {
+            builder.append(" clickable");
+        }
+        return builder.toString();
+    }
+
+    @Nullable
+    @Override
+    public String deepToString(String indent) {
+        return indent + this;
+    }
+
+    @NonNull
+    public String serializedName() {
+        return "SEMANTICS";
+    }
+
+    @Override
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
+        serializer.append(indent, serializedName() + " = " + this);
+    }
+
+    public static void read(WireBuffer buffer, List<Operation> operations) {
+        CoreSemantics semantics = new CoreSemantics();
+
+        semantics.read(buffer);
+
+        operations.add(semantics);
+    }
+
+    @Override
+    public Integer getContentDescriptionId() {
+        return mContentDescriptionId != 0 ? mContentDescriptionId : null;
+    }
+
+    public @Nullable Integer getStateDescriptionId() {
+        return mStateDescriptionId != 0 ? mStateDescriptionId : null;
+    }
+
+    public @Nullable Integer getTextId() {
+        return mTextId != 0 ? mTextId : null;
+    }
+
+    public enum Mode {
+        SET,
+        CLEAR_AND_SET,
+        MERGE
+    }
+}
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 975213f..2c874b1 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
@@ -68,6 +68,11 @@
         return "BooleanConstant[" + mId + "] = " + mValue + "";
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "OrigamiBoolean";
@@ -108,6 +113,11 @@
         operations.add(new BooleanConstant(id, value));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, "BooleanConstant")
                 .description("A boolean and its associated id")
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 210a15a..5462d3e 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
@@ -60,6 +60,11 @@
         return "IntegerConstant[" + mId + "] = " + mValue + "";
     }
 
+    /**
+     * The name of the class
+     *
+     * @return the name
+     */
     @NonNull
     public static String name() {
         return "IntegerConstant";
@@ -100,6 +105,11 @@
         operations.add(new IntegerConstant(id, value));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", id(), "IntegerConstant")
                 .description("A integer and its associated id")
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 9875c93..1a3cdb1 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
@@ -96,6 +96,11 @@
         operations.add(new LongConstant(id, value));
     }
 
+    /**
+     * Populate the documentation with a description of this operation
+     *
+     * @param doc to append the description to.
+     */
     public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, "LongConstant")
                 .description("A boolean and its associated id")
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 19b4b36..19453a0 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
@@ -85,6 +85,7 @@
             }
         } else {
             mInner.setDocument(null);
+            this.setAccessibilityDelegate(null);
         }
         mapColors();
         setupSensors();
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 bc7d5e1..daa44c8 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
@@ -250,7 +250,7 @@
     @Override
     public void getTextBounds(int textId, int start, int end, int flags, @NonNull float[] bounds) {
         String str = getText(textId);
-        if (end == -1) {
+        if (end == -1 || end > str.length()) {
             end = str.length();
         }
 
@@ -757,11 +757,17 @@
 
     private Path getPath(int id, float start, float end) {
         AndroidRemoteContext androidContext = (AndroidRemoteContext) mContext;
+        Path p = (Path) androidContext.mRemoteComposeState.getPath(id);
+        if (p != null) {
+            return p;
+        }
         Path path = new Path();
         float[] pathData = androidContext.mRemoteComposeState.getPathData(id);
         if (pathData != null) {
             FloatsToPath.genPath(path, pathData, start, end);
+            androidContext.mRemoteComposeState.putPath(id, path);
         }
+
         return path;
     }
 
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 ecfd13a..8da5b9d 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
@@ -241,9 +241,9 @@
             case MotionEvent.ACTION_DOWN:
                 mActionDownPoint.x = (int) event.getX();
                 mActionDownPoint.y = (int) event.getY();
-                mInActionDown = true;
                 CoreDocument doc = mDocument.getDocument();
                 if (doc.hasTouchListener()) {
+                    mInActionDown = true;
                     if (mVelocityTracker == null) {
                         mVelocityTracker = VelocityTracker.obtain();
                     } else {
@@ -251,8 +251,10 @@
                     }
                     mVelocityTracker.addMovement(event);
                     doc.touchDown(mARContext, event.getX(), event.getY());
+                    invalidate();
+                    return true;
                 }
-                return true;
+                return false;
 
             case MotionEvent.ACTION_CANCEL:
                 mInActionDown = false;
@@ -262,8 +264,10 @@
                     float dx = mVelocityTracker.getXVelocity(pointerId);
                     float dy = mVelocityTracker.getYVelocity(pointerId);
                     doc.touchCancel(mARContext, event.getX(), event.getY(), dx, dy);
+                    invalidate();
+                    return true;
                 }
-                return true;
+
             case MotionEvent.ACTION_UP:
                 mInActionDown = false;
                 performClick();
@@ -273,8 +277,9 @@
                     float dx = mVelocityTracker.getXVelocity(pointerId);
                     float dy = mVelocityTracker.getYVelocity(pointerId);
                     doc.touchUp(mARContext, event.getX(), event.getY(), dx, dy);
+                    invalidate();
+                    return true;
                 }
-                return true;
 
             case MotionEvent.ACTION_MOVE:
                 if (mInActionDown) {
@@ -286,6 +291,7 @@
                             invalidate();
                         }
                     }
+                    return true;
                 }
         }
         return false;