Merge "Add flag for VDM settings" into main
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index a81bcbc..a452226 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -575,14 +575,22 @@
             EVENT_FLAG_DISPLAY_ADDED,
             EVENT_FLAG_DISPLAY_CHANGED,
             EVENT_FLAG_DISPLAY_REMOVED,
-            EVENT_FLAG_DISPLAY_BRIGHTNESS,
-            EVENT_FLAG_HDR_SDR_RATIO_CHANGED,
-            EVENT_FLAG_DISPLAY_CONNECTION_CHANGED,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface EventFlag {}
 
     /**
+     * @hide
+     */
+    @LongDef(flag = true, prefix = {"PRIVATE_EVENT_FLAG_"}, value = {
+            PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS,
+            PRIVATE_EVENT_FLAG_HDR_SDR_RATIO_CHANGED,
+            PRIVATE_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PrivateEventFlag {}
+
+    /**
      * Event type for when a new display is added.
      *
      * @see #registerDisplayListener(DisplayListener, Handler, long)
@@ -618,7 +626,7 @@
      *
      * @hide
      */
-    public static final long EVENT_FLAG_DISPLAY_BRIGHTNESS = 1L << 3;
+    public static final long PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS = 1L << 0;
 
     /**
      * Event flag to register for a display's hdr/sdr ratio changes. This notification is sent
@@ -631,14 +639,16 @@
      *
      * @hide
      */
-    public static final long EVENT_FLAG_HDR_SDR_RATIO_CHANGED = 1L << 4;
+    public static final long PRIVATE_EVENT_FLAG_HDR_SDR_RATIO_CHANGED = 1L << 1;
 
     /**
      * Event flag to register for a display's connection changed.
      *
+     * @see #registerDisplayListener(DisplayListener, Handler, long)
      * @hide
      */
-    public static final long EVENT_FLAG_DISPLAY_CONNECTION_CHANGED = 1L << 5;
+    public static final long PRIVATE_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED = 1L << 2;
+
 
     /** @hide */
     public DisplayManager(Context context) {
@@ -774,20 +784,49 @@
      * @param listener The listener to register.
      * @param handler The handler on which the listener should be invoked, or null
      * if the listener should be invoked on the calling thread's looper.
-     * @param eventFlagsMask A bitmask of the event types for which this listener is subscribed.
+     * @param eventFlags A bitmask of the event types for which this listener is subscribed.
      *
      * @see #EVENT_FLAG_DISPLAY_ADDED
      * @see #EVENT_FLAG_DISPLAY_CHANGED
      * @see #EVENT_FLAG_DISPLAY_REMOVED
-     * @see #EVENT_FLAG_DISPLAY_BRIGHTNESS
      * @see #registerDisplayListener(DisplayListener, Handler)
      * @see #unregisterDisplayListener
      *
      * @hide
      */
     public void registerDisplayListener(@NonNull DisplayListener listener,
-            @Nullable Handler handler, @EventFlag long eventFlagsMask) {
-        mGlobal.registerDisplayListener(listener, handler, eventFlagsMask,
+            @Nullable Handler handler, @EventFlag long eventFlags) {
+        mGlobal.registerDisplayListener(listener, handler,
+                mGlobal.mapFlagsToInternalEventFlag(eventFlags, 0),
+                ActivityThread.currentPackageName());
+    }
+
+    /**
+     * Registers a display listener to receive notifications about given display event types.
+     *
+     * @param listener The listener to register.
+     * @param handler The handler on which the listener should be invoked, or null
+     * if the listener should be invoked on the calling thread's looper.
+     * @param eventFlags A bitmask of the event types for which this listener is subscribed.
+     * @param privateEventFlags A bitmask of the private event types for which this listener
+     *                          is subscribed.
+     *
+     * @see #EVENT_FLAG_DISPLAY_ADDED
+     * @see #EVENT_FLAG_DISPLAY_CHANGED
+     * @see #EVENT_FLAG_DISPLAY_REMOVED
+     * @see #PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS
+     * @see #PRIVATE_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED
+     * @see #PRIVATE_EVENT_FLAG_HDR_SDR_RATIO_CHANGED
+     * @see #registerDisplayListener(DisplayListener, Handler)
+     * @see #unregisterDisplayListener
+     *
+     * @hide
+     */
+    public void registerDisplayListener(@NonNull DisplayListener listener,
+            @Nullable Handler handler, @EventFlag long eventFlags,
+            @PrivateEventFlag long privateEventFlags) {
+        mGlobal.registerDisplayListener(listener, handler,
+                mGlobal.mapFlagsToInternalEventFlag(eventFlags, privateEventFlags),
                 ActivityThread.currentPackageName());
     }
 
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 56307ae..644850a 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -24,6 +24,7 @@
 import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.IntDef;
+import android.annotation.LongDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -118,6 +119,24 @@
     public static final int EVENT_DISPLAY_CONNECTED = 6;
     public static final int EVENT_DISPLAY_DISCONNECTED = 7;
 
+    @LongDef(prefix = {"INTERNAL_EVENT_DISPLAY"}, flag = true, value = {
+            INTERNAL_EVENT_FLAG_DISPLAY_ADDED,
+            INTERNAL_EVENT_FLAG_DISPLAY_CHANGED,
+            INTERNAL_EVENT_FLAG_DISPLAY_REMOVED,
+            INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED,
+            INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED,
+            INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface InternalEventFlag {}
+
+    public static final long INTERNAL_EVENT_FLAG_DISPLAY_ADDED = 1L << 0;
+    public static final long INTERNAL_EVENT_FLAG_DISPLAY_CHANGED = 1L << 1;
+    public static final long INTERNAL_EVENT_FLAG_DISPLAY_REMOVED = 1L << 2;
+    public static final long INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED = 1L << 3;
+    public static final long INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED = 1L << 4;
+    public static final long INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED = 1L << 5;
+
     @UnsupportedAppUsage
     private static DisplayManagerGlobal sInstance;
 
@@ -130,7 +149,7 @@
     private final IDisplayManager mDm;
 
     private DisplayManagerCallback mCallback;
-    private @EventFlag long mRegisteredEventFlagsMask = 0;
+    private @InternalEventFlag long mRegisteredInternalEventFlag = 0;
     private final CopyOnWriteArrayList<DisplayListenerDelegate> mDisplayListeners =
             new CopyOnWriteArrayList<>();
 
@@ -346,11 +365,11 @@
      * @param packageName of the calling package.
      */
     public void registerDisplayListener(@NonNull DisplayListener listener,
-            @Nullable Handler handler, @EventFlag long eventFlagsMask,
+            @Nullable Handler handler, @InternalEventFlag long internalEventFlagsMask,
             String packageName) {
         Looper looper = getLooperForHandler(handler);
         Handler springBoard = new Handler(looper);
-        registerDisplayListener(listener, new HandlerExecutor(springBoard), eventFlagsMask,
+        registerDisplayListener(listener, new HandlerExecutor(springBoard), internalEventFlagsMask,
                 packageName);
     }
 
@@ -359,32 +378,34 @@
      *
      * @param listener The listener that will be called when display changes occur.
      * @param executor Executor for the thread that will be receiving the callbacks. Cannot be null.
-     * @param eventFlagsMask Flag of events to be listened to.
+     * @param internalEventFlagsMask Mask of events to be listened to.
      * @param packageName of the calling package.
      */
     public void registerDisplayListener(@NonNull DisplayListener listener,
-            @NonNull Executor executor, @EventFlag long eventFlagsMask, String packageName) {
+            @NonNull Executor executor, @InternalEventFlag long internalEventFlagsMask,
+            String packageName) {
         if (listener == null) {
             throw new IllegalArgumentException("listener must not be null");
         }
 
-        if (eventFlagsMask == 0) {
+        if (internalEventFlagsMask == 0) {
             throw new IllegalArgumentException("The set of events to listen to must not be empty.");
         }
 
         if (extraLogging()) {
             Slog.i(TAG, "Registering Display Listener: "
-                    + Long.toBinaryString(eventFlagsMask) + ", packageName: " + packageName);
+                    + Long.toBinaryString(internalEventFlagsMask)
+                    + ", packageName: " + packageName);
         }
 
         synchronized (mLock) {
             int index = findDisplayListenerLocked(listener);
             if (index < 0) {
                 mDisplayListeners.add(new DisplayListenerDelegate(listener, executor,
-                        eventFlagsMask, packageName));
+                        internalEventFlagsMask, packageName));
                 registerCallbackIfNeededLocked();
             } else {
-                mDisplayListeners.get(index).setEventFlagsMask(eventFlagsMask);
+                mDisplayListeners.get(index).setEventsMask(internalEventFlagsMask);
             }
             updateCallbackIfNeededLocked();
             maybeLogAllDisplayListeners();
@@ -456,17 +477,17 @@
         return -1;
     }
 
-    @EventFlag
-    private int calculateEventFlagsMaskLocked() {
-        int mask = 0;
+    @InternalEventFlag
+    private long calculateEventsMaskLocked() {
+        long mask = 0;
         final int numListeners = mDisplayListeners.size();
         for (int i = 0; i < numListeners; i++) {
-            mask |= mDisplayListeners.get(i).mEventFlagsMask;
+            mask |= mDisplayListeners.get(i).mInternalEventFlagsMask;
         }
         if (mDispatchNativeCallbacks) {
-            mask |= DisplayManager.EVENT_FLAG_DISPLAY_ADDED
-                    | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-                    | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
+            mask |= INTERNAL_EVENT_FLAG_DISPLAY_ADDED
+                    | INTERNAL_EVENT_FLAG_DISPLAY_CHANGED
+                    | INTERNAL_EVENT_FLAG_DISPLAY_REMOVED;
         }
         return mask;
     }
@@ -479,14 +500,14 @@
     }
 
     private void updateCallbackIfNeededLocked() {
-        int mask = calculateEventFlagsMaskLocked();
+        long mask = calculateEventsMaskLocked();
         if (DEBUG) {
-            Log.d(TAG, "Flag for listener: " + mask);
+            Log.d(TAG, "Mask for listener: " + mask);
         }
-        if (mask != mRegisteredEventFlagsMask) {
+        if (mask != mRegisteredInternalEventFlag) {
             try {
                 mDm.registerCallbackWithEventMask(mCallback, mask);
-                mRegisteredEventFlagsMask = mask;
+                mRegisteredInternalEventFlag = mask;
             } catch (RemoteException ex) {
                 throw ex.rethrowFromSystemServer();
             }
@@ -1268,8 +1289,8 @@
         @Override
         public void onDisplayEvent(int displayId, @DisplayEvent int event) {
             if (DEBUG) {
-                Log.d(TAG, "onDisplayEvent: displayId=" + displayId + ", event=" + eventToString(
-                        event));
+                Log.d(TAG, "onDisplayEvent: displayId=" + displayId + ", event="
+                        + eventToString(event));
             }
             handleDisplayEvent(displayId, event, false /* forceUpdate */);
         }
@@ -1277,7 +1298,7 @@
 
     private static final class DisplayListenerDelegate {
         public final DisplayListener mListener;
-        public volatile long mEventFlagsMask;
+        public volatile long mInternalEventFlagsMask;
 
         private final DisplayInfo mDisplayInfo = new DisplayInfo();
         private final Executor mExecutor;
@@ -1285,10 +1306,10 @@
         private final String mPackageName;
 
         DisplayListenerDelegate(DisplayListener listener, @NonNull Executor executor,
-                @EventFlag long eventFlag, String packageName) {
+                @InternalEventFlag long internalEventFlag, String packageName) {
             mExecutor = executor;
             mListener = listener;
-            mEventFlagsMask = eventFlag;
+            mInternalEventFlagsMask = internalEventFlag;
             mPackageName = packageName;
         }
 
@@ -1310,16 +1331,16 @@
             mGenerationId.incrementAndGet();
         }
 
-        void setEventFlagsMask(@EventFlag long newEventsFlag) {
-            mEventFlagsMask = newEventsFlag;
+        void setEventsMask(@InternalEventFlag long newInternalEventFlagsMask) {
+            mInternalEventFlagsMask = newInternalEventFlagsMask;
         }
 
-        private void handleDisplayEventInner(int displayId, @DisplayEvent int eventFlagsMask,
+        private void handleDisplayEventInner(int displayId, @DisplayEvent int event,
                 @Nullable DisplayInfo info, boolean forceUpdate) {
             if (extraLogging()) {
-                Slog.i(TAG, "DLD(" + eventToString(eventFlagsMask)
+                Slog.i(TAG, "DLD(" + eventToString(event)
                         + ", display=" + displayId
-                        + ", mEventsFlagMask=" + Long.toBinaryString(mEventFlagsMask)
+                        + ", mEventsMask=" + Long.toBinaryString(mInternalEventFlagsMask)
                         + ", mPackageName=" + mPackageName
                         + ", displayInfo=" + info
                         + ", listener=" + mListener.getClass() + ")");
@@ -1327,18 +1348,19 @@
             if (DEBUG) {
                 Trace.beginSection(
                         TextUtils.trimToSize(
-                                "DLD(" + eventToString(eventFlagsMask)
+                                "DLD(" + eventToString(event)
                                 + ", display=" + displayId
                                 + ", listener=" + mListener.getClass() + ")", 127));
             }
-            switch (eventFlagsMask) {
+            switch (event) {
                 case EVENT_DISPLAY_ADDED:
-                    if ((mEventFlagsMask & DisplayManager.EVENT_FLAG_DISPLAY_ADDED) != 0) {
+                    if ((mInternalEventFlagsMask & INTERNAL_EVENT_FLAG_DISPLAY_ADDED) != 0) {
                         mListener.onDisplayAdded(displayId);
                     }
                     break;
                 case EVENT_DISPLAY_CHANGED:
-                    if ((mEventFlagsMask & DisplayManager.EVENT_FLAG_DISPLAY_CHANGED) != 0) {
+                    if ((mInternalEventFlagsMask & INTERNAL_EVENT_FLAG_DISPLAY_CHANGED)
+                            != 0) {
                         if (info != null && (forceUpdate || !info.equals(mDisplayInfo))) {
                             if (extraLogging()) {
                                 Slog.i(TAG, "Sending onDisplayChanged: Display Changed. Info: "
@@ -1350,29 +1372,32 @@
                     }
                     break;
                 case EVENT_DISPLAY_BRIGHTNESS_CHANGED:
-                    if ((mEventFlagsMask & DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS) != 0) {
+                    if ((mInternalEventFlagsMask
+                            & INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED) != 0) {
                         mListener.onDisplayChanged(displayId);
                     }
                     break;
                 case EVENT_DISPLAY_REMOVED:
-                    if ((mEventFlagsMask & DisplayManager.EVENT_FLAG_DISPLAY_REMOVED) != 0) {
+                    if ((mInternalEventFlagsMask & INTERNAL_EVENT_FLAG_DISPLAY_REMOVED)
+                            != 0) {
                         mListener.onDisplayRemoved(displayId);
                     }
                     break;
                 case EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED:
-                    if ((mEventFlagsMask & DisplayManager.EVENT_FLAG_HDR_SDR_RATIO_CHANGED) != 0) {
+                    if ((mInternalEventFlagsMask
+                            & INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED) != 0) {
                         mListener.onDisplayChanged(displayId);
                     }
                     break;
                 case EVENT_DISPLAY_CONNECTED:
-                    if ((mEventFlagsMask & DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED)
-                            != 0) {
+                    if ((mInternalEventFlagsMask
+                            & INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED) != 0) {
                         mListener.onDisplayConnected(displayId);
                     }
                     break;
                 case EVENT_DISPLAY_DISCONNECTED:
-                    if ((mEventFlagsMask & DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED)
-                            != 0) {
+                    if ((mInternalEventFlagsMask
+                            & INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED) != 0) {
                         mListener.onDisplayDisconnected(displayId);
                     }
                     break;
@@ -1384,7 +1409,7 @@
 
         @Override
         public String toString() {
-            return "mEventFlagsMask: {" + mEventFlagsMask + "}, for " + mListener.getClass();
+            return "flag: {" + mInternalEventFlagsMask + "}, for " + mListener.getClass();
         }
     }
 
@@ -1532,4 +1557,53 @@
     private static boolean extraLogging() {
         return sExtraDisplayListenerLogging;
     }
+
+
+    /**
+     * Maps the supplied public and private event flags to a unified InternalEventFlag
+     * @param eventFlags A bitmask of the event types for which this listener is subscribed.
+     * @param privateEventFlags A bitmask of the private event types for which this listener
+     *                          is subscribed.
+     * @return returns the bitmask of both public and private event flags unified to
+     * InternalEventFlag
+     */
+    public @InternalEventFlag long mapFlagsToInternalEventFlag(@EventFlag long eventFlags,
+            @DisplayManager.PrivateEventFlag long privateEventFlags) {
+        return mapPrivateEventFlags(privateEventFlags) | mapPublicEventFlags(eventFlags);
+    }
+
+    private long mapPrivateEventFlags(@DisplayManager.PrivateEventFlag long privateEventFlags) {
+        long baseEventMask = 0;
+        if ((privateEventFlags & DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS) != 0) {
+            baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED;
+        }
+
+        if ((privateEventFlags & DisplayManager.PRIVATE_EVENT_FLAG_HDR_SDR_RATIO_CHANGED) != 0) {
+            baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED;
+        }
+
+        if ((privateEventFlags
+                & DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED) != 0) {
+            baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED;
+        }
+        return baseEventMask;
+    }
+
+    private long mapPublicEventFlags(@EventFlag long eventFlags) {
+        long baseEventMask = 0;
+        if ((eventFlags & DisplayManager.EVENT_FLAG_DISPLAY_ADDED) != 0) {
+            baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_ADDED;
+        }
+
+        if ((eventFlags & DisplayManager.EVENT_FLAG_DISPLAY_CHANGED) != 0) {
+            baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_CHANGED;
+        }
+
+        if ((eventFlags
+                & DisplayManager.EVENT_FLAG_DISPLAY_REMOVED) != 0) {
+            baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_REMOVED;
+        }
+
+        return baseEventMask;
+    }
 }
diff --git a/core/java/android/net/vcn/flags.aconfig b/core/java/android/net/vcn/flags.aconfig
index 1adefe5..1b2c575 100644
--- a/core/java/android/net/vcn/flags.aconfig
+++ b/core/java/android/net/vcn/flags.aconfig
@@ -18,13 +18,6 @@
 }
 
 flag {
-    name: "safe_mode_timeout_config"
-    namespace: "vcn"
-    description: "Feature flag for adjustable safe mode timeout"
-    bug: "317406085"
-}
-
-flag {
     name: "fix_config_garbage_collection"
     namespace: "vcn"
     description: "Handle race condition in subscription change"
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 910e644..0241e94 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -1549,8 +1549,9 @@
             // Although we only care about the HDR/SDR ratio changing, that can also come in the
             // form of the larger DISPLAY_CHANGED event
             mGlobal.registerDisplayListener(toRegister, executor,
-                    DisplayManager.EVENT_FLAG_HDR_SDR_RATIO_CHANGED
-                            | DisplayManagerGlobal.EVENT_DISPLAY_CHANGED,
+                    DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED
+                            | DisplayManagerGlobal
+                                    .INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED,
                     ActivityThread.currentPackageName());
         }
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 3ce6870..9a2aa0b 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -185,7 +185,6 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
 import android.hardware.SyncFence;
-import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManager.DisplayListener;
 import android.hardware.display.DisplayManagerGlobal;
 import android.hardware.input.InputManagerGlobal;
@@ -1816,9 +1815,9 @@
                 .registerDisplayListener(
                         mDisplayListener,
                         mHandler,
-                        DisplayManager.EVENT_FLAG_DISPLAY_ADDED
-                        | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-                        | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED,
+                        DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
+                        | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED
+                        | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED,
                         mBasePackageName);
 
         if (forceInvertColor()) {
diff --git a/core/java/android/window/BackProgressAnimator.java b/core/java/android/window/BackProgressAnimator.java
index a5be58b..16eb437 100644
--- a/core/java/android/window/BackProgressAnimator.java
+++ b/core/java/android/window/BackProgressAnimator.java
@@ -16,8 +16,11 @@
 
 package android.window;
 
+import static android.window.BackEvent.EDGE_NONE;
+
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 import static com.android.window.flags.Flags.predictiveBackTimestampApi;
+import static com.android.window.flags.Flags.predictiveBackSwipeEdgeNoneApi;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -60,6 +63,12 @@
     @Nullable
     private Runnable mBackInvokedFinishRunnable;
     private FlingAnimation mBackInvokedFlingAnim;
+    private final SpringForce mGestureSpringForce = new SpringForce()
+            .setStiffness(SpringForce.STIFFNESS_MEDIUM)
+            .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY);
+    private final SpringForce mButtonSpringForce = new SpringForce()
+            .setStiffness(500)
+            .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY);
     private final DynamicAnimation.OnAnimationEndListener mOnAnimationEndListener =
             (animation, canceled, value, velocity) -> {
                 if (mBackCancelledFinishRunnable != null) invokeBackCancelledRunnable();
@@ -109,9 +118,7 @@
     public BackProgressAnimator() {
         mSpring = new SpringAnimation(this, PROGRESS_PROP);
         mSpring.addUpdateListener(this);
-        mSpring.setSpring(new SpringForce()
-                .setStiffness(SpringForce.STIFFNESS_MEDIUM)
-                .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY));
+        mSpring.setSpring(mGestureSpringForce);
     }
 
     /**
@@ -123,6 +130,11 @@
         if (!mBackAnimationInProgress) {
             return;
         }
+        if (predictiveBackSwipeEdgeNoneApi()) {
+            if (event.getSwipeEdge() == EDGE_NONE) {
+                return;
+            }
+        }
         mLastBackEvent = event;
         if (mSpring == null) {
             return;
@@ -143,7 +155,17 @@
         mBackAnimationInProgress = true;
         updateProgressValue(/* progress */ 0, /* velocity */ 0,
                 /* frameTime */ System.nanoTime() / TimeUtils.NANOS_PER_MS);
-        onBackProgressed(event);
+        if (predictiveBackSwipeEdgeNoneApi()) {
+            if (event.getSwipeEdge() == EDGE_NONE) {
+                mSpring.setSpring(mButtonSpringForce);
+                mSpring.animateToFinalPosition(SCALE_FACTOR);
+            } else {
+                mSpring.setSpring(mGestureSpringForce);
+                onBackProgressed(event);
+            }
+        } else {
+            onBackProgressed(event);
+        }
     }
 
     /**
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 11f6849..9845bf0 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -388,4 +388,11 @@
     description: "Provide pre-make predictive back API extension"
     is_fixed_read_only: true
     bug: "362938401"
-}
\ No newline at end of file
+}
+
+flag {
+    name: "predictive_back_three_button_nav"
+    namespace: "systemui"
+    description: "Enable Predictive Back Animation for 3-button-nav"
+    bug: "373544911"
+}
diff --git a/core/java/com/android/internal/display/BrightnessSynchronizer.java b/core/java/com/android/internal/display/BrightnessSynchronizer.java
index 21fbf9d..a50dbb0 100644
--- a/core/java/com/android/internal/display/BrightnessSynchronizer.java
+++ b/core/java/com/android/internal/display/BrightnessSynchronizer.java
@@ -600,8 +600,8 @@
             final ContentResolver cr = mContext.getContentResolver();
             cr.registerContentObserver(BRIGHTNESS_URI, false,
                     createBrightnessContentObserver(handler), UserHandle.USER_ALL);
-            mDisplayManager.registerDisplayListener(mListener, handler,
-                    DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS);
+            mDisplayManager.registerDisplayListener(mListener, handler, /* eventFlags */ 0,
+                    DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS);
             mIsObserving = true;
         }
     }
diff --git a/core/java/com/android/internal/jank/DisplayResolutionTracker.java b/core/java/com/android/internal/jank/DisplayResolutionTracker.java
index ca6c54d..0c2fd4b 100644
--- a/core/java/com/android/internal/jank/DisplayResolutionTracker.java
+++ b/core/java/com/android/internal/jank/DisplayResolutionTracker.java
@@ -147,8 +147,9 @@
                 @Override
                 public void registerDisplayListener(DisplayManager.DisplayListener listener) {
                     manager.registerDisplayListener(listener, handler,
-                            DisplayManager.EVENT_FLAG_DISPLAY_ADDED
-                                    | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED,
+                            DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
+                                    | DisplayManagerGlobal
+                                            .INTERNAL_EVENT_FLAG_DISPLAY_CHANGED,
                             ActivityThread.currentPackageName());
                 }
 
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 212df02..0761a24 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
@@ -15,12 +15,14 @@
  */
 package com.android.internal.widget.remotecompose.core;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.operations.ComponentValue;
 import com.android.internal.widget.remotecompose.core.operations.IntegerExpression;
 import com.android.internal.widget.remotecompose.core.operations.NamedVariable;
 import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior;
 import com.android.internal.widget.remotecompose.core.operations.Theme;
-import com.android.internal.widget.remotecompose.core.operations.layout.ClickModifierEnd;
 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.ComponentEnd;
@@ -28,7 +30,11 @@
 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;
 import com.android.internal.widget.remotecompose.core.operations.layout.RootLayoutComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.TouchCancelModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.TouchDownModifierOperation;
+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.utilities.StringSerializer;
@@ -49,12 +55,12 @@
 
     ArrayList<Operation> mOperations;
 
-    RootLayoutComponent mRootLayoutComponent = null;
+    @Nullable RootLayoutComponent mRootLayoutComponent = null;
 
     RemoteComposeState mRemoteComposeState = new RemoteComposeState();
-    TimeVariables mTimeVariables = new TimeVariables();
+    @NonNull TimeVariables mTimeVariables = new TimeVariables();
     // Semantic version of the document
-    Version mVersion = new Version(0, 1, 0);
+    @NonNull Version mVersion = new Version(0, 1, 0);
 
     String mContentDescription; // text description of the document (used for accessibility)
 
@@ -72,6 +78,8 @@
 
     private final HashMap<Long, IntegerExpression> mIntegerExpressions = new HashMap<>();
 
+    private HashSet<Component> mAppliedTouchOperations = new HashSet<>();
+
     private int mLastId = 1; // last component id when inflating the file
 
     public String getContentDescription() {
@@ -272,6 +280,7 @@
      *
      * @return list of click areas in document coordinates
      */
+    @NonNull
     public Set<ClickAreaRepresentation> getClickAreas() {
         return mClickAreas;
     }
@@ -281,6 +290,7 @@
      *
      * @return returns the root component if it exists, null otherwise
      */
+    @Nullable
     public RootLayoutComponent getRootLayoutComponent() {
         return mRootLayoutComponent;
     }
@@ -298,6 +308,7 @@
      * @param id component id
      * @return the component if it exists, null otherwise
      */
+    @Nullable
     public Component getComponent(int id) {
         if (mRootLayoutComponent != null) {
             return mRootLayoutComponent.getComponent(id);
@@ -310,6 +321,7 @@
      *
      * @return a standardized string representation of the component hierarchy
      */
+    @NonNull
     public String displayHierarchy() {
         StringSerializer serializer = new StringSerializer();
         for (Operation op : mOperations) {
@@ -329,7 +341,8 @@
      * @param targetId the id of the value to update with the expression
      * @param context the current context
      */
-    public void evaluateIntExpression(long expressionId, int targetId, RemoteContext context) {
+    public void evaluateIntExpression(
+            long expressionId, int targetId, @NonNull RemoteContext context) {
         IntegerExpression expression = mIntegerExpressions.get(expressionId);
         if (expression != null) {
             int v = expression.evaluate(context);
@@ -337,22 +350,46 @@
         }
     }
 
-    /** Callback interface for host actions */
-    public interface ActionCallback {
-        // TODO: add payload support
-        void onAction(String name);
+    // ============== Haptic support ==================
+    public interface HapticEngine {
+        void haptic(int type);
     }
 
-    HashSet<ActionCallback> mActionListeners = new HashSet<ActionCallback>();
+    HapticEngine mHapticEngine;
+
+    public void setHapticEngine(HapticEngine engine) {
+        mHapticEngine = engine;
+    }
+
+    public void haptic(int type) {
+        if (mHapticEngine != null) {
+            mHapticEngine.haptic(type);
+        }
+    }
+
+    // ============== Haptic support ==================
+
+    public void appliedTouchOperation(Component operation) {
+        mAppliedTouchOperations.add(operation);
+    }
+
+    /** Callback interface for host actions */
+    public interface ActionCallback {
+        void onAction(String name, Object value);
+    }
+
+    @NonNull HashSet<ActionCallback> mActionListeners = new HashSet<ActionCallback>();
 
     /**
      * Warn action listeners for the given named action
      *
      * @param name the action name
+     * @param value a parameter to the action
      */
-    public void runNamedAction(String name) {
+    public void runNamedAction(String name, Object value) {
+        // TODO: we might add an interface to group all valid parameter types
         for (ActionCallback callback : mActionListeners) {
-            callback.onAction(name);
+            callback.onAction(name, value);
         }
     }
 
@@ -374,8 +411,9 @@
         void click(int id, String metadata);
     }
 
-    HashSet<ClickCallbacks> mClickListeners = new HashSet<>();
-    HashSet<ClickAreaRepresentation> mClickAreas = new HashSet<>();
+    @NonNull HashSet<ClickCallbacks> mClickListeners = new HashSet<>();
+    @NonNull HashSet<TouchListener> mTouchListeners = new HashSet<>();
+    @NonNull HashSet<ClickAreaRepresentation> mClickAreas = new HashSet<>();
 
     static class Version {
         public final int major;
@@ -456,7 +494,7 @@
     }
 
     /** Load operations from the given buffer */
-    public void initFromBuffer(RemoteComposeBuffer buffer) {
+    public void initFromBuffer(@NonNull RemoteComposeBuffer buffer) {
         mOperations = new ArrayList<Operation>();
         buffer.inflateFromBuffer(mOperations);
         for (Operation op : mOperations) {
@@ -484,12 +522,16 @@
      * @param operations flat list of operations
      * @return nested list of operations / components
      */
-    private ArrayList<Operation> inflateComponents(ArrayList<Operation> operations) {
+    @NonNull
+    private ArrayList<Operation> inflateComponents(@NonNull ArrayList<Operation> operations) {
         Component currentComponent = null;
         ArrayList<Component> components = new ArrayList<>();
         ArrayList<Operation> finalOperationsList = new ArrayList<>();
         ArrayList<Operation> ops = finalOperationsList;
         ClickModifierOperation currentClickModifier = null;
+        TouchDownModifierOperation currentTouchDownModifier = null;
+        TouchUpModifierOperation currentTouchUpModifier = null;
+        TouchCancelModifierOperation currentTouchCancelModifier = null;
         LoopOperation currentLoop = null;
 
         mLastId = -1;
@@ -519,10 +561,30 @@
                 // TODO: refactor to add container <- component...
                 currentClickModifier = (ClickModifierOperation) o;
                 ops = currentClickModifier.getList();
-            } else if (o instanceof ClickModifierEnd) {
+            } else if (o instanceof TouchDownModifierOperation) {
+                currentTouchDownModifier = (TouchDownModifierOperation) o;
+                ops = currentTouchDownModifier.getList();
+            } else if (o instanceof TouchUpModifierOperation) {
+                currentTouchUpModifier = (TouchUpModifierOperation) o;
+                ops = currentTouchUpModifier.getList();
+            } else if (o instanceof TouchCancelModifierOperation) {
+                currentTouchCancelModifier = (TouchCancelModifierOperation) o;
+                ops = currentTouchCancelModifier.getList();
+            } else if (o instanceof OperationsListEnd) {
                 ops = currentComponent.getList();
-                ops.add(currentClickModifier);
-                currentClickModifier = null;
+                if (currentClickModifier != null) {
+                    ops.add(currentClickModifier);
+                    currentClickModifier = null;
+                } else if (currentTouchDownModifier != null) {
+                    ops.add(currentTouchDownModifier);
+                    currentTouchDownModifier = null;
+                } else if (currentTouchUpModifier != null) {
+                    ops.add(currentTouchUpModifier);
+                    currentTouchUpModifier = null;
+                } else if (currentTouchCancelModifier != null) {
+                    ops.add(currentTouchCancelModifier);
+                    currentTouchCancelModifier = null;
+                }
             } else if (o instanceof LoopOperation) {
                 currentLoop = (LoopOperation) o;
                 ops = currentLoop.getList();
@@ -541,9 +603,9 @@
         return ops;
     }
 
-    private HashMap<Integer, Component> mComponentMap = new HashMap<Integer, Component>();
+    @NonNull private HashMap<Integer, Component> mComponentMap = new HashMap<Integer, Component>();
 
-    private void registerVariables(RemoteContext context, ArrayList<Operation> list) {
+    private void registerVariables(RemoteContext context, @NonNull ArrayList<Operation> list) {
         for (Operation op : list) {
             if (op instanceof VariableSupport) {
                 ((VariableSupport) op).updateVariables(context);
@@ -578,7 +640,7 @@
      * Called when an initialization is needed, allowing the document to eg load resources / cache
      * them.
      */
-    public void initializeContext(RemoteContext context) {
+    public void initializeContext(@NonNull RemoteContext context) {
         mRemoteComposeState.reset();
         mRemoteComposeState.setContext(context);
         mClickAreas.clear();
@@ -651,6 +713,15 @@
     }
 
     /**
+     * Called by commands to listen to touch events
+     *
+     * @param listener
+     */
+    public void addTouchListener(TouchListener listener) {
+        mTouchListeners.add(listener);
+    }
+
+    /**
      * Add a click listener. This will get called when a click is detected on the document
      *
      * @param callback called when a click area has been hit, passing the click are id and metadata.
@@ -664,6 +735,7 @@
      *
      * @return set of click listeners
      */
+    @NonNull
     public HashSet<CoreDocument.ClickCallbacks> getClickListeners() {
         return mClickListeners;
     }
@@ -700,12 +772,98 @@
     }
 
     /** Warn click listeners when a click area is activated */
-    private void warnClickListeners(ClickAreaRepresentation clickArea) {
+    private void warnClickListeners(@NonNull ClickAreaRepresentation clickArea) {
         for (ClickCallbacks listener : mClickListeners) {
             listener.click(clickArea.mId, clickArea.mMetadata);
         }
     }
 
+    /**
+     * Returns true if the document has touch listeners
+     *
+     * @return true if the document needs to react to touch events
+     */
+    public boolean hasTouchListener() {
+        boolean hasComponentsTouchListeners =
+                mRootLayoutComponent != null && mRootLayoutComponent.hasTouchListeners();
+        return hasComponentsTouchListeners || !mTouchListeners.isEmpty();
+    }
+
+    // TODO support velocity estimate support, support regions
+    /**
+     * Support touch drag events on commands supporting touch
+     *
+     * @param x position of touch
+     * @param y position of touch
+     */
+    public boolean touchDrag(RemoteContext context, float x, float y) {
+        context.loadFloat(RemoteContext.ID_TOUCH_POS_X, x);
+        context.loadFloat(RemoteContext.ID_TOUCH_POS_Y, y);
+        for (TouchListener clickArea : mTouchListeners) {
+            clickArea.touchDrag(context, x, y);
+        }
+        if (!mTouchListeners.isEmpty()) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Support touch down events on commands supporting touch
+     *
+     * @param x position of touch
+     * @param y position of touch
+     */
+    public void touchDown(RemoteContext context, float x, float y) {
+        context.loadFloat(RemoteContext.ID_TOUCH_POS_X, x);
+        context.loadFloat(RemoteContext.ID_TOUCH_POS_Y, y);
+        for (TouchListener clickArea : mTouchListeners) {
+            clickArea.touchDown(context, x, y);
+        }
+        if (mRootLayoutComponent != null) {
+            mRootLayoutComponent.onTouchDown(context, this, x, y);
+        }
+        mRepaintNext = 1;
+    }
+
+    /**
+     * Support touch up events on commands supporting touch
+     *
+     * @param x position of touch
+     * @param y position of touch
+     */
+    public void touchUp(RemoteContext context, float x, float y, float dx, float dy) {
+        context.loadFloat(RemoteContext.ID_TOUCH_POS_X, x);
+        context.loadFloat(RemoteContext.ID_TOUCH_POS_Y, y);
+        for (TouchListener clickArea : mTouchListeners) {
+            clickArea.touchUp(context, x, y, dx, dy);
+        }
+        if (mRootLayoutComponent != null) {
+            for (Component component : mAppliedTouchOperations) {
+                component.onTouchUp(context, this, x, y, true);
+            }
+            mAppliedTouchOperations.clear();
+        }
+        mRepaintNext = 1;
+    }
+
+    /**
+     * Support touch cancel events on commands supporting touch
+     *
+     * @param x position of touch
+     * @param y position of touch
+     */
+    public void touchCancel(RemoteContext context, float x, float y, float dx, float dy) {
+        if (mRootLayoutComponent != null) {
+            for (Component component : mAppliedTouchOperations) {
+                component.onTouchCancel(context, this, x, y, true);
+            }
+            mAppliedTouchOperations.clear();
+        }
+        mRepaintNext = 1;
+    }
+
+    @NonNull
     @Override
     public String toString() {
         StringBuilder builder = new StringBuilder();
@@ -721,12 +879,22 @@
      *
      * @return array of named colors or null
      */
+    @Nullable
     public String[] getNamedColors() {
+        return getNamedVariables(NamedVariable.COLOR_TYPE);
+    }
+
+    /**
+     * Gets the names of all named Variables.
+     *
+     * @return array of named variables or null
+     */
+    public String[] getNamedVariables(int type) {
         int count = 0;
         for (Operation op : mOperations) {
             if (op instanceof NamedVariable) {
                 NamedVariable n = (NamedVariable) op;
-                if (n.mVarType == NamedVariable.COLOR_TYPE) {
+                if (n.mVarType == type) {
                     count++;
                 }
             }
@@ -739,7 +907,7 @@
         for (Operation op : mOperations) {
             if (op instanceof NamedVariable) {
                 NamedVariable n = (NamedVariable) op;
-                if (n.mVarType == NamedVariable.COLOR_TYPE) {
+                if (n.mVarType == type) {
                     ret[i++] = n.mVarName;
                 }
             }
@@ -770,10 +938,9 @@
      * @param context the provided PaintContext
      * @param theme the theme we want to use for this document.
      */
-    public void paint(RemoteContext context, int theme) {
+    public void paint(@NonNull RemoteContext context, int theme) {
         context.getPaintContext().clearNeedsRepaint();
         context.mMode = RemoteContext.ContextMode.UNSET;
-
         // current theme starts as UNSPECIFIED, until a Theme setter
         // operation gets executed and modify it.
         context.setTheme(Theme.UNSPECIFIED);
@@ -807,6 +974,7 @@
             }
             // TODO -- this should be specifically about applying animation, not paint
             mRootLayoutComponent.paint(context.getPaintContext());
+            context.mPaintContext.reset();
             // TODO -- should be able to remove this
             mRootLayoutComponent.updateVariables(context);
             if (DEBUG) {
@@ -843,6 +1011,7 @@
         }
     }
 
+    @NonNull
     public String[] getStats() {
         ArrayList<String> ret = new ArrayList<>();
         WireBuffer buffer = new WireBuffer();
@@ -875,7 +1044,7 @@
         return ret.toArray(new String[0]);
     }
 
-    private int sizeOfComponent(Operation com, WireBuffer tmp) {
+    private int sizeOfComponent(@NonNull Operation com, @NonNull WireBuffer tmp) {
         tmp.reset(100);
         com.write(tmp);
         int size = tmp.getSize();
@@ -883,7 +1052,8 @@
         return size;
     }
 
-    private int addChildren(Component base, HashMap<String, int[]> map, WireBuffer tmp) {
+    private int addChildren(
+            @NonNull Component base, @NonNull HashMap<String, int[]> map, @NonNull WireBuffer tmp) {
         int count = base.mList.size();
         for (Operation mOperation : base.mList) {
             Class<? extends Operation> c = mOperation.getClass();
@@ -903,6 +1073,7 @@
         return count;
     }
 
+    @NonNull
     public String toNestedString() {
         StringBuilder ret = new StringBuilder();
         for (Operation mOperation : mOperations) {
@@ -915,7 +1086,8 @@
         return ret.toString();
     }
 
-    private void toNestedString(Component base, StringBuilder ret, String indent) {
+    private void toNestedString(
+            @NonNull Component base, @NonNull StringBuilder ret, String indent) {
         for (Operation mOperation : base.mList) {
             ret.append(mOperation.toString());
             ret.append("\n");
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 9f565a2..f1885f9 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Operation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Operation.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core;
 
+import android.annotation.Nullable;
+
 /** Base interface for RemoteCompose operations */
 public interface Operation {
 
@@ -29,5 +31,6 @@
     void apply(RemoteContext context);
 
     /** Debug utility to display an operation + indentation */
+    @Nullable
     String deepToString(String indent);
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/Operations.java b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
index acebe07..53c45fa 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Operations.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.operations.BitmapData;
 import com.android.internal.widget.remotecompose.core.operations.ClickArea;
 import com.android.internal.widget.remotecompose.core.operations.ClipPath;
@@ -65,15 +67,19 @@
 import com.android.internal.widget.remotecompose.core.operations.TextMeasure;
 import com.android.internal.widget.remotecompose.core.operations.TextMerge;
 import com.android.internal.widget.remotecompose.core.operations.Theme;
+import com.android.internal.widget.remotecompose.core.operations.TouchExpression;
 import com.android.internal.widget.remotecompose.core.operations.layout.CanvasContent;
-import com.android.internal.widget.remotecompose.core.operations.layout.ClickModifierEnd;
 import com.android.internal.widget.remotecompose.core.operations.layout.ClickModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.ComponentEnd;
 import com.android.internal.widget.remotecompose.core.operations.layout.ComponentStart;
 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.TouchCancelModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.TouchDownModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.TouchUpModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.animation.AnimationSpec;
 import com.android.internal.widget.remotecompose.core.operations.layout.managers.BoxLayout;
 import com.android.internal.widget.remotecompose.core.operations.layout.managers.CanvasLayout;
@@ -85,15 +91,19 @@
 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.ComponentVisibilityOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.GraphicsLayerModifierOperation;
 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.OffsetModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.PaddingModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.RoundedClipRectModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ValueFloatChangeActionOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ValueIntegerChangeActionOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ValueIntegerExpressionChangeActionOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ValueStringChangeActionOperation;
 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.types.BooleanConstant;
 import com.android.internal.widget.remotecompose.core.types.IntegerConstant;
@@ -165,6 +175,7 @@
     public static final int DATA_MAP_LOOKUP = 154;
     public static final int TEXT_MEASURE = 155;
     public static final int TEXT_LENGTH = 156;
+    public static final int TOUCH_EXPRESSION = 157;
 
     ///////////////////////////////////////// ======================
 
@@ -194,8 +205,16 @@
     public static final int MODIFIER_ROUNDED_CLIP_RECT = 54;
 
     public static final int MODIFIER_CLICK = 59;
+    public static final int MODIFIER_TOUCH_DOWN = 219;
+    public static final int MODIFIER_TOUCH_UP = 220;
+    public static final int MODIFIER_TOUCH_CANCEL = 225;
 
-    public static final int MODIFIER_CLICK_END = 214;
+    public static final int OPERATIONS_LIST_END = 214;
+
+    public static final int MODIFIER_OFFSET = 221;
+    public static final int MODIFIER_ZINDEX = 223;
+    public static final int MODIFIER_GRAPHICS_LAYER = 224;
+
     public static final int LOOP_START = 215;
     public static final int LOOP_END = 216;
 
@@ -206,12 +225,13 @@
     public static final int VALUE_INTEGER_CHANGE_ACTION = 212;
     public static final int VALUE_STRING_CHANGE_ACTION = 213;
     public static final int VALUE_INTEGER_EXPRESSION_CHANGE_ACTION = 218;
+    public static final int VALUE_FLOAT_CHANGE_ACTION = 222;
 
     public static final int ANIMATION_SPEC = 14;
 
     public static final int COMPONENT_VALUE = 150;
 
-    public static UniqueIntMap<CompanionOperation> map = new UniqueIntMap<>();
+    @NonNull public static UniqueIntMap<CompanionOperation> map = new UniqueIntMap<>();
 
     static class UniqueIntMap<T> extends IntMap<T> {
         @Override
@@ -289,8 +309,16 @@
         map.put(MODIFIER_ROUNDED_CLIP_RECT, RoundedClipRectModifierOperation::read);
         map.put(MODIFIER_CLIP_RECT, ClipRectModifierOperation::read);
         map.put(MODIFIER_CLICK, ClickModifierOperation::read);
-        map.put(MODIFIER_CLICK_END, ClickModifierEnd::read);
+        map.put(MODIFIER_TOUCH_DOWN, TouchDownModifierOperation::read);
+        map.put(MODIFIER_TOUCH_UP, TouchUpModifierOperation::read);
+        map.put(MODIFIER_TOUCH_CANCEL, TouchCancelModifierOperation::read);
         map.put(MODIFIER_VISIBILITY, ComponentVisibilityOperation::read);
+        map.put(MODIFIER_OFFSET, OffsetModifierOperation::read);
+        map.put(MODIFIER_ZINDEX, ZIndexModifierOperation::read);
+        map.put(MODIFIER_GRAPHICS_LAYER, GraphicsLayerModifierOperation::read);
+
+        map.put(OPERATIONS_LIST_END, OperationsListEnd::read);
+
         map.put(HOST_ACTION, HostActionOperation::read);
         map.put(HOST_NAMED_ACTION, HostNamedActionOperation::read);
         map.put(VALUE_INTEGER_CHANGE_ACTION, ValueIntegerChangeActionOperation::read);
@@ -298,6 +326,7 @@
                 VALUE_INTEGER_EXPRESSION_CHANGE_ACTION,
                 ValueIntegerExpressionChangeActionOperation::read);
         map.put(VALUE_STRING_CHANGE_ACTION, ValueStringChangeActionOperation::read);
+        map.put(VALUE_FLOAT_CHANGE_ACTION, ValueFloatChangeActionOperation::read);
 
         map.put(LAYOUT_ROOT, RootLayoutComponent::read);
         map.put(LAYOUT_CONTENT, LayoutComponentContent::read);
@@ -315,5 +344,6 @@
         map.put(DATA_MAP_LOOKUP, DataMapLookup::read);
         map.put(TEXT_MEASURE, TextMeasure::read);
         map.put(TEXT_LENGTH, TextLength::read);
+        map.put(TOUCH_EXPRESSION, TouchExpression::read);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java b/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java
index 13d6f78..1a71afe 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java
@@ -271,4 +271,24 @@
     public void needsRepaint() {
         mNeedsRepaint = true;
     }
+
+    public abstract void startGraphicsLayer(int w, int h);
+
+    public abstract void setGraphicsLayer(
+            float scaleX,
+            float scaleY,
+            float rotationX,
+            float rotationY,
+            float rotationZ,
+            float shadowElevation,
+            float transformOriginX,
+            float transformOriginY,
+            float alpha,
+            int renderEffectId);
+
+    public abstract void endGraphicsLayer();
+
+    public boolean isVisualDebug() {
+        return mContext.isVisualDebug();
+    }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java b/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java
index 9b7b50f..049e477 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core;
 
+import android.annotation.NonNull;
+
 /**
  * PaintOperation interface, used for operations aimed at painting (while any operation _can_ paint,
  * this make it a little more explicit)
@@ -22,7 +24,7 @@
 public abstract class PaintOperation implements Operation {
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         if (context.getMode() == RemoteContext.ContextMode.PAINT) {
             PaintContext paintContext = context.getPaintContext();
             if (paintContext != null) {
@@ -31,6 +33,7 @@
         }
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/Platform.java b/core/java/com/android/internal/widget/remotecompose/core/Platform.java
index 6725e7e..7fbcfae 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Platform.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Platform.java
@@ -15,16 +15,30 @@
  */
 package com.android.internal.widget.remotecompose.core;
 
+import android.annotation.Nullable;
+
 /** Services that are needed to be provided by the platform during encoding. */
 public interface Platform {
+    @Nullable
     byte[] imageToByteArray(Object image);
 
     int getImageWidth(Object image);
 
     int getImageHeight(Object image);
 
+    @Nullable
     float[] pathToFloatArray(Object path);
 
+    enum LogCategory {
+        DEBUG,
+        INFO,
+        WARN,
+        ERROR,
+        TODO,
+    }
+
+    void log(LogCategory category, String message);
+
     Platform None =
             new Platform() {
                 @Override
@@ -46,5 +60,8 @@
                 public float[] pathToFloatArray(Object path) {
                     throw new UnsupportedOperationException();
                 }
+
+                @Override
+                public void log(LogCategory category, String message) {}
             };
 }
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 5b5adc2..7d9439d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
@@ -15,6 +15,9 @@
  */
 package com.android.internal.widget.remotecompose.core;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.operations.BitmapData;
 import com.android.internal.widget.remotecompose.core.operations.ClickArea;
 import com.android.internal.widget.remotecompose.core.operations.ClipPath;
@@ -64,6 +67,7 @@
 import com.android.internal.widget.remotecompose.core.operations.TextMeasure;
 import com.android.internal.widget.remotecompose.core.operations.TextMerge;
 import com.android.internal.widget.remotecompose.core.operations.Theme;
+import com.android.internal.widget.remotecompose.core.operations.TouchExpression;
 import com.android.internal.widget.remotecompose.core.operations.Utils;
 import com.android.internal.widget.remotecompose.core.operations.layout.CanvasContent;
 import com.android.internal.widget.remotecompose.core.operations.layout.ComponentEnd;
@@ -81,8 +85,11 @@
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.BackgroundModifierOperation;
 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.OffsetModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.PaddingModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.RoundedClipRectModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ZIndexModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
 import com.android.internal.widget.remotecompose.core.operations.utilities.NanMap;
 import com.android.internal.widget.remotecompose.core.operations.utilities.easing.FloatAnimation;
@@ -111,7 +118,7 @@
     public static final int EASING_EASE_OUT_BOUNCE = FloatAnimation.EASE_OUT_BOUNCE;
     public static final int EASING_EASE_OUT_ELASTIC = FloatAnimation.EASE_OUT_ELASTIC;
     WireBuffer mBuffer = new WireBuffer();
-    Platform mPlatform = null;
+    @Nullable Platform mPlatform = null;
     RemoteComposeState mRemoteComposeState;
     private static final boolean DEBUG = false;
 
@@ -143,6 +150,7 @@
         return mLastComponentId;
     }
 
+    @Nullable
     public Platform getPlatform() {
         return mPlatform;
     }
@@ -172,7 +180,11 @@
      * @param capabilities bitmask indicating needed capabilities (unused for now)
      */
     public void header(
-            int width, int height, String contentDescription, float density, long capabilities) {
+            int width,
+            int height,
+            @Nullable String contentDescription,
+            float density,
+            long capabilities) {
         Header.apply(mBuffer, width, height, density, capabilities);
         int contentDescriptionId = 0;
         if (contentDescription != null) {
@@ -219,7 +231,7 @@
             int dstTop,
             int dstRight,
             int dstBottom,
-            String contentDescription) {
+            @Nullable String contentDescription) {
         int imageId = mRemoteComposeState.dataGetId(image);
         if (imageId == -1) {
             imageId = mRemoteComposeState.cacheData(image);
@@ -267,7 +279,7 @@
      *
      * @param text the string to inject in the buffer
      */
-    public int addText(String text) {
+    public int addText(@NonNull String text) {
         int id = mRemoteComposeState.dataGetId(text);
         if (id == -1) {
             id = mRemoteComposeState.cacheData(text);
@@ -289,12 +301,12 @@
      */
     public void addClickArea(
             int id,
-            String contentDescription,
+            @Nullable String contentDescription,
             float left,
             float top,
             float right,
             float bottom,
-            String metadata) {
+            @Nullable String metadata) {
         int contentDescriptionId = 0;
         if (contentDescription != null) {
             contentDescriptionId = addText(contentDescription);
@@ -380,7 +392,7 @@
             float top,
             float right,
             float bottom,
-            String contentDescription) {
+            @Nullable String contentDescription) {
         int imageId = mRemoteComposeState.dataGetId(image);
         if (imageId == -1) {
             imageId = mRemoteComposeState.cacheData(image);
@@ -411,7 +423,7 @@
             float top,
             float right,
             float bottom,
-            String contentDescription) {
+            @Nullable String contentDescription) {
         int contentDescriptionId = 0;
         if (contentDescription != null) {
             contentDescriptionId = addText(contentDescription);
@@ -445,7 +457,7 @@
             float dstBottom,
             int scaleType,
             float scaleFactor,
-            String contentDescription) {
+            @Nullable String contentDescription) {
         int imageId = mRemoteComposeState.dataGetId(image);
         if (imageId == -1) {
             imageId = mRemoteComposeState.cacheData(image);
@@ -500,7 +512,7 @@
      * @param image drawScaledBitmap
      * @return id of the image useful with
      */
-    public int addBitmap(Object image, String name) {
+    public int addBitmap(Object image, @NonNull String name) {
         int imageId = mRemoteComposeState.dataGetId(image);
         if (imageId == -1) {
             imageId = mRemoteComposeState.cacheData(image);
@@ -521,7 +533,7 @@
      * @param id of the Bitmap
      * @param name Name of the color
      */
-    public void setBitmapName(int id, String name) {
+    public void setBitmapName(int id, @NonNull String name) {
         NamedVariable.apply(mBuffer, id, NamedVariable.IMAGE_TYPE, name);
     }
 
@@ -551,7 +563,7 @@
             float dstBottom,
             int scaleType,
             float scaleFactor,
-            String contentDescription) {
+            @Nullable String contentDescription) {
         int contentDescriptionId = 0;
         if (contentDescription != null) {
             contentDescriptionId = addText(contentDescription);
@@ -669,7 +681,7 @@
      * @param hOffset The distance along the path to add to the text's starting position
      * @param vOffset The distance above(-) or below(+) the path to position the text
      */
-    public void addDrawTextOnPath(String text, Object path, float hOffset, float vOffset) {
+    public void addDrawTextOnPath(@NonNull String text, Object path, float hOffset, float vOffset) {
         int pathId = mRemoteComposeState.dataGetId(path);
         if (pathId == -1) { // never been seen before
             pathId = addPathData(path);
@@ -692,7 +704,7 @@
      * @param rtl Draw RTTL
      */
     public void addDrawTextRun(
-            String text,
+            @NonNull String text,
             int start,
             int end,
             int contextStart,
@@ -749,7 +761,8 @@
      * @param panY position text -1.0=above, 0.0=center, 1.0=below, Nan=baseline
      * @param flags 1 = RTL
      */
-    public void drawTextAnchored(String text, float x, float y, float panX, float panY, int flags) {
+    public void drawTextAnchored(
+            @NonNull String text, float x, float y, float panX, float panY, int flags) {
         int textId = addText(text);
         DrawTextAnchored.apply(mBuffer, textId, x, y, panX, panY, flags);
     }
@@ -760,7 +773,7 @@
      * @param text
      * @return
      */
-    public int createTextId(String text) {
+    public int createTextId(@NonNull String text) {
         return addText(text);
     }
 
@@ -891,7 +904,7 @@
      *
      * @param paint
      */
-    public void addPaint(PaintBundle paint) {
+    public void addPaint(@NonNull PaintBundle paint) {
         PaintData.apply(mBuffer, paint);
     }
 
@@ -912,7 +925,8 @@
         }
     }
 
-    public static void readNextOperation(WireBuffer buffer, ArrayList<Operation> operations) {
+    public static void readNextOperation(
+            @NonNull WireBuffer buffer, ArrayList<Operation> operations) {
         int opId = buffer.readByte();
         if (DEBUG) {
             Utils.log(">> " + opId);
@@ -924,6 +938,7 @@
         operation.read(buffer, operations);
     }
 
+    @NonNull
     RemoteComposeBuffer copy() {
         ArrayList<Operation> operations = new ArrayList<>();
         inflateFromBuffer(operations);
@@ -935,33 +950,38 @@
         Theme.apply(mBuffer, theme);
     }
 
+    @NonNull
     static String version() {
         return "v1.0";
     }
 
-    public static RemoteComposeBuffer fromFile(String path, RemoteComposeState remoteComposeState)
-            throws IOException {
+    @NonNull
+    public static RemoteComposeBuffer fromFile(
+            @NonNull String path, RemoteComposeState remoteComposeState) throws IOException {
         RemoteComposeBuffer buffer = new RemoteComposeBuffer(remoteComposeState);
         read(new File(path), buffer);
         return buffer;
     }
 
-    public RemoteComposeBuffer fromFile(File file, RemoteComposeState remoteComposeState)
+    @NonNull
+    public RemoteComposeBuffer fromFile(@NonNull File file, RemoteComposeState remoteComposeState)
             throws IOException {
         RemoteComposeBuffer buffer = new RemoteComposeBuffer(remoteComposeState);
         read(file, buffer);
         return buffer;
     }
 
+    @NonNull
     public static RemoteComposeBuffer fromInputStream(
-            InputStream inputStream, RemoteComposeState remoteComposeState) {
+            @NonNull InputStream inputStream, RemoteComposeState remoteComposeState) {
         RemoteComposeBuffer buffer = new RemoteComposeBuffer(remoteComposeState);
         read(inputStream, buffer);
         return buffer;
     }
 
+    @NonNull
     RemoteComposeBuffer copyFromOperations(
-            ArrayList<Operation> operations, RemoteComposeBuffer buffer) {
+            @NonNull ArrayList<Operation> operations, @NonNull RemoteComposeBuffer buffer) {
 
         for (Operation operation : operations) {
             operation.write(buffer.mBuffer);
@@ -975,7 +995,7 @@
      * @param buffer a RemoteComposeBuffer
      * @param file a target file
      */
-    public void write(RemoteComposeBuffer buffer, File file) {
+    public void write(@NonNull RemoteComposeBuffer buffer, @NonNull File file) {
         try {
             FileOutputStream fd = new FileOutputStream(file);
             fd.write(buffer.mBuffer.getBuffer(), 0, buffer.mBuffer.getSize());
@@ -986,12 +1006,12 @@
         }
     }
 
-    static void read(File file, RemoteComposeBuffer buffer) throws IOException {
+    static void read(@NonNull File file, @NonNull RemoteComposeBuffer buffer) throws IOException {
         FileInputStream fd = new FileInputStream(file);
         read(fd, buffer);
     }
 
-    public static void read(InputStream fd, RemoteComposeBuffer buffer) {
+    public static void read(@NonNull InputStream fd, @NonNull RemoteComposeBuffer buffer) {
         try {
             byte[] bytes = readAllBytes(fd);
             buffer.reset(bytes.length);
@@ -1002,7 +1022,7 @@
         }
     }
 
-    private static byte[] readAllBytes(InputStream is) throws IOException {
+    private static byte[] readAllBytes(@NonNull InputStream is) throws IOException {
         byte[] buff = new byte[32 * 1024]; // moderate size buff to start
         int red = 0;
         while (true) {
@@ -1176,20 +1196,59 @@
      * @param value A RPN style float operation i.e. "4, 3, ADD" outputs 7
      * @return NaN id of the result of the calculation
      */
-    public float addAnimatedFloat(float... value) {
+    public float addAnimatedFloat(@NonNull float... value) {
         int id = mRemoteComposeState.cacheData(value);
         FloatExpression.apply(mBuffer, id, value, null);
         return Utils.asNan(id);
     }
 
     /**
+     * Add a touch handle system
+     *
+     * @param value the default value
+     * @param min the minimum value
+     * @param max the maximum value
+     * @param velocityId the id for the velocity TODO support in v2
+     * @param exp The Float Expression
+     * @param touchMode the touch up handling behaviour
+     * @param touchSpec the touch up handling parameters
+     * @param easingSpec the easing parameter TODO support in v2
+     * @return id of the variable to be used controlled by touch handling
+     */
+    public float addTouchExpression(
+            float value,
+            float min,
+            float max,
+            float velocityId,
+            int touchEffects,
+            float[] exp,
+            int touchMode,
+            float[] touchSpec,
+            float[] easingSpec) {
+        int id = mRemoteComposeState.nextId();
+        TouchExpression.apply(
+                mBuffer,
+                id,
+                value,
+                min,
+                max,
+                velocityId,
+                touchEffects,
+                exp,
+                touchMode,
+                touchSpec,
+                easingSpec);
+        return Utils.asNan(id);
+    }
+
+    /**
      * Add a float that is a computation based on variables. see packAnimation
      *
      * @param value A RPN style float operation i.e. "4, 3, ADD" outputs 7
      * @param animation Array of floats that represents animation
      * @return NaN id of the result of the calculation
      */
-    public float addAnimatedFloat(float[] value, float[] animation) {
+    public float addAnimatedFloat(@NonNull float[] value, float[] animation) {
         int id = mRemoteComposeState.cacheData(value);
         FloatExpression.apply(mBuffer, id, value, animation);
         return Utils.asNan(id);
@@ -1228,7 +1287,7 @@
      * @param values
      * @return the id of the array, encoded as a float NaN
      */
-    public float addFloatArray(float[] values) {
+    public float addFloatArray(@NonNull float[] values) {
         int id = mRemoteComposeState.cacheData(values, NanMap.TYPE_ARRAY);
         DataListFloat.apply(mBuffer, id, values);
         return Utils.asNan(id);
@@ -1240,7 +1299,7 @@
      * @param values array of floats to be individually stored
      * @return id of the list
      */
-    public float addFloatList(float[] values) {
+    public float addFloatList(@NonNull float[] values) {
         int[] listId = new int[values.length];
         for (int i = 0; i < listId.length; i++) {
             listId[i] = mRemoteComposeState.cacheFloat(values[i]);
@@ -1255,7 +1314,7 @@
      * @param listId array id to be stored
      * @return id of the list
      */
-    public float addList(int[] listId) {
+    public float addList(@NonNull int[] listId) {
         int id = mRemoteComposeState.cacheData(listId, NanMap.TYPE_ARRAY);
         DataListIds.apply(mBuffer, id, listId);
         return Utils.asNan(id);
@@ -1268,7 +1327,7 @@
      * @param values
      * @return the id of the map, encoded as a float NaN
      */
-    public float addFloatMap(String[] keys, float[] values) {
+    public float addFloatMap(@NonNull String[] keys, @NonNull float[] values) {
         int[] listId = new int[values.length];
         byte[] type = new byte[values.length];
         for (int i = 0; i < listId.length; i++) {
@@ -1286,7 +1345,7 @@
      * @param listId
      * @return the id of the map, encoded as a float NaN
      */
-    public int addMap(String[] keys, byte[] types, int[] listId) {
+    public int addMap(@NonNull String[] keys, byte[] types, int[] listId) {
         int id = mRemoteComposeState.cacheData(listId, NanMap.TYPE_ARRAY);
         DataMapIds.apply(mBuffer, id, keys, types, listId);
         return id;
@@ -1331,7 +1390,7 @@
      * @param value array of values to calculate maximum 32
      * @return the id as an integer
      */
-    public int addIntegerExpression(int mask, int[] value) {
+    public int addIntegerExpression(int mask, @NonNull int[] value) {
         int id = mRemoteComposeState.cacheData(value);
         IntegerExpression.apply(mBuffer, id, mask, value);
         return id;
@@ -1474,7 +1533,7 @@
      * @param id of the color
      * @param name Name of the color
      */
-    public void setColorName(int id, String name) {
+    public void setColorName(int id, @NonNull String name) {
         NamedVariable.apply(mBuffer, id, NamedVariable.COLOR_TYPE, name);
     }
 
@@ -1484,7 +1543,7 @@
      * @param id of the string
      * @param name name of the string
      */
-    public void setStringName(int id, String name) {
+    public void setStringName(int id, @NonNull String name) {
         NamedVariable.apply(mBuffer, id, NamedVariable.STRING_TYPE, name);
     }
 
@@ -1576,6 +1635,72 @@
     }
 
     /**
+     * Add an offset modifier
+     *
+     * @param x x offset
+     * @param y y offset
+     */
+    public void addModifierOffset(float x, float y) {
+        OffsetModifierOperation.apply(mBuffer, x, y);
+    }
+
+    /**
+     * Add a zIndex modifier
+     *
+     * @param value z-Index value
+     */
+    public void addModifierZIndex(float value) {
+        ZIndexModifierOperation.apply(mBuffer, value);
+    }
+
+    /**
+     * Add a graphics layer
+     *
+     * @param scaleX
+     * @param scaleY
+     * @param rotationX
+     * @param rotationY
+     * @param rotationZ
+     * @param shadowElevation
+     * @param transformOriginX
+     * @param transformOriginY
+     */
+    public void addModifierGraphicsLayer(
+            float scaleX,
+            float scaleY,
+            float rotationX,
+            float rotationY,
+            float rotationZ,
+            float shadowElevation,
+            float transformOriginX,
+            float transformOriginY,
+            float alpha,
+            float cameraDistance,
+            int blendMode,
+            int spotShadowColorId,
+            int ambientShadowColorId,
+            int colorFilterId,
+            int renderEffectId) {
+        GraphicsLayerModifierOperation.apply(
+                mBuffer,
+                scaleX,
+                scaleY,
+                rotationX,
+                rotationY,
+                rotationZ,
+                shadowElevation,
+                transformOriginX,
+                transformOriginY,
+                alpha,
+                cameraDistance,
+                blendMode,
+                spotShadowColorId,
+                ambientShadowColorId,
+                colorFilterId,
+                renderEffectId);
+    }
+
+    /**
      * Sets the clip based on rounded clip rect
      *
      * @param topStart
@@ -1721,7 +1846,8 @@
             float fontSize,
             int fontStyle,
             float fontWeight,
-            String fontFamily) {
+            @Nullable String fontFamily,
+            int textAlign) {
         mLastComponentId = getComponentId(componentId);
         int fontFamilyId = -1;
         if (fontFamily != null) {
@@ -1736,6 +1862,11 @@
                 fontSize,
                 fontStyle,
                 fontWeight,
-                fontFamilyId);
+                fontFamilyId,
+                textAlign);
+    }
+
+    public int createID(int type) {
+        return mRemoteComposeState.nextId(type);
     }
 }
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 51445f2..3039328 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
@@ -15,6 +15,9 @@
  */
 package com.android.internal.widget.remotecompose.core;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.operations.utilities.ArrayAccess;
 import com.android.internal.widget.remotecompose.core.operations.utilities.CollectionsAccess;
 import com.android.internal.widget.remotecompose.core.operations.utilities.DataMap;
@@ -50,10 +53,11 @@
 
     private final boolean[] mDataOverride = new boolean[MAX_DATA];
     private final boolean[] mIntegerOverride = new boolean[MAX_DATA];
+    private final boolean[] mFloatOverride = new boolean[MAX_DATA];
 
     private int mNextId = START_ID;
-    private int[] mIdMaps = new int[] {START_ID, NanMap.START_VAR, NanMap.START_ARRAY};
-    private RemoteContext mRemoteContext = null;
+    @NonNull private int[] mIdMaps = new int[] {START_ID, NanMap.START_VAR, NanMap.START_ARRAY};
+    @Nullable private RemoteContext mRemoteContext = null;
 
     /**
      * Get Object based on id. The system will cache things like bitmaps Paths etc. They can be
@@ -62,6 +66,7 @@
      * @param id
      * @return
      */
+    @Nullable
     public Object getFromId(int id) {
         return mIntDataMap.get(id);
     }
@@ -158,10 +163,28 @@
 
     /** Insert an float item in the cache */
     public void updateFloat(int id, float value) {
+        if (!mFloatOverride[id]) {
+            float previous = mFloatMap.get(id);
+            if (previous != value) {
+                mFloatMap.put(id, value);
+                mIntegerMap.put(id, (int) value);
+                updateListeners(id);
+            }
+        }
+    }
+
+    /**
+     * Adds a float Override.
+     *
+     * @param id
+     * @param value the new value
+     */
+    public void overrideFloat(int id, float value) {
         float previous = mFloatMap.get(id);
         if (previous != value) {
             mFloatMap.put(id, value);
             mIntegerMap.put(id, (int) value);
+            mFloatOverride[id] = true;
             updateListeners(id);
         }
     }
@@ -294,6 +317,16 @@
     }
 
     /**
+     * Clear the float override
+     *
+     * @param id the float id to clear
+     */
+    public void clearFloatOverride(int id) {
+        mFloatOverride[id] = false;
+        updateListeners(id);
+    }
+
+    /**
      * Method to determine if a cached value has been written to the documents WireBuffer based on
      * its id.
      */
@@ -322,7 +355,8 @@
     }
 
     /**
-     * Get the next available id
+     * Get the next available id 0 is normal (float,int,String,color) 1 is VARIABLES 2 is
+     * collections
      *
      * @return
      */
@@ -342,8 +376,8 @@
         mNextId = id;
     }
 
-    IntMap<ArrayList<VariableSupport>> mVarListeners = new IntMap<>();
-    ArrayList<VariableSupport> mAllVarListeners = new ArrayList<>();
+    @NonNull IntMap<ArrayList<VariableSupport>> mVarListeners = new IntMap<>();
+    @NonNull ArrayList<VariableSupport> mAllVarListeners = new ArrayList<>();
 
     private void add(int id, VariableSupport variableSupport) {
         ArrayList<VariableSupport> v = mVarListeners.get(id);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
index 1066e7d..23cc5b8 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core;
 
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.operations.FloatExpression;
 import com.android.internal.widget.remotecompose.core.operations.ShaderData;
 import com.android.internal.widget.remotecompose.core.operations.Theme;
@@ -40,10 +42,12 @@
     protected CoreDocument mDocument;
     public RemoteComposeState mRemoteComposeState;
     long mStart = System.nanoTime(); // todo This should be set at a hi level
-    protected PaintContext mPaintContext = null;
+    @Nullable protected PaintContext mPaintContext = null;
+    protected float mDensity = 2.75f;
+
     ContextMode mMode = ContextMode.UNSET;
 
-    boolean mDebug = false;
+    int mDebug = 0;
 
     private int mTheme = Theme.UNSPECIFIED;
 
@@ -56,6 +60,14 @@
     public Component lastComponent;
     public long currentTime = 0L;
 
+    public float getDensity() {
+        return mDensity;
+    }
+
+    public void setDensity(float density) {
+        mDensity = density;
+    }
+
     public boolean isAnimationEnabled() {
         return mAnimate;
     }
@@ -173,12 +185,22 @@
 
     public abstract void runAction(int id, String metadata);
 
-    public abstract void runNamedAction(int textId);
+    // TODO: we might add an interface to group all valid parameter types
+    public abstract void runNamedAction(int textId, Object value);
 
     public abstract void putObject(int mId, Object command);
 
     public abstract Object getObject(int mId);
 
+    public void addTouchListener(TouchListener touchExpression) {}
+
+    /**
+     * Vibrate the device
+     *
+     * @param type 0 = none, 1-21 ,see HapticFeedbackConstants
+     */
+    public abstract void hapticEffect(int type);
+
     /**
      * 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
@@ -206,6 +228,7 @@
         this.mMode = mode;
     }
 
+    @Nullable
     public PaintContext getPaintContext() {
         return mPaintContext;
     }
@@ -219,10 +242,14 @@
     }
 
     public boolean isDebug() {
-        return mDebug;
+        return mDebug == 1;
     }
 
-    public void setDebug(boolean debug) {
+    public boolean isVisualDebug() {
+        return mDebug == 2;
+    }
+
+    public void setDebug(int debug) {
         this.mDebug = debug;
     }
 
@@ -314,6 +341,14 @@
     public abstract void loadFloat(int id, float value);
 
     /**
+     * Override an existing float value
+     *
+     * @param id
+     * @param value
+     */
+    public abstract void overrideFloat(int id, float value);
+
+    /**
      * Load a integer
      *
      * @param id id of the integer
@@ -321,8 +356,20 @@
      */
     public abstract void loadInteger(int id, int value);
 
+    /**
+     * Override an existing int value
+     *
+     * @param id
+     * @param value
+     */
     public abstract void overrideInteger(int id, int value);
 
+    /**
+     * Override an existing text value
+     *
+     * @param id
+     * @param valueId
+     */
     public abstract void overrideText(int id, int valueId);
 
     /**
@@ -400,6 +447,25 @@
     public static final int ID_OFFSET_TO_UTC = 10;
     public static final int ID_WEEK_DAY = 11;
     public static final int ID_DAY_OF_MONTH = 12;
+    public static final int ID_TOUCH_POS_X = 13;
+    public static final int ID_TOUCH_POS_Y = 14;
+
+    public static final int ID_TOUCH_VEL_X = 15;
+    public static final int ID_TOUCH_VEL_Y = 16;
+
+    public static final int ID_ACCELERATION_X = 17;
+    public static final int ID_ACCELERATION_Y = 18;
+    public static final int ID_ACCELERATION_Z = 19;
+
+    public static final int ID_GYRO_ROT_X = 20;
+    public static final int ID_GYRO_ROT_Y = 21;
+    public static final int ID_GYRO_ROT_Z = 22;
+
+    public static final int ID_MAGNETIC_X = 23;
+    public static final int ID_MAGNETIC_Y = 24;
+    public static final int ID_MAGNETIC_Z = 25;
+
+    public static final int ID_LIGHT = 26;
 
     /** CONTINUOUS_SEC is seconds from midnight looping every hour 0-3600 */
     public static final float FLOAT_CONTINUOUS_SEC = Utils.asNan(ID_CONTINUOUS_SEC);
@@ -426,9 +492,52 @@
     public static final float FLOAT_WINDOW_HEIGHT = Utils.asNan(ID_WINDOW_HEIGHT);
     public static final float FLOAT_COMPONENT_WIDTH = Utils.asNan(ID_COMPONENT_WIDTH);
     public static final float FLOAT_COMPONENT_HEIGHT = Utils.asNan(ID_COMPONENT_HEIGHT);
-    // ID_OFFSET_TO_UTC is the offset from UTC in sec (typically / 3600f)
+
+    /** ID_OFFSET_TO_UTC is the offset from UTC in sec (typically / 3600f) */
     public static final float FLOAT_OFFSET_TO_UTC = Utils.asNan(ID_OFFSET_TO_UTC);
 
+    /** TOUCH_POS_X is the x position of the touch */
+    public static final float FLOAT_TOUCH_POS_X = Utils.asNan(ID_TOUCH_POS_X);
+
+    /** TOUCH_POS_Y is the y position of the touch */
+    public static final float FLOAT_TOUCH_POS_Y = Utils.asNan(ID_TOUCH_POS_Y);
+
+    /** TOUCH_VEL_X is the x velocity of the touch */
+    public static final float FLOAT_TOUCH_VEL_X = Utils.asNan(ID_TOUCH_VEL_X);
+
+    /** TOUCH_VEL_Y is the x velocity of the touch */
+    public static final float FLOAT_TOUCH_VEL_Y = Utils.asNan(ID_TOUCH_VEL_Y);
+
+    /** X acceleration sensor value in M/s^2 */
+    public static final float FLOAT_ACCELERATION_X = Utils.asNan(ID_ACCELERATION_X);
+
+    /** Y acceleration sensor value in M/s^2 */
+    public static final float FLOAT_ACCELERATION_Y = Utils.asNan(ID_ACCELERATION_Y);
+
+    /** Z acceleration sensor value in M/s^2 */
+    public static final float FLOAT_ACCELERATION_Z = Utils.asNan(ID_ACCELERATION_Z);
+
+    /** X Gyroscope rotation rate sensor value in radians/second */
+    public static final float FLOAT_GYRO_ROT_X = Utils.asNan(ID_GYRO_ROT_X);
+
+    /** Y Gyroscope rotation rate sensor value in radians/second */
+    public static final float FLOAT_GYRO_ROT_Y = Utils.asNan(ID_GYRO_ROT_Y);
+
+    /** Z Gyroscope rotation rate sensor value in radians/second */
+    public static final float FLOAT_GYRO_ROT_Z = Utils.asNan(ID_GYRO_ROT_Z);
+
+    /** Ambient magnetic field in X. sensor value in micro-Tesla (uT) */
+    public static final float FLOAT_MAGNETIC_X = Utils.asNan(ID_MAGNETIC_X);
+
+    /** Ambient magnetic field in Y. sensor value in micro-Tesla (uT) */
+    public static final float FLOAT_MAGNETIC_Y = Utils.asNan(ID_MAGNETIC_Y);
+
+    /** Ambient magnetic field in Z. sensor value in micro-Tesla (uT) */
+    public static final float FLOAT_MAGNETIC_Z = Utils.asNan(ID_MAGNETIC_Z);
+
+    /** Ambient light level in SI lux */
+    public static final float FLOAT_LIGHT = Utils.asNan(ID_LIGHT);
+
     ///////////////////////////////////////////////////////////////////////////////////////////////
     // Click handling
     ///////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java b/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java
index fa0cf3f..14aed2f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core;
 
+import android.annotation.NonNull;
+
 import java.time.LocalDateTime;
 import java.time.OffsetDateTime;
 import java.time.ZoneId;
@@ -27,7 +29,7 @@
      *
      * @param context
      */
-    public void updateTime(RemoteContext context) {
+    public void updateTime(@NonNull RemoteContext context) {
         LocalDateTime dateTime =
                 LocalDateTime.now(ZoneId.systemDefault()); // TODO, pass in a timezone explicitly?
         // This define the time in the format
diff --git a/core/java/com/android/internal/widget/remotecompose/core/TouchListener.java b/core/java/com/android/internal/widget/remotecompose/core/TouchListener.java
new file mode 100644
index 0000000..3dda678
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/TouchListener.java
@@ -0,0 +1,24 @@
+/*
+ * 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;
+
+public interface TouchListener {
+    void touchDown(RemoteContext context, float x, float y);
+
+    void touchUp(RemoteContext context, float x, float y, float dx, float dy);
+
+    void touchDrag(RemoteContext context, float x, float y);
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
index c71b490..738e42b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core;
 
+import android.annotation.NonNull;
+
 import java.util.Arrays;
 
 /** The base communication buffer capable of encoding and decoding various types */
@@ -184,11 +186,13 @@
         return b;
     }
 
+    @NonNull
     public String readUTF8() {
         byte[] stringBuffer = readBuffer();
         return new String(stringBuffer);
     }
 
+    @NonNull
     public String readUTF8(int maxSize) {
         byte[] stringBuffer = readBuffer(maxSize);
         return new String(stringBuffer);
@@ -250,7 +254,7 @@
         writeLong(Double.doubleToRawLongBits(value));
     }
 
-    public void writeBuffer(byte[] b) {
+    public void writeBuffer(@NonNull byte[] b) {
         resize(b.length + 4);
         writeInt(b.length);
         for (int i = 0; i < b.length; i++) {
@@ -259,7 +263,7 @@
         mSize += b.length;
     }
 
-    public void writeUTF8(String content) {
+    public void writeUTF8(@NonNull String content) {
         byte[] buffer = content.getBytes();
         writeBuffer(buffer);
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentedOperation.java b/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentedOperation.java
index c33ae24..5edecaa 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentedOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentedOperation.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.documentation;
 
+import android.annotation.NonNull;
+
 import java.util.ArrayList;
 
 public class DocumentedOperation {
@@ -40,12 +42,13 @@
     boolean mWIP;
     String mTextExamples;
 
-    ArrayList<StringPair> mExamples = new ArrayList<>();
-    ArrayList<OperationField> mFields = new ArrayList<>();
-    String mVarSize = "";
+    @NonNull ArrayList<StringPair> mExamples = new ArrayList<>();
+    @NonNull ArrayList<OperationField> mFields = new ArrayList<>();
+    @NonNull String mVarSize = "";
     int mExamplesWidth = 100;
     int mExamplesHeight = 100;
 
+    @NonNull
     public static String getType(int type) {
         switch (type) {
             case INT:
@@ -85,6 +88,7 @@
         this(category, id, name, false);
     }
 
+    @NonNull
     public ArrayList<OperationField> getFields() {
         return mFields;
     }
@@ -105,6 +109,7 @@
         return mWIP;
     }
 
+    @NonNull
     public String getVarSize() {
         return mVarSize;
     }
@@ -129,6 +134,7 @@
         return mTextExamples;
     }
 
+    @NonNull
     public ArrayList<StringPair> getExamples() {
         return mExamples;
     }
@@ -141,16 +147,19 @@
         return mExamplesHeight;
     }
 
+    @NonNull
     public DocumentedOperation field(int type, String name, String description) {
         mFields.add(new OperationField(type, name, description));
         return this;
     }
 
+    @NonNull
     public DocumentedOperation field(int type, String name, String varSize, String description) {
         mFields.add(new OperationField(type, name, varSize, description));
         return this;
     }
 
+    @NonNull
     public DocumentedOperation possibleValues(String name, int value) {
         if (!mFields.isEmpty()) {
             mFields.get(mFields.size() - 1).possibleValue(name, "" + value);
@@ -158,21 +167,25 @@
         return this;
     }
 
+    @NonNull
     public DocumentedOperation description(String description) {
         mDescription = description;
         return this;
     }
 
+    @NonNull
     public DocumentedOperation examples(String examples) {
         mTextExamples = examples;
         return this;
     }
 
+    @NonNull
     public DocumentedOperation exampleImage(String name, String imagePath) {
         mExamples.add(new StringPair(name, imagePath));
         return this;
     }
 
+    @NonNull
     public DocumentedOperation examplesDimension(int width, int height) {
         mExamplesWidth = width;
         mExamplesHeight = height;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/documentation/OperationField.java b/core/java/com/android/internal/widget/remotecompose/core/documentation/OperationField.java
index c770483..cbb5ca9 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/documentation/OperationField.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/documentation/OperationField.java
@@ -15,15 +15,18 @@
  */
 package com.android.internal.widget.remotecompose.core.documentation;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import java.util.ArrayList;
 
 public class OperationField {
     int mType;
     String mName;
     String mDescription;
-    String mVarSize = null;
+    @Nullable String mVarSize = null;
 
-    ArrayList<StringPair> mPossibleValues = new ArrayList<>();
+    @NonNull ArrayList<StringPair> mPossibleValues = new ArrayList<>();
 
     public OperationField(int type, String name, String description) {
         mType = type;
@@ -50,6 +53,7 @@
         return mDescription;
     }
 
+    @NonNull
     public ArrayList<StringPair> getPossibleValues() {
         return mPossibleValues;
     }
@@ -62,6 +66,7 @@
         return !mPossibleValues.isEmpty();
     }
 
+    @Nullable
     public String getVarSize() {
         return mVarSize;
     }
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 20ba8c31..8da0e18 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
@@ -18,6 +18,8 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT_ARRAY;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -58,15 +60,17 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mImageId, mImageWidth, mImageHeight, mBitmap);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "BITMAP DATA " + mImageId;
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -75,7 +79,12 @@
         return OP_CODE;
     }
 
-    public static void apply(WireBuffer buffer, int imageId, int width, int height, byte[] bitmap) {
+    public static void apply(
+            @NonNull WireBuffer buffer,
+            int imageId,
+            int width,
+            int height,
+            @NonNull byte[] bitmap) {
         buffer.start(OP_CODE);
         buffer.writeInt(imageId);
         buffer.writeInt(width);
@@ -83,7 +92,7 @@
         buffer.writeBuffer(bitmap);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int imageId = buffer.readInt();
         int width = buffer.readInt();
         int height = buffer.readInt();
@@ -97,7 +106,7 @@
         operations.add(new BitmapData(imageId, width, height, bitmap));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Data Operations", OP_CODE, CLASS_NAME)
                 .description("Bitmap data")
                 .field(DocumentedOperation.INT, "id", "id of bitmap data")
@@ -107,17 +116,18 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         context.loadBitmap(mImageId, mImageWidth, mImageHeight, mBitmap);
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
     }
 
     @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
         serializer.append(
                 indent,
                 CLASS_NAME + " id " + mImageId + " (" + mImageWidth + "x" + mImageHeight + ")");
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 8b9e5a8..83d0ac7 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
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+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.RemoteComposeOperation;
@@ -67,10 +69,11 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mId, mContentDescription, mLeft, mTop, mRight, mBottom, mMetadata);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "CLICK_AREA <"
@@ -97,18 +100,20 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         if (context.getMode() != RemoteContext.ContextMode.DATA) {
             return;
         }
         context.addClickArea(mId, mContentDescription, mLeft, mTop, mRight, mBottom, mMetadata);
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -118,7 +123,7 @@
     }
 
     public static void apply(
-            WireBuffer buffer,
+            @NonNull WireBuffer buffer,
             int id,
             int contentDescription,
             float left,
@@ -136,7 +141,7 @@
         buffer.writeInt(metadata);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
         int contentDescription = buffer.readInt();
         float left = buffer.readFloat();
@@ -149,7 +154,7 @@
         operations.add(clickArea);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description("Define a region you can click on")
                 .field(DocumentedOperation.FLOAT, "left", "The left side of the region")
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 96b600a..db93829 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
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -57,16 +59,17 @@
     public static final int UNDEFINED = PATH_CLIP_UNDEFINED;
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mId);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "ClipPath " + mId + ";";
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int pack = buffer.readInt();
         int id = pack & 0xFFFFF;
         int regionOp = pack >> 24;
@@ -74,6 +77,7 @@
         operations.add(op);
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -82,19 +86,19 @@
         return OP_CODE;
     }
 
-    public static void apply(WireBuffer buffer, int id) {
+    public static void apply(@NonNull WireBuffer buffer, int id) {
         buffer.start(OP_CODE);
         buffer.writeInt(id);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description("Intersect the current clip with the path")
                 .field(DocumentedOperation.INT, "id", "id of the path");
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.clipPath(mId, mRegionOp);
     }
 }
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 b101bfb..df54fb1 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+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;
@@ -29,7 +31,7 @@
     public static final int OP_CODE = Operations.CLIP_RECT;
     public static final String CLASS_NAME = "ClipRect";
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m = ClipRect::new;
         read(m, buffer, operations);
     }
@@ -38,16 +40,17 @@
         return OP_CODE;
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
     @Override
-    protected void write(WireBuffer buffer, float v1, float v2, float v3, float v4) {
+    protected void write(@NonNull WireBuffer buffer, float v1, float v2, float v3, float v4) {
         apply(buffer, v1, v2, v3, v4);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, CLASS_NAME)
                 .description("Intersect the current clip with rectangle")
                 .field(
@@ -74,7 +77,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.clipRect(mX1, mY1, mX2, mY2);
     }
 
@@ -87,7 +90,7 @@
      * @param x2 end x of the DrawOval
      * @param y2 end y of the DrawOval
      */
-    public static void apply(WireBuffer buffer, float x1, float y1, float x2, float y2) {
+    public static void apply(@NonNull WireBuffer buffer, float x1, float y1, float x2, float y2) {
         write(buffer, OP_CODE, x1, y1, x2, y2);
     }
 }
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 19d80da..929c9a60 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
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -39,15 +41,17 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mColorId, mColor);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "ColorConstant[" + mColorId + "] = " + Utils.colorInt(mColor) + "";
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -63,19 +67,19 @@
      * @param colorId
      * @param color
      */
-    public static void apply(WireBuffer buffer, int colorId, int color) {
+    public static void apply(@NonNull WireBuffer buffer, int colorId, int color) {
         buffer.start(OP_CODE);
         buffer.writeInt(colorId);
         buffer.writeInt(color);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int colorId = buffer.readInt();
         int color = buffer.readInt();
         operations.add(new ColorConstant(colorId, color));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, CLASS_NAME)
                 .description("Define a Color")
                 .field(DocumentedOperation.INT, "id", "Id of the color")
@@ -83,10 +87,11 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         context.loadColor(mColorId, mColor);
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java
index b6041ea..3d840c5 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
@@ -18,6 +18,8 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -94,7 +96,7 @@
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         if (mMode == 4) {
             if (Float.isNaN(mHue)) {
                 mOutHue = context.getFloat(Utils.idFromNan(mHue));
@@ -118,7 +120,7 @@
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         if (mMode == 4) {
             if (Float.isNaN(mHue)) {
                 context.listensTo(Utils.idFromNan(mHue), this);
@@ -143,7 +145,7 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         if (mMode == 4) {
             context.loadColor(
                     mId, (mAlpha << 24) | (0xFFFFFF & Utils.hsvToRgb(mOutHue, mOutSat, mOutValue)));
@@ -164,11 +166,12 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         int mode = mMode | (mAlpha << 16);
         apply(buffer, mId, mode, mColor1, mColor2, mTween);
     }
 
+    @NonNull
     @Override
     public String toString() {
         if (mMode == 4) {
@@ -196,6 +199,7 @@
                 + ")";
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -215,7 +219,7 @@
      * @param tween
      */
     public static void apply(
-            WireBuffer buffer, int id, int mode, int color1, int color2, float tween) {
+            @NonNull WireBuffer buffer, int id, int mode, int color1, int color2, float tween) {
         buffer.start(OP_CODE);
         buffer.writeInt(id);
         buffer.writeInt(mode);
@@ -224,7 +228,7 @@
         buffer.writeFloat(tween);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
         int mode = buffer.readInt();
         int color1 = buffer.readInt();
@@ -234,7 +238,7 @@
         operations.add(new ColorExpression(id, mode, color1, color2, tween));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, CLASS_NAME)
                 .description("A Color defined by an expression")
                 .field(DocumentedOperation.INT, "id", "Id of the color")
@@ -249,6 +253,7 @@
                 .field(FLOAT, "tween", "32 bit ARGB color");
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ComponentValue.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ComponentValue.java
index 9929720..142c97b2 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
@@ -17,6 +17,9 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -43,10 +46,12 @@
         return OP_CODE;
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return CLASS_NAME + "(" + mType + ", " + mComponentID + ", " + mValueId + ")";
@@ -65,7 +70,7 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mType, mComponentID, mValueId);
     }
 
@@ -74,7 +79,7 @@
         // Nothing
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int type = buffer.readInt();
         int componentId = buffer.readInt();
         int valueId = buffer.readInt();
@@ -82,7 +87,7 @@
         operations.add(op);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, CLASS_NAME)
                 .description("Encode a component-related value (eg its width, height etc.)")
                 .field(
@@ -111,20 +116,21 @@
      * @param componentId component id to reference
      * @param valueId remote float used to represent the component value
      */
-    public static void apply(WireBuffer buffer, int type, int componentId, int valueId) {
+    public static void apply(@NonNull WireBuffer buffer, int type, int componentId, int valueId) {
         buffer.start(OP_CODE);
         buffer.writeInt(type);
         buffer.writeInt(componentId);
         buffer.writeInt(valueId);
     }
 
+    @Nullable
     @Override
     public String deepToString(String indent) {
         return null;
     }
 
     @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
         String type = "WIDTH";
         if (mType == HEIGHT) {
             type = "HEIGHT";
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java
index 0075869..ba02b91 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java
@@ -18,6 +18,8 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT_ARRAY;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -48,7 +50,7 @@
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         context.addCollection(mId, this);
         for (float value : mValues) {
             if (Utils.isVariable(value)) {
@@ -58,16 +60,17 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mId, mValues);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "DataListFloat[" + Utils.idString(mId) + "] " + Arrays.toString(mValues);
     }
 
-    public static void apply(WireBuffer buffer, int id, float[] values) {
+    public static void apply(@NonNull WireBuffer buffer, int id, @NonNull float[] values) {
         buffer.start(OP_CODE);
         buffer.writeInt(id);
         buffer.writeInt(values.length);
@@ -76,7 +79,7 @@
         }
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
         int len = buffer.readInt();
         if (len > MAX_FLOAT_ARRAY) {
@@ -90,7 +93,7 @@
         operations.add(data);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Data Operations", OP_CODE, CLASS_NAME)
                 .description("a list of Floats")
                 .field(DocumentedOperation.INT, "id", "id the array (2xxxxx)")
@@ -98,13 +101,14 @@
                 .field(FLOAT_ARRAY, "values", "length", "array of floats");
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         context.addCollection(mId, this);
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java
index c43dab4..b9820f8 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java
@@ -18,6 +18,8 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT_ARRAY;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -49,16 +51,17 @@
     public void registerListening(RemoteContext context) {}
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mId, mIds);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "map[" + Utils.idString(mId) + "]  \"" + Arrays.toString(mIds) + "\"";
     }
 
-    public static void apply(WireBuffer buffer, int id, int[] ids) {
+    public static void apply(@NonNull WireBuffer buffer, int id, @NonNull int[] ids) {
         buffer.start(OP_CODE);
         buffer.writeInt(id);
         buffer.writeInt(ids.length);
@@ -67,7 +70,7 @@
         }
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
         int len = buffer.readInt();
         if (len > MAX_LIST) {
@@ -81,7 +84,7 @@
         operations.add(data);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Data Operations", OP_CODE, CLASS_NAME)
                 .description("a list of id's")
                 .field(DocumentedOperation.INT, "id", "id the array")
@@ -89,13 +92,14 @@
                 .field(INT_ARRAY, "ids[n]", "length", "ids of other variables");
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         context.addCollection(mId, this);
     }
 
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 75db29d..fb559bb 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
@@ -18,6 +18,8 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.UTF8;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -64,10 +66,11 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mId, mDataMap.mNames, mDataMap.mTypes, mDataMap.mIds);
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuilder builder = new StringBuilder("DataMapIds[" + Utils.idString(mId) + "] ");
@@ -84,7 +87,8 @@
         return builder.toString();
     }
 
-    public static void apply(WireBuffer buffer, int id, String[] names, byte[] type, int[] ids) {
+    public static void apply(
+            @NonNull WireBuffer buffer, int id, @NonNull String[] names, byte[] type, int[] ids) {
         buffer.start(OP_CODE);
         buffer.writeInt(id);
         buffer.writeInt(names.length);
@@ -95,7 +99,7 @@
         }
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
         int len = buffer.readInt();
         if (len > MAX_MAP) {
@@ -113,7 +117,7 @@
         operations.add(data);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Data Operations", OP_CODE, CLASS_NAME)
                 .description("Encode a collection of name id pairs")
                 .field(INT, "id", "id the array")
@@ -122,13 +126,14 @@
                 .field(UTF8, "id[0]", "length", "path encoded as floats");
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         context.putDataMap(mId, mDataMap);
     }
 }
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 e078307..629f786 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+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;
@@ -28,7 +30,7 @@
     public static final int OP_CODE = Operations.DRAW_ARC;
     private static final String CLASS_NAME = "DrawArc";
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m = DrawArc::new;
         read(m, buffer, operations);
     }
@@ -49,7 +51,13 @@
      * @param v6 Sweep angle (in degrees) measured clockwise
      */
     public static void apply(
-            WireBuffer buffer, float v1, float v2, float v3, float v4, float v5, float v6) {
+            @NonNull WireBuffer buffer,
+            float v1,
+            float v2,
+            float v3,
+            float v4,
+            float v5,
+            float v6) {
         buffer.start(OP_CODE);
         buffer.writeFloat(v1);
         buffer.writeFloat(v2);
@@ -61,11 +69,17 @@
 
     @Override
     protected void write(
-            WireBuffer buffer, float v1, float v2, float v3, float v4, float v5, float v6) {
+            @NonNull WireBuffer buffer,
+            float v1,
+            float v2,
+            float v3,
+            float v4,
+            float v5,
+            float v6) {
         apply(buffer, v1, v2, v3, v4, v5, v6);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description(
                         "Draw the specified arc"
@@ -90,7 +104,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.drawArc(mV1, mV2, mV3, mV4, mV5, mV6);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java
index c678cc4..984599e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java
@@ -17,6 +17,9 @@
 
 import static com.android.internal.widget.remotecompose.core.operations.Utils.floatToString;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.PaintOperation;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -27,7 +30,7 @@
 
 /** Base class for commands that take 3 float */
 public abstract class DrawBase2 extends PaintOperation implements VariableSupport {
-    protected String mName = "DrawRectBase";
+    @NonNull protected String mName = "DrawRectBase";
     float mV1;
     float mV2;
     float mValue1;
@@ -41,13 +44,13 @@
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         mV1 = Float.isNaN(mValue1) ? context.getFloat(Utils.idFromNan(mValue1)) : mValue1;
         mV2 = Float.isNaN(mValue2) ? context.getFloat(Utils.idFromNan(mValue2)) : mValue2;
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         if (Float.isNaN(mValue1)) {
             context.listensTo(Utils.idFromNan(mValue1), this);
         }
@@ -67,12 +70,14 @@
         DrawBase2 create(float v1, float v2);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return mName + " " + floatToString(mV1) + " " + floatToString(mV2);
     }
 
-    public static void read(Maker maker, WireBuffer buffer, List<Operation> operations) {
+    public static void read(
+            @NonNull Maker maker, @NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         float v1 = buffer.readFloat();
         float v2 = buffer.readFloat();
 
@@ -87,6 +92,7 @@
      * @param y1
      * @return
      */
+    @Nullable
     public Operation construct(float x1, float y1) {
         return null;
     }
@@ -99,7 +105,7 @@
      * @param x1
      * @param y1
      */
-    protected static void write(WireBuffer buffer, int opCode, float x1, float y1) {
+    protected static void write(@NonNull WireBuffer buffer, int opCode, float x1, float y1) {
         buffer.start(opCode);
         buffer.writeFloat(x1);
         buffer.writeFloat(y1);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java
index e1108e9..825da52 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java
@@ -17,6 +17,9 @@
 
 import static com.android.internal.widget.remotecompose.core.operations.Utils.floatToString;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.PaintOperation;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -28,7 +31,7 @@
 /** Base class for commands that take 3 float */
 public abstract class DrawBase3 extends PaintOperation implements VariableSupport {
 
-    protected String mName = "DrawRectBase";
+    @NonNull protected String mName = "DrawRectBase";
     float mV1;
     float mV2;
     float mV3;
@@ -47,14 +50,14 @@
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         mV1 = Utils.isVariable(mValue1) ? context.getFloat(Utils.idFromNan(mValue1)) : mValue1;
         mV2 = Utils.isVariable(mValue2) ? context.getFloat(Utils.idFromNan(mValue2)) : mValue2;
         mV3 = Utils.isVariable(mValue3) ? context.getFloat(Utils.idFromNan(mValue3)) : mValue3;
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         if (Utils.isVariable(mValue1)) {
             context.listensTo(Utils.idFromNan(mValue1), this);
         }
@@ -77,6 +80,7 @@
         DrawBase3 create(float v1, float v2, float v3);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return mName
@@ -88,7 +92,8 @@
                 + floatToString(mV3);
     }
 
-    public static void read(Maker maker, WireBuffer buffer, List<Operation> operations) {
+    public static void read(
+            @NonNull Maker maker, @NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         float v1 = buffer.readFloat();
         float v2 = buffer.readFloat();
         float v3 = buffer.readFloat();
@@ -104,6 +109,7 @@
      * @param x2
      * @return
      */
+    @Nullable
     public Operation construct(float x1, float y1, float x2) {
         return null;
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java
index 09f0df9..a23bcb9 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java
@@ -17,6 +17,9 @@
 
 import static com.android.internal.widget.remotecompose.core.operations.Utils.floatToString;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.PaintOperation;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -27,7 +30,7 @@
 
 /** Base class for draw commands that take 4 floats */
 public abstract class DrawBase4 extends PaintOperation implements VariableSupport {
-    protected String mName = "DrawRectBase";
+    @NonNull protected String mName = "DrawRectBase";
     protected float mX1;
     protected float mY1;
     protected float mX2;
@@ -50,7 +53,7 @@
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         mX1 = Float.isNaN(mX1Value) ? context.getFloat(Utils.idFromNan(mX1Value)) : mX1Value;
         mY1 = Float.isNaN(mY1Value) ? context.getFloat(Utils.idFromNan(mY1Value)) : mY1Value;
         mX2 = Float.isNaN(mX2Value) ? context.getFloat(Utils.idFromNan(mX2Value)) : mX2Value;
@@ -58,7 +61,7 @@
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         if (Float.isNaN(mX1Value)) {
             context.listensTo(Utils.idFromNan(mX1Value), this);
         }
@@ -84,6 +87,7 @@
         DrawBase4 create(float v1, float v2, float v3, float v4);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return mName
@@ -97,7 +101,8 @@
                 + floatToString(mY2Value, mY2);
     }
 
-    public static void read(Maker maker, WireBuffer buffer, List<Operation> operations) {
+    public static void read(
+            @NonNull Maker maker, @NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         float v1 = buffer.readFloat();
         float v2 = buffer.readFloat();
         float v3 = buffer.readFloat();
@@ -116,6 +121,7 @@
      * @param y2
      * @return
      */
+    @Nullable
     public Operation construct(float x1, float y1, float x2, float y2) {
         return null;
     }
@@ -131,7 +137,7 @@
      * @param y2
      */
     protected static void write(
-            WireBuffer buffer, int opCode, float x1, float y1, float x2, float y2) {
+            @NonNull WireBuffer buffer, int opCode, float x1, float y1, float x2, float y2) {
         buffer.start(opCode);
         buffer.writeFloat(x1);
         buffer.writeFloat(y1);
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 e071d5f..68c9f8c 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
@@ -15,6 +15,9 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.PaintOperation;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -25,7 +28,7 @@
 
 /** Base class for draw commands the take 6 floats */
 public abstract class DrawBase6 extends PaintOperation implements VariableSupport {
-    protected String mName = "DrawRectBase";
+    @NonNull protected String mName = "DrawRectBase";
     float mV1;
     float mV2;
     float mV3;
@@ -56,7 +59,7 @@
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         mV1 = Float.isNaN(mValue1) ? context.getFloat(Utils.idFromNan(mValue1)) : mValue1;
         mV2 = Float.isNaN(mValue2) ? context.getFloat(Utils.idFromNan(mValue2)) : mValue2;
         mV3 = Float.isNaN(mValue3) ? context.getFloat(Utils.idFromNan(mValue3)) : mValue3;
@@ -66,7 +69,7 @@
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         if (Float.isNaN(mValue1)) {
             context.listensTo(Utils.idFromNan(mValue1), this);
         }
@@ -95,6 +98,7 @@
     protected abstract void write(
             WireBuffer buffer, float v1, float v2, float v3, float v4, float v5, float v6);
 
+    @NonNull
     @Override
     public String toString() {
         return mName
@@ -112,7 +116,8 @@
         DrawBase6 create(float v1, float v2, float v3, float v4, float v5, float v6);
     }
 
-    public static void read(Maker build, WireBuffer buffer, List<Operation> operations) {
+    public static void read(
+            @NonNull Maker build, @NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         float sv1 = buffer.readFloat();
         float sv2 = buffer.readFloat();
         float sv3 = buffer.readFloat();
@@ -135,10 +140,12 @@
      * @param v6
      * @return
      */
+    @Nullable
     public Operation construct(float v1, float v2, float v3, float v4, float v5, float v6) {
         return null;
     }
 
+    @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 0b43fd2..9c23c95 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
@@ -18,6 +18,8 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -54,7 +56,7 @@
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         mOutputLeft = Float.isNaN(mLeft) ? context.getFloat(Utils.idFromNan(mLeft)) : mLeft;
         mOutputTop = Float.isNaN(mTop) ? context.getFloat(Utils.idFromNan(mTop)) : mTop;
         mOutputRight = Float.isNaN(mRight) ? context.getFloat(Utils.idFromNan(mRight)) : mRight;
@@ -62,7 +64,7 @@
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         if (Float.isNaN(mLeft)) {
             context.listensTo(Utils.idFromNan(mLeft), this);
         }
@@ -78,10 +80,11 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mId, mLeft, mTop, mRight, mBottom, mDescriptionId);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "DrawBitmap (desc="
@@ -97,7 +100,7 @@
                 + ";";
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
         float sLeft = buffer.readFloat();
         float srcTop = buffer.readFloat();
@@ -109,6 +112,7 @@
         operations.add(op);
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -118,7 +122,7 @@
     }
 
     public static void apply(
-            WireBuffer buffer,
+            @NonNull WireBuffer buffer,
             int id,
             float left,
             float top,
@@ -134,7 +138,7 @@
         buffer.writeInt(descriptionId);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Draw Operations", OP_CODE, CLASS_NAME)
                 .description("Draw a bitmap")
                 .field(INT, "id", "id of float")
@@ -146,7 +150,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.drawBitmap(mId, mOutputLeft, mOutputTop, mOutputRight, mOutputBottom);
     }
 }
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 fc74827..da9fe24 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
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+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;
@@ -64,7 +66,7 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(
                 buffer,
                 mImageId,
@@ -79,6 +81,7 @@
                 mContentDescId);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "DRAW_BITMAP_INT "
@@ -103,6 +106,7 @@
                 + ";";
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -112,7 +116,7 @@
     }
 
     public static void apply(
-            WireBuffer buffer,
+            @NonNull WireBuffer buffer,
             int imageId,
             int srcLeft,
             int srcTop,
@@ -136,7 +140,7 @@
         buffer.writeInt(cdId);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int imageId = buffer.readInt();
         int sLeft = buffer.readInt();
         int srcTop = buffer.readInt();
@@ -155,7 +159,7 @@
         operations.add(op);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Draw Operations", OP_CODE, CLASS_NAME)
                 .description("Draw a bitmap using integer coordinates")
                 .field(DocumentedOperation.INT, "id", "id of bitmap")
@@ -171,7 +175,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.drawBitmap(
                 mImageId,
                 mSrcLeft,
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 22742c6..e20bcd2 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
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+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;
@@ -45,7 +47,7 @@
     float mScaleFactor, mOutScaleFactor;
     int mScaleType;
 
-    ImageScaling mScaling = new ImageScaling();
+    @NonNull ImageScaling mScaling = new ImageScaling();
     public static final int SCALE_NONE = ImageScaling.SCALE_NONE;
     public static final int SCALE_INSIDE = ImageScaling.SCALE_INSIDE;
     public static final int SCALE_FILL_WIDTH = ImageScaling.SCALE_FILL_WIDTH;
@@ -83,7 +85,7 @@
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         mOutSrcLeft =
                 Float.isNaN(mSrcLeft) ? context.getFloat(Utils.idFromNan(mSrcLeft)) : mSrcLeft;
         mOutSrcTop = Float.isNaN(mSrcTop) ? context.getFloat(Utils.idFromNan(mSrcTop)) : mSrcTop;
@@ -109,7 +111,7 @@
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         register(context, mSrcLeft);
         register(context, mSrcTop);
         register(context, mSrcRight);
@@ -121,12 +123,13 @@
         register(context, mScaleFactor);
     }
 
-    private void register(RemoteContext context, float value) {
+    private void register(@NonNull RemoteContext context, float value) {
         if (Float.isNaN(value)) {
             context.listensTo(Utils.idFromNan(value), this);
         }
     }
 
+    @NonNull
     static String str(float v) {
         String s = "  " + (int) v;
         return s.substring(s.length() - 3);
@@ -140,7 +143,7 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(
                 buffer,
                 mImageId,
@@ -157,6 +160,7 @@
                 mContentDescId);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "DrawBitmapScaled "
@@ -185,6 +189,7 @@
                 + Utils.floatToString(mScaleFactor, mOutScaleFactor);
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -194,7 +199,7 @@
     }
 
     public static void apply(
-            WireBuffer buffer,
+            @NonNull WireBuffer buffer,
             int imageId,
             float srcLeft,
             float srcTop,
@@ -225,7 +230,7 @@
         buffer.writeInt(cdId);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int imageId = buffer.readInt();
 
         float sLeft = buffer.readFloat();
@@ -258,7 +263,7 @@
         operations.add(op);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Draw Operations", OP_CODE, CLASS_NAME)
                 .description("Draw a bitmap using integer coordinates")
                 .field(DocumentedOperation.INT, "id", "id of bitmap")
@@ -289,7 +294,7 @@
     //    }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         mScaling.setup(
                 mOutSrcLeft,
                 mOutSrcTop,
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 04f095a..11bd49a 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
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+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;
@@ -28,7 +30,7 @@
     private static final int OP_CODE = Operations.DRAW_CIRCLE;
     private static final String CLASS_NAME = "DrawCircle";
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m = DrawCircle::new;
         read(m, buffer, operations);
     }
@@ -37,11 +39,12 @@
         return OP_CODE;
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description("Draw a Circle")
                 .field(
@@ -56,7 +59,7 @@
     }
 
     @Override
-    protected void write(WireBuffer buffer, float v1, float v2, float v3) {
+    protected void write(@NonNull WireBuffer buffer, float v1, float v2, float v3) {
         apply(buffer, v1, v2, v3);
     }
 
@@ -66,7 +69,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.drawCircle(mV1, mV2, mV3);
     }
 
@@ -78,7 +81,7 @@
      * @param y1
      * @param x2
      */
-    public static void apply(WireBuffer buffer, float x1, float y1, float x2) {
+    public static void apply(@NonNull WireBuffer buffer, float x1, float y1, float x2) {
         buffer.start(OP_CODE);
         buffer.writeFloat(x1);
         buffer.writeFloat(y1);
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 dacbb03..7310a9d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+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;
@@ -30,7 +32,7 @@
     private static final int OP_CODE = Operations.DRAW_LINE;
     private static final String CLASS_NAME = "DrawLine";
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m = DrawLine::new;
         read(m, buffer, operations);
     }
@@ -39,11 +41,12 @@
         return OP_CODE;
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description("Draw a line segment")
                 .field(
@@ -65,7 +68,7 @@
     }
 
     @Override
-    protected void write(WireBuffer buffer, float v1, float v2, float v3, float v4) {
+    protected void write(@NonNull WireBuffer buffer, float v1, float v2, float v3, float v4) {
         apply(buffer, v1, v2, v3, v4);
     }
 
@@ -75,7 +78,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.drawLine(mX1, mY1, mX2, mY2);
     }
 
@@ -88,12 +91,12 @@
      * @param x2 end x of the line
      * @param y2 end y of the line
      */
-    public static void apply(WireBuffer buffer, float x1, float y1, float x2, float y2) {
+    public static void apply(@NonNull WireBuffer buffer, float x1, float y1, float x2, float y2) {
         write(buffer, OP_CODE, x1, y1, x2, y2);
     }
 
     @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
         String x1 = "" + mX1;
         if (Float.isNaN(mX1Value)) {
             x1 = "[" + Utils.idFromNan(mX1Value) + " = " + mX1 + "]";
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 5d498e8..aa5116e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+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;
@@ -28,7 +30,7 @@
     private static final int OP_CODE = Operations.DRAW_OVAL;
     private static final String CLASS_NAME = "DrawOval";
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m = DrawOval::new;
         read(m, buffer, operations);
     }
@@ -37,11 +39,12 @@
         return OP_CODE;
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description("Draw the specified oval")
                 .field(DocumentedOperation.FLOAT, "left", "The left side of the oval")
@@ -51,12 +54,12 @@
     }
 
     @Override
-    protected void write(WireBuffer buffer, float v1, float v2, float v3, float v4) {
+    protected void write(@NonNull WireBuffer buffer, float v1, float v2, float v3, float v4) {
         apply(buffer, v1, v2, v3, v4);
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mX1, mY1, mX2, mY2);
     }
 
@@ -66,7 +69,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.drawOval(mX1, mY1, mX2, mY2);
     }
 
@@ -79,7 +82,7 @@
      * @param x2 end x of the DrawOval
      * @param y2 end y of the DrawOval
      */
-    public static void apply(WireBuffer buffer, float x1, float y1, float x2, float y2) {
+    public static void apply(@NonNull WireBuffer buffer, float x1, float y1, float x2, float y2) {
         write(buffer, OP_CODE, x1, y1, x2, y2);
     }
 }
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 ccbf3d9..d35094b 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
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+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;
@@ -38,21 +40,23 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mId);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "DrawPath " + "[" + mId + "]" + ", " + mStart + ", " + mEnd;
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
         DrawPath op = new DrawPath(id);
         operations.add(op);
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -61,19 +65,19 @@
         return Operations.DRAW_PATH;
     }
 
-    public static void apply(WireBuffer buffer, int id) {
+    public static void apply(@NonNull WireBuffer buffer, int id) {
         buffer.start(Operations.DRAW_PATH);
         buffer.writeInt(id);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Draw Operations", OP_CODE, CLASS_NAME)
                 .description("Draw a bitmap using integer coordinates")
                 .field(DocumentedOperation.INT, "id", "id of path");
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.drawPath(mId, mStart, mEnd);
     }
 }
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 644011b..db7633c 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+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;
@@ -29,7 +31,7 @@
     private static final int OP_CODE = Operations.DRAW_RECT;
     private static final String CLASS_NAME = "DrawRect";
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m = DrawRect::new;
         read(m, buffer, operations);
     }
@@ -38,11 +40,12 @@
         return OP_CODE;
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description("Draw the specified rectangle")
                 .field(DocumentedOperation.FLOAT, "left", "The left side of the rectangle")
@@ -52,7 +55,7 @@
     }
 
     @Override
-    protected void write(WireBuffer buffer, float v1, float v2, float v3, float v4) {
+    protected void write(@NonNull WireBuffer buffer, float v1, float v2, float v3, float v4) {
         apply(buffer, v1, v2, v3, v4);
     }
 
@@ -62,7 +65,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.drawRect(mX1, mY1, mX2, mY2);
     }
 
@@ -75,7 +78,7 @@
      * @param x2 right x of the rect
      * @param y2 bottom y of the rect
      */
-    public static void apply(WireBuffer buffer, float x1, float y1, float x2, float y2) {
+    public static void apply(@NonNull WireBuffer buffer, float x1, float y1, float x2, float y2) {
         write(buffer, OP_CODE, x1, y1, x2, y2);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java
index 64a3b28..c306e2b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+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;
@@ -29,7 +31,7 @@
     private static final int OP_CODE = Operations.DRAW_ROUND_RECT;
     private static final String CLASS_NAME = "DrawRoundRect";
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m = DrawRoundRect::new;
         read(m, buffer, operations);
     }
@@ -50,7 +52,13 @@
      * @param v6 The y-radius of the oval used to round the corners
      */
     public static void apply(
-            WireBuffer buffer, float v1, float v2, float v3, float v4, float v5, float v6) {
+            @NonNull WireBuffer buffer,
+            float v1,
+            float v2,
+            float v3,
+            float v4,
+            float v5,
+            float v6) {
         buffer.start(OP_CODE);
         buffer.writeFloat(v1);
         buffer.writeFloat(v2);
@@ -62,11 +70,17 @@
 
     @Override
     protected void write(
-            WireBuffer buffer, float v1, float v2, float v3, float v4, float v5, float v6) {
+            @NonNull WireBuffer buffer,
+            float v1,
+            float v2,
+            float v3,
+            float v4,
+            float v5,
+            float v6) {
         apply(buffer, v1, v2, v3, v4, v5, v6);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description("Draw the specified round-rect")
                 .field(DocumentedOperation.FLOAT, "left", "The left side of the rect")
@@ -89,7 +103,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.drawRoundRect(mV1, mV2, mV3, mV4, mV5, mV6);
     }
 }
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 3cb1916..3b60df7 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
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+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;
@@ -28,7 +30,7 @@
     public static final int OP_CODE = Operations.DRAW_SECTOR;
     private static final String CLASS_NAME = "DrawSector";
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m = DrawSector::new;
         read(m, buffer, operations);
     }
@@ -49,7 +51,13 @@
      * @param v6 Sweep angle (in degrees) measured clockwise
      */
     public static void apply(
-            WireBuffer buffer, float v1, float v2, float v3, float v4, float v5, float v6) {
+            @NonNull WireBuffer buffer,
+            float v1,
+            float v2,
+            float v3,
+            float v4,
+            float v5,
+            float v6) {
         buffer.start(OP_CODE);
         buffer.writeFloat(v1);
         buffer.writeFloat(v2);
@@ -61,11 +69,17 @@
 
     @Override
     protected void write(
-            WireBuffer buffer, float v1, float v2, float v3, float v4, float v5, float v6) {
+            @NonNull WireBuffer buffer,
+            float v1,
+            float v2,
+            float v3,
+            float v4,
+            float v5,
+            float v6) {
         apply(buffer, v1, v2, v3, v4, v5, v6);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description(
                         "Draw the specified sector (pie shape)"
@@ -90,7 +104,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.drawSector(mV1, mV2, mV3, mV4, mV5, mV6);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java
index bcb7852..9c587ab 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
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.operations.Utils.floatToString;
 
+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;
@@ -64,13 +66,13 @@
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         mOutX = Float.isNaN(mX) ? context.getFloat(Utils.idFromNan(mX)) : mX;
         mOutY = Float.isNaN(mY) ? context.getFloat(Utils.idFromNan(mY)) : mY;
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         if (Float.isNaN(mX)) {
             context.listensTo(Utils.idFromNan(mX), this);
         }
@@ -80,10 +82,11 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mTextID, mStart, mEnd, mContextStart, mContextEnd, mX, mY, mRtl);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "DrawTextRun ["
@@ -98,7 +101,7 @@
                 + floatToString(mY, mOutY);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int text = buffer.readInt();
         int start = buffer.readInt();
         int end = buffer.readInt();
@@ -112,6 +115,7 @@
         operations.add(op);
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -134,7 +138,7 @@
      * @param rtl is it Right to Left text
      */
     public static void apply(
-            WireBuffer buffer,
+            @NonNull WireBuffer buffer,
             int textID,
             int start,
             int end,
@@ -154,7 +158,7 @@
         buffer.writeBoolean(rtl);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Draw Operations", id(), CLASS_NAME)
                 .description("Draw a run of text, all in a single direction")
                 .field(DocumentedOperation.INT, "textId", "id of bitmap")
@@ -177,7 +181,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.drawTextRun(mTextID, mStart, mEnd, mContextStart, mContextEnd, mOutX, mOutY, mRtl);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java
index 95a8766..8b70181 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
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -57,7 +59,7 @@
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         mOutX = Float.isNaN(mX) ? context.getFloat(Utils.idFromNan(mX)) : mX;
         mOutY = Float.isNaN(mY) ? context.getFloat(Utils.idFromNan(mY)) : mY;
         mOutPanX = Float.isNaN(mPanX) ? context.getFloat(Utils.idFromNan(mPanX)) : mPanX;
@@ -65,7 +67,7 @@
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         if (Float.isNaN(mX)) {
             context.listensTo(Utils.idFromNan(mX), this);
         }
@@ -81,10 +83,11 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mTextID, mX, mY, mPanX, mPanY, mFlags);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "DrawTextAnchored ["
@@ -108,7 +111,7 @@
         return Float.toString(v);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int textID = buffer.readInt();
         float x = buffer.readFloat();
         float y = buffer.readFloat();
@@ -121,6 +124,7 @@
         operations.add(op);
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -141,7 +145,13 @@
      * @param flags Change the behaviour
      */
     public static void apply(
-            WireBuffer buffer, int textID, float x, float y, float panX, float panY, int flags) {
+            @NonNull WireBuffer buffer,
+            int textID,
+            float x,
+            float y,
+            float panX,
+            float panY,
+            int flags) {
         buffer.start(OP_CODE);
         buffer.writeInt(textID);
         buffer.writeFloat(x);
@@ -151,7 +161,7 @@
         buffer.writeInt(flags);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Draw Operations", OP_CODE, CLASS_NAME)
                 .description("Draw text centered about an anchor point")
                 .field(DocumentedOperation.INT, "textId", "id of bitmap")
@@ -168,7 +178,7 @@
                 .field(DocumentedOperation.INT, "flags", "Change the behaviour");
     }
 
-    float[] mBounds = new float[4];
+    @NonNull float[] mBounds = new float[4];
 
     private float getHorizontalOffset() {
         // TODO scale  TextSize / BaseTextSize;
@@ -188,7 +198,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         int flags =
                 ((mFlags & ANCHOR_MONOSPACE_MEASURE) != 0)
                         ? PaintContext.TEXT_MEASURE_MONOSPACE_WIDTH
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 aefd6f3..e90122b 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
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+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;
@@ -46,7 +48,7 @@
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         mOutHOffset =
                 Float.isNaN(mHOffset) ? context.getFloat(Utils.idFromNan(mHOffset)) : mHOffset;
         mOutVOffset =
@@ -54,7 +56,7 @@
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         if (Float.isNaN(mHOffset)) {
             context.listensTo(Utils.idFromNan(mHOffset), this);
         }
@@ -64,10 +66,11 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mTextId, mPathId, mHOffset, mVOffset);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "DrawTextOnPath ["
@@ -80,7 +83,7 @@
                 + Utils.floatToString(mVOffset, mOutVOffset);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int textId = buffer.readInt();
         int pathId = buffer.readInt();
         float vOffset = buffer.readFloat();
@@ -89,6 +92,7 @@
         operations.add(op);
     }
 
+    @NonNull
     public static String name() {
         return "DrawTextOnPath";
     }
@@ -98,7 +102,7 @@
     }
 
     public static void apply(
-            WireBuffer buffer, int textId, int pathId, float hOffset, float vOffset) {
+            @NonNull WireBuffer buffer, int textId, int pathId, float hOffset, float vOffset) {
         buffer.start(OP_CODE);
         buffer.writeInt(textId);
         buffer.writeInt(pathId);
@@ -106,7 +110,7 @@
         buffer.writeFloat(hOffset);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Draw Operations", OP_CODE, CLASS_NAME)
                 .description("Draw text along path object")
                 .field(DocumentedOperation.INT, "textId", "id of the text")
@@ -116,7 +120,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.drawTextOnPath(mTextId, mPathId, mOutHOffset, mOutVOffset);
     }
 }
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 b6d45d9..0aaaf42 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
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.operations.Utils.floatToString;
 
+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;
@@ -50,14 +52,14 @@
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         mOutTween = Float.isNaN(mTween) ? context.getFloat(Utils.idFromNan(mTween)) : mTween;
         mOutStart = Float.isNaN(mStart) ? context.getFloat(Utils.idFromNan(mStart)) : mStart;
         mOutStop = Float.isNaN(mStop) ? context.getFloat(Utils.idFromNan(mStop)) : mStop;
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         if (Float.isNaN(mTween)) {
             context.listensTo(Utils.idFromNan(mTween), this);
         }
@@ -70,10 +72,11 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mPath1Id, mPath2Id, mTween, mStart, mStop);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "DrawTweenPath "
@@ -89,7 +92,7 @@
                 + floatToString(mStop, mOutStop);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int path1Id = buffer.readInt();
         int path2Id = buffer.readInt();
         float tween = buffer.readFloat();
@@ -99,6 +102,7 @@
         operations.add(op);
     }
 
+    @NonNull
     public static String name() {
         return "DrawTweenPath";
     }
@@ -108,7 +112,12 @@
     }
 
     public static void apply(
-            WireBuffer buffer, int path1Id, int path2Id, float tween, float start, float stop) {
+            @NonNull WireBuffer buffer,
+            int path1Id,
+            int path2Id,
+            float tween,
+            float start,
+            float stop) {
         buffer.start(OP_CODE);
         buffer.writeInt(path1Id);
         buffer.writeInt(path2Id);
@@ -117,7 +126,7 @@
         buffer.writeFloat(stop);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Draw Operations", OP_CODE, CLASS_NAME)
                 .description("Draw text along path object")
                 .field(DocumentedOperation.INT, "pathId1", "id of path 1")
@@ -128,7 +137,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.drawTweenPath(mPath1Id, mPath2Id, mOutTween, mOutStart, mOutStop);
     }
 }
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 765e150..89390ac 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
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -39,15 +41,17 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mTextId, mValue);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "FloatConstant[" + mTextId + "] = " + mValue;
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -63,20 +67,20 @@
      * @param id the id
      * @param value the value of the float
      */
-    public static void apply(WireBuffer buffer, int id, float value) {
+    public static void apply(@NonNull WireBuffer buffer, int id, float value) {
         buffer.start(OP_CODE);
         buffer.writeInt(id);
         buffer.writeFloat(value);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int textId = buffer.readInt();
 
         float value = buffer.readFloat();
         operations.add(new FloatConstant(textId, value));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, CLASS_NAME)
                 .description("A float and its associated id")
                 .field(DocumentedOperation.INT, "id", "id of float")
@@ -84,10 +88,11 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         context.loadFloat(mTextId, mValue);
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java
index d717933..e1c6c25 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
@@ -20,6 +20,9 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.SHORT;
 
+import android.annotation.NonNull;
+import 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;
@@ -48,7 +51,7 @@
     public float[] mPreCalcValue;
     private float mLastChange = Float.NaN;
     private float mLastCalculatedValue = Float.NaN;
-    AnimatedFloatExpression mExp = new AnimatedFloatExpression();
+    @NonNull AnimatedFloatExpression mExp = new AnimatedFloatExpression();
     public static final int MAX_EXPRESSION_SIZE = 32;
 
     public FloatExpression(int id, float[] value, float[] animation) {
@@ -61,7 +64,7 @@
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         if (mPreCalcValue == null || mPreCalcValue.length != mSrcValue.length) {
             mPreCalcValue = new float[mSrcValue.length];
         }
@@ -107,7 +110,7 @@
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         for (float v : mSrcValue) {
             if (Float.isNaN(v)
                     && !AnimatedFloatExpression.isMathOperator(v)
@@ -118,7 +121,7 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         updateVariables(context);
         float t = context.getAnimationTime();
         if (Float.isNaN(mLastChange)) {
@@ -135,10 +138,11 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mId, mSrcValue, mSrcAnimation);
     }
 
+    @NonNull
     @Override
     public String toString() {
         String[] labels = new String[mSrcValue.length];
@@ -161,6 +165,7 @@
                 + ")";
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -177,7 +182,11 @@
      * @param value the float expression array
      * @param animation the animation expression array
      */
-    public static void apply(WireBuffer buffer, int id, float[] value, float[] animation) {
+    public static void apply(
+            @NonNull WireBuffer buffer,
+            int id,
+            @NonNull float[] value,
+            @Nullable float[] animation) {
         buffer.start(OP_CODE);
         buffer.writeInt(id);
 
@@ -197,7 +206,7 @@
         }
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
         int len = buffer.readInt();
         int valueLen = len & 0xFFFF;
@@ -222,7 +231,7 @@
         operations.add(new FloatExpression(id, values, animation));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, CLASS_NAME)
                 .description("A Float expression")
                 .field(DocumentedOperation.INT, "id", "The id of the Color")
@@ -245,6 +254,7 @@
                 .field(FLOAT, "wrapValue", "> [Wrap value] ");
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java
index 4f8516f..1979bc5 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
@@ -18,6 +18,8 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.LONG;
 
+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.RemoteComposeOperation;
@@ -80,10 +82,11 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mWidth, mHeight, mDensity, mCapabilities);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "HEADER v"
@@ -102,15 +105,17 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         context.header(mMajorVersion, mMinorVersion, mPatchVersion, mWidth, mHeight, mCapabilities);
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return toString();
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -120,7 +125,7 @@
     }
 
     public static void apply(
-            WireBuffer buffer, int width, int height, float density, long capabilities) {
+            @NonNull WireBuffer buffer, int width, int height, float density, long capabilities) {
         buffer.start(OP_CODE);
         buffer.writeInt(MAJOR_VERSION); // major version number of the protocol
         buffer.writeInt(MINOR_VERSION); // minor version number of the protocol
@@ -131,7 +136,7 @@
         buffer.writeLong(capabilities);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int majorVersion = buffer.readInt();
         int minorVersion = buffer.readInt();
         int patchVersion = buffer.readInt();
@@ -152,7 +157,7 @@
         operations.add(header);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Protocol Operations", OP_CODE, CLASS_NAME)
                 .description(
                         "Document metadata, containing the version,"
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 c9a8508..6375f00 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
@@ -18,6 +18,8 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT_ARRAY;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -45,7 +47,7 @@
     public int[] mPreCalcValue;
     private float mLastChange = Float.NaN;
     public static final int MAX_SIZE = 320;
-    IntegerExpressionEvaluator mExp = new IntegerExpressionEvaluator();
+    @NonNull IntegerExpressionEvaluator mExp = new IntegerExpressionEvaluator();
 
     public IntegerExpression(int id, int mask, int[] value) {
         this.mId = id;
@@ -54,7 +56,7 @@
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         if (mPreCalcValue == null || mPreCalcValue.length != mSrcValue.length) {
             mPreCalcValue = new int[mSrcValue.length];
         }
@@ -70,7 +72,7 @@
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         for (int i = 0; i < mSrcValue.length; i++) {
             if (isId(mMask, i, mSrcValue[i])) {
                 context.listensTo(mSrcValue[i], this);
@@ -79,7 +81,7 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         updateVariables(context);
         float t = context.getAnimationTime();
         if (Float.isNaN(mLastChange)) {
@@ -95,7 +97,7 @@
      * @param context current context
      * @return the resulting value
      */
-    public int evaluate(RemoteContext context) {
+    public int evaluate(@NonNull RemoteContext context) {
         updateVariables(context);
         float t = context.getAnimationTime();
         if (Float.isNaN(mLastChange)) {
@@ -105,10 +107,11 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mId, mMask, mSrcValue);
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuilder s = new StringBuilder();
@@ -132,6 +135,7 @@
         return "IntegerExpression[" + mId + "] = (" + s + ")";
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -148,7 +152,7 @@
      * @param mask the mask bits of ints & operators or variables
      * @param value array of integers to be evaluated
      */
-    public static void apply(WireBuffer buffer, int id, int mask, int[] value) {
+    public static void apply(@NonNull WireBuffer buffer, int id, int mask, @NonNull int[] value) {
         buffer.start(OP_CODE);
         buffer.writeInt(id);
         buffer.writeInt(mask);
@@ -158,7 +162,7 @@
         }
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
         int mask = buffer.readInt();
         int len = buffer.readInt();
@@ -173,7 +177,7 @@
         operations.add(new IntegerExpression(id, mask, values));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Data Operations", OP_CODE, CLASS_NAME)
                 .description("Expression that computes an integer")
                 .field(DocumentedOperation.INT, "id", "id of integer")
@@ -182,6 +186,7 @@
                 .field(INT_ARRAY, "values", "length", "Array of ints");
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java
index 04f8a50..6a620e5 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
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+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;
@@ -31,20 +33,22 @@
     public MatrixRestore() {}
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(WireBuffer buffer, @NonNull List<Operation> operations) {
         MatrixRestore op = new MatrixRestore();
         operations.add(op);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "MatrixRestore";
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -53,17 +57,17 @@
         return OP_CODE;
     }
 
-    public static void apply(WireBuffer buffer) {
+    public static void apply(@NonNull WireBuffer buffer) {
         buffer.start(OP_CODE);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description("Restore the matrix and clip");
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.matrixRestore();
     }
 }
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 df10f32..438a2aa 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+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;
@@ -28,9 +30,10 @@
     public static final int OP_CODE = Operations.MATRIX_ROTATE;
     private static final String CLASS_NAME = "MatrixRotate";
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m =
                 new Maker() {
+                    @NonNull
                     @Override
                     public DrawBase3 create(float v1, float v2, float v3) {
                         return new MatrixRotate(v1, v2, v3);
@@ -43,11 +46,12 @@
         return OP_CODE;
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description("apply rotation to matrix")
                 .field(DocumentedOperation.FLOAT, "rotate", "Angle to rotate")
@@ -56,7 +60,7 @@
     }
 
     @Override
-    protected void write(WireBuffer buffer, float v1, float v2, float v3) {
+    protected void write(@NonNull WireBuffer buffer, float v1, float v2, float v3) {
         apply(buffer, v1, v2, v3);
     }
 
@@ -66,7 +70,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.matrixRotate(mV1, mV2, mV3);
     }
 
@@ -78,7 +82,7 @@
      * @param y1 X Pivot point
      * @param x2 Y Pivot point
      */
-    public static void apply(WireBuffer buffer, float x1, float y1, float x2) {
+    public static void apply(@NonNull WireBuffer buffer, float x1, float y1, float x2) {
         buffer.start(OP_CODE);
         buffer.writeFloat(x1);
         buffer.writeFloat(y1);
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 67612c7..1880b19 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
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+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;
@@ -29,20 +31,22 @@
     private static final String CLASS_NAME = "MatrixSave";
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "MatrixSave;";
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(WireBuffer buffer, @NonNull List<Operation> operations) {
         MatrixSave op = new MatrixSave();
         operations.add(op);
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -51,17 +55,17 @@
         return OP_CODE;
     }
 
-    public static void apply(WireBuffer buffer) {
+    public static void apply(@NonNull WireBuffer buffer) {
         buffer.start(Operations.MATRIX_SAVE);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description("Save the matrix and clip to a stack");
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.matrixSave();
     }
 }
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 26c898a..6304584 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+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;
@@ -28,7 +30,7 @@
     public static final int OP_CODE = Operations.MATRIX_SCALE;
     public static final String CLASS_NAME = "MatrixScale";
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m = MatrixScale::new;
         read(m, buffer, operations);
     }
@@ -37,16 +39,17 @@
         return OP_CODE;
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
     @Override
-    protected void write(WireBuffer buffer, float v1, float v2, float v3, float v4) {
+    protected void write(@NonNull WireBuffer buffer, float v1, float v2, float v3, float v4) {
         apply(buffer, v1, v2, v3, v4);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description("Draw the specified Oval")
                 .field(DocumentedOperation.FLOAT, "scaleX", "The amount to scale in X")
@@ -61,7 +64,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.matrixScale(mX1, mY1, mX2, mY2);
     }
 
@@ -74,7 +77,7 @@
      * @param x2 end x of the DrawOval
      * @param y2 end y of the DrawOval
      */
-    public static void apply(WireBuffer buffer, float x1, float y1, float x2, float y2) {
+    public static void apply(@NonNull WireBuffer buffer, float x1, float y1, float x2, float y2) {
         write(buffer, OP_CODE, x1, y1, x2, y2);
     }
 }
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 d641178..675cf0d 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
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -29,7 +31,7 @@
     public static final int OP_CODE = Operations.MATRIX_SKEW;
     public static final String CLASS_NAME = "MatrixSkew";
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m = MatrixSkew::new;
         read(m, buffer, operations);
     }
@@ -38,16 +40,17 @@
         return OP_CODE;
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
     @Override
-    protected void write(WireBuffer buffer, float v1, float v2) {
+    protected void write(@NonNull WireBuffer buffer, float v1, float v2) {
         apply(buffer, v1, v2);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description("Current matrix with the specified skew.")
                 .field(FLOAT, "skewX", "The amount to skew in X")
@@ -60,7 +63,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.matrixSkew(mV1, mV2);
     }
 
@@ -71,7 +74,7 @@
      * @param x1 start x of DrawOval
      * @param y1 start y of the DrawOval
      */
-    public static void apply(WireBuffer buffer, float x1, float y1) {
+    public static void apply(@NonNull WireBuffer buffer, float x1, float y1) {
         write(buffer, OP_CODE, x1, y1);
     }
 }
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 e008292..b0a7d35 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+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;
@@ -28,7 +30,7 @@
     public static final int OP_CODE = Operations.MATRIX_TRANSLATE;
     public static final String CLASS_NAME = "MatrixTranslate";
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m = MatrixTranslate::new;
         read(m, buffer, operations);
     }
@@ -37,16 +39,17 @@
         return OP_CODE;
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
     @Override
-    protected void write(WireBuffer buffer, float v1, float v2) {
+    protected void write(@NonNull WireBuffer buffer, float v1, float v2) {
         apply(buffer, v1, v2);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, "MatrixTranslate")
                 .description("Preconcat the current matrix with the specified translation")
                 .field(DocumentedOperation.FLOAT, "dx", "The distance to translate in X")
@@ -59,7 +62,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.matrixTranslate(mV1, mV2);
     }
 
@@ -70,7 +73,7 @@
      * @param x1 start x of DrawOval
      * @param y1 start y of the DrawOval
      */
-    public static void apply(WireBuffer buffer, float x1, float y1) {
+    public static void apply(@NonNull WireBuffer buffer, float x1, float y1) {
         write(buffer, OP_CODE, x1, y1);
     }
 }
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 fa6e271..6310521e 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
@@ -18,6 +18,8 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.UTF8;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -47,10 +49,11 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mVarId, mVarType, mVarName);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "VariableName["
@@ -61,6 +64,7 @@
                 + mVarType;
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -77,21 +81,22 @@
      * @param varType The type of variable
      * @param text String
      */
-    public static void apply(WireBuffer buffer, int varId, int varType, String text) {
+    public static void apply(
+            @NonNull WireBuffer buffer, int varId, int varType, @NonNull String text) {
         buffer.start(Operations.NAMED_VARIABLE);
         buffer.writeInt(varId);
         buffer.writeInt(varType);
         buffer.writeUTF8(text);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int varId = buffer.readInt();
         int varType = buffer.readInt();
         String text = buffer.readUTF8(MAX_STRING_SIZE);
         operations.add(new NamedVariable(varId, varType, text));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Data Operations", OP_CODE, CLASS_NAME)
                 .description("Add a string name for an ID")
                 .field(DocumentedOperation.INT, "varId", "id to label")
@@ -100,10 +105,11 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         context.loadVariableName(mVarName, mVarId, mVarType);
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java
index 095a010..527d5610 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
@@ -18,6 +18,8 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT_ARRAY;
 
+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;
@@ -33,31 +35,33 @@
 public class PaintData extends PaintOperation implements VariableSupport {
     private static final int OP_CODE = Operations.PAINT_VALUES;
     private static final String CLASS_NAME = "PaintData";
-    public PaintBundle mPaintData = new PaintBundle();
+    @NonNull public PaintBundle mPaintData = new PaintBundle();
     public static final int MAX_STRING_SIZE = 4000;
 
     public PaintData() {}
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         mPaintData.updateVariables(context);
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         mPaintData.registerVars(context, this);
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mPaintData);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "PaintData " + "\"" + mPaintData + "\"";
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -66,31 +70,32 @@
         return OP_CODE;
     }
 
-    public static void apply(WireBuffer buffer, PaintBundle paintBundle) {
+    public static void apply(@NonNull WireBuffer buffer, @NonNull PaintBundle paintBundle) {
         buffer.start(Operations.PAINT_VALUES);
         paintBundle.writeBundle(buffer);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         PaintData data = new PaintData();
         data.mPaintData.readBundle(buffer);
         operations.add(data);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Data Operations", OP_CODE, CLASS_NAME)
                 .description("Encode a Paint ")
                 .field(INT, "length", "id string")
                 .field(INT_ARRAY, "paint", "length", "path encoded as floats");
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.applyPaint(mPaintData);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java
index 13d5a49..06a1fec 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java
@@ -18,6 +18,9 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT_ARRAY;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -43,7 +46,7 @@
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         for (int i = 0; i < mFloatPath.length; i++) {
             float v = mFloatPath[i];
             if (Utils.isVariable(v)) {
@@ -55,7 +58,7 @@
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         for (float v : mFloatPath) {
             if (Float.isNaN(v)) {
                 context.listensTo(Utils.idFromNan(v), this);
@@ -64,15 +67,17 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mInstanceId, mOutputPath);
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return pathString(mFloatPath);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "PathData[" + mInstanceId + "] = " + "\"" + deepToString(" ") + "\"";
@@ -102,6 +107,7 @@
     public static final float CLOSE_NAN = Utils.asNan(CLOSE);
     public static final float DONE_NAN = Utils.asNan(DONE);
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -110,7 +116,7 @@
         return OP_CODE;
     }
 
-    public static void apply(WireBuffer buffer, int id, float[] data) {
+    public static void apply(@NonNull WireBuffer buffer, int id, @NonNull float[] data) {
         buffer.start(Operations.DATA_PATH);
         buffer.writeInt(id);
         buffer.writeInt(data.length);
@@ -119,7 +125,7 @@
         }
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int imageId = buffer.readInt();
         int len = buffer.readInt();
         float[] data = new float[len];
@@ -129,7 +135,7 @@
         operations.add(new PathData(imageId, data));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Data Operations", OP_CODE, CLASS_NAME)
                 .description("Encode a Path ")
                 .field(DocumentedOperation.INT, "id", "id string")
@@ -137,7 +143,8 @@
                 .field(FLOAT_ARRAY, "pathData", "length", "path encoded as floats");
     }
 
-    public static String pathString(float[] path) {
+    @NonNull
+    public static String pathString(@Nullable float[] path) {
         if (path == null) {
             return "null";
         }
@@ -186,7 +193,7 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         context.loadPathData(mInstanceId, mOutputPath);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java
index 4a8f532..6ff9ad7 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
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+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.RemoteComposeOperation;
@@ -172,10 +174,11 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mScroll, mAlignment, mSizing, mMode);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "ROOT_CONTENT_BEHAVIOR scroll: "
@@ -187,15 +190,17 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         context.setRootContentBehavior(mScroll, mAlignment, mSizing, mMode);
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return toString();
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -204,7 +209,8 @@
         return OP_CODE;
     }
 
-    public static void apply(WireBuffer buffer, int scroll, int alignment, int sizing, int mode) {
+    public static void apply(
+            @NonNull WireBuffer buffer, int scroll, int alignment, int sizing, int mode) {
         buffer.start(OP_CODE);
         buffer.writeInt(scroll);
         buffer.writeInt(alignment);
@@ -212,7 +218,7 @@
         buffer.writeInt(mode);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int scroll = buffer.readInt();
         int alignment = buffer.readInt();
         int sizing = buffer.readInt();
@@ -222,7 +228,7 @@
         operations.add(rootContentBehavior);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Protocol Operations", OP_CODE, CLASS_NAME)
                 .description("Describes the behaviour of the root")
                 .field(DocumentedOperation.INT, "scroll", "scroll")
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 bff9029..c2d62a7 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
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+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.RemoteComposeOperation;
@@ -41,25 +43,28 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mContentDescription);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "RootContentDescription " + mContentDescription;
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         context.setDocumentContentDescription(mContentDescription);
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return toString();
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -68,18 +73,18 @@
         return OP_CODE;
     }
 
-    public static void apply(WireBuffer buffer, int contentDescription) {
+    public static void apply(@NonNull WireBuffer buffer, int contentDescription) {
         buffer.start(Operations.ROOT_CONTENT_DESCRIPTION);
         buffer.writeInt(contentDescription);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int contentDescription = buffer.readInt();
         RootContentDescription header = new RootContentDescription(contentDescription);
         operations.add(header);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Protocol Operations", OP_CODE, CLASS_NAME)
                 .description("Content description of root")
                 .field(DocumentedOperation.INT, "id", "id of Int");
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 7ec7879..ae61c3a 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
@@ -22,6 +22,9 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.SHORT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.UTF8;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -43,17 +46,17 @@
     private static final String CLASS_NAME = "ShaderData";
     int mShaderTextId; // the actual text of a shader
     int mShaderID; // allows shaders to be referenced by number
-    HashMap<String, float[]> mUniformRawFloatMap = null;
-    HashMap<String, float[]> mUniformFloatMap = null;
-    HashMap<String, int[]> mUniformIntMap = null;
-    HashMap<String, Integer> mUniformBitmapMap = null;
+    @Nullable HashMap<String, float[]> mUniformRawFloatMap = null;
+    @Nullable HashMap<String, float[]> mUniformFloatMap = null;
+    @Nullable HashMap<String, int[]> mUniformIntMap = null;
+    @Nullable HashMap<String, Integer> mUniformBitmapMap = null;
 
     public ShaderData(
             int shaderID,
             int shaderTextId,
-            HashMap<String, float[]> floatMap,
-            HashMap<String, int[]> intMap,
-            HashMap<String, Integer> bitmapMap) {
+            @Nullable HashMap<String, float[]> floatMap,
+            @Nullable HashMap<String, int[]> intMap,
+            @Nullable HashMap<String, Integer> bitmapMap) {
         mShaderID = shaderID;
         mShaderTextId = shaderTextId;
         if (floatMap != null) {
@@ -89,6 +92,7 @@
      *
      * @return Names of all uniform floats or empty array
      */
+    @NonNull
     public String[] getUniformFloatNames() {
         if (mUniformFloatMap == null) return new String[0];
         return mUniformFloatMap.keySet().toArray(new String[0]);
@@ -109,6 +113,7 @@
      *
      * @return Name of all integer uniforms
      */
+    @NonNull
     public String[] getUniformIntegerNames() {
         if (mUniformIntMap == null) return new String[0];
         return mUniformIntMap.keySet().toArray(new String[0]);
@@ -129,6 +134,7 @@
      *
      * @return Name of all bitmap uniforms
      */
+    @NonNull
     public String[] getUniformBitmapNames() {
         if (mUniformBitmapMap == null) return new String[0];
         return mUniformBitmapMap.keySet().toArray(new String[0]);
@@ -145,7 +151,7 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(
                 buffer,
                 mShaderID,
@@ -155,13 +161,14 @@
                 mUniformBitmapMap);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "SHADER DATA " + mShaderID;
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         for (String name : mUniformRawFloatMap.keySet()) {
             float[] value = mUniformRawFloatMap.get(name);
             float[] out = null;
@@ -178,7 +185,7 @@
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         for (String name : mUniformRawFloatMap.keySet()) {
             float[] value = mUniformRawFloatMap.get(name);
             for (float v : value) {
@@ -189,6 +196,7 @@
         }
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -208,12 +216,12 @@
      * @param bitmapMap the map of bitmap uniforms
      */
     public static void apply(
-            WireBuffer buffer,
+            @NonNull WireBuffer buffer,
             int shaderID,
             int shaderTextId,
-            HashMap<String, float[]> floatMap,
-            HashMap<String, int[]> intMap,
-            HashMap<String, Integer> bitmapMap) {
+            @Nullable HashMap<String, float[]> floatMap,
+            @Nullable HashMap<String, int[]> intMap,
+            @Nullable HashMap<String, Integer> bitmapMap) {
         buffer.start(OP_CODE);
         buffer.writeInt(shaderID);
 
@@ -256,7 +264,7 @@
         }
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int shaderID = buffer.readInt();
         int shaderTextId = buffer.readInt();
         HashMap<String, float[]> floatMap = null;
@@ -308,7 +316,7 @@
         operations.add(new ShaderData(shaderID, shaderTextId, floatMap, intMap, bitmapMap));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Data Operations", OP_CODE, CLASS_NAME)
                 .description("Shader")
                 .field(DocumentedOperation.INT, "shaderID", "id of shader")
@@ -326,10 +334,11 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         context.loadShader(mShaderID, this);
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java
index 6383249..dbaef7e 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
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.UTF8;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -42,15 +44,17 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mTextId, mText);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "TextData[" + mTextId + "] = \"" + Utils.trimString(mText, 10) + "\"";
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -59,20 +63,20 @@
         return OP_CODE;
     }
 
-    public static void apply(WireBuffer buffer, int textId, String text) {
+    public static void apply(@NonNull WireBuffer buffer, int textId, @NonNull String text) {
         buffer.start(OP_CODE);
         buffer.writeInt(textId);
         buffer.writeUTF8(text);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int textId = buffer.readInt();
 
         String text = buffer.readUTF8(MAX_STRING_SIZE);
         operations.add(new TextData(textId, text));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Data Operations", OP_CODE, CLASS_NAME)
                 .description("Encode a string ")
                 .field(DocumentedOperation.INT, "id", "id string")
@@ -80,20 +84,22 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         context.loadText(mTextId, mText);
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
     }
 
     @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
         serializer.append(indent, getSerializedName() + "<" + mTextId + "> = \"" + mText + "\"");
     }
 
+    @NonNull
     private String getSerializedName() {
         return "DATA_TEXT";
     }
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 0d966d1..fb5087f 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
@@ -18,6 +18,8 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.SHORT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -87,10 +89,11 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mTextId, mValue, mDigitsBefore, mDigitsAfter, mFlags);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "TextFromFloat["
@@ -106,19 +109,20 @@
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         if (Float.isNaN(mValue)) {
             mOutValue = context.getFloat(Utils.idFromNan(mValue));
         }
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         if (Float.isNaN(mValue)) {
             context.listensTo(Utils.idFromNan(mValue), this);
         }
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -138,7 +142,7 @@
      * @param flags flags that control if and how to fill the empty spots
      */
     public static void apply(
-            WireBuffer buffer,
+            @NonNull WireBuffer buffer,
             int textId,
             float value,
             short digitsBefore,
@@ -151,7 +155,7 @@
         buffer.writeInt(flags);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int textId = buffer.readInt();
         float value = buffer.readFloat();
         int tmp = buffer.readInt();
@@ -162,7 +166,7 @@
         operations.add(new TextFromFloat(textId, value, pre, post, flags));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, CLASS_NAME)
                 .description("Draw text along path object")
                 .field(DocumentedOperation.INT, "textId", "id of the text generated")
@@ -173,12 +177,13 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         float v = mOutValue;
         String s = StringUtils.floatToString(v, mDigitsBefore, mDigitsAfter, mPre, mAfter);
         context.loadText(mTextId, s);
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java
index b04d698..2129edd 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
@@ -18,6 +18,8 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -48,10 +50,11 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mTextId, mDataSetId, mIndex);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "TextLookup["
@@ -63,19 +66,20 @@
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         if (Float.isNaN(mIndex)) {
             mOutIndex = context.getFloat(Utils.idFromNan(mIndex));
         }
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         if (Float.isNaN(mIndex)) {
             context.listensTo(Utils.idFromNan(mIndex), this);
         }
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -92,21 +96,21 @@
      * @param dataSet float pointer to the array/list to turn int a string
      * @param index index of element to return
      */
-    public static void apply(WireBuffer buffer, int textId, int dataSet, float index) {
+    public static void apply(@NonNull WireBuffer buffer, int textId, int dataSet, float index) {
         buffer.start(OP_CODE);
         buffer.writeInt(textId);
         buffer.writeInt(dataSet);
         buffer.writeFloat(index);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int textId = buffer.readInt();
         int dataSetId = buffer.readInt();
         float index = buffer.readFloat();
         operations.add(new TextLookup(textId, dataSetId, index));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, CLASS_NAME)
                 .description("Look an array and turn into a text object")
                 .field(INT, "textId", "id of the text generated")
@@ -115,11 +119,12 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         int id = context.getCollectionsAccess().getId(mDataSetId, (int) mOutIndex);
         context.loadText(mTextId, context.getText(id));
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java
index 171bea2..ea550cb 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
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -45,10 +47,11 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mTextId, mDataSetId, mIndex);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "TextLookupInt["
@@ -60,15 +63,16 @@
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         mOutIndex = context.getInteger(mIndex);
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         context.listensTo(mIndex, this);
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -85,21 +89,21 @@
      * @param dataSet float pointer to the array/list to turn int a string
      * @param indexId index of element to return
      */
-    public static void apply(WireBuffer buffer, int textId, int dataSet, int indexId) {
+    public static void apply(@NonNull WireBuffer buffer, int textId, int dataSet, int indexId) {
         buffer.start(OP_CODE);
         buffer.writeInt(textId);
         buffer.writeInt(dataSet);
         buffer.writeInt(indexId);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int textId = buffer.readInt();
         int dataSetId = buffer.readInt();
         int indexId = buffer.readInt();
         operations.add(new TextLookupInt(textId, dataSetId, indexId));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, CLASS_NAME)
                 .description("Look up an array and turn into a text object")
                 .field(DocumentedOperation.INT, "textId", "id of the text generated")
@@ -108,11 +112,12 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         int id = context.getCollectionsAccess().getId(mDataSetId, (int) mOutIndex);
         context.loadText(mTextId, context.getText(id));
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java
index 78cc674..fa18b4d 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
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -41,15 +43,17 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mTextId, mSrcId1, mSrcId2);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "TextMerge[" + mTextId + "] = [" + mSrcId1 + " ] + [ " + mSrcId2 + "]";
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -66,14 +70,14 @@
      * @param srcId1 source text 1
      * @param srcId2 source text 2
      */
-    public static void apply(WireBuffer buffer, int textId, int srcId1, int srcId2) {
+    public static void apply(@NonNull WireBuffer buffer, int textId, int srcId1, int srcId2) {
         buffer.start(OP_CODE);
         buffer.writeInt(textId);
         buffer.writeInt(srcId1);
         buffer.writeInt(srcId2);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int textId = buffer.readInt();
         int srcId1 = buffer.readInt();
         int srcId2 = buffer.readInt();
@@ -81,7 +85,7 @@
         operations.add(new TextMerge(textId, srcId1, srcId2));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Data Operations", OP_CODE, CLASS_NAME)
                 .description("Merge two string into one")
                 .field(DocumentedOperation.INT, "textId", "id of the text")
@@ -90,12 +94,13 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         String str1 = context.getText(mSrcId1);
         String str2 = context.getText(mSrcId2);
         context.loadText(mTextId, str1 + str2);
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java
index 845f25d..1e90ab1 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
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteComposeOperation;
@@ -49,25 +51,28 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mTheme);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "SET_THEME " + mTheme;
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         context.setTheme(mTheme);
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -76,17 +81,17 @@
         return OP_CODE;
     }
 
-    public static void apply(WireBuffer buffer, int theme) {
+    public static void apply(@NonNull WireBuffer buffer, int theme) {
         buffer.start(OP_CODE);
         buffer.writeInt(theme);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int theme = buffer.readInt();
         operations.add(new Theme(theme));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Protocol Operations", OP_CODE, CLASS_NAME)
                 .description("Set a theme")
                 .field(INT, "THEME", "theme id")
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
new file mode 100644
index 0000000..b25a7f6
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java
@@ -0,0 +1,599 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations;
+
+import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
+import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT_ARRAY;
+import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
+import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.SHORT;
+
+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.TouchListener;
+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.layout.Component;
+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;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Operation to deal with Touch handling (typically on canvas) This support handling of many typical
+ * touch behaviours. Including animating to Notched, positions. and tweaking the dynamics of the
+ * animation.
+ */
+public class TouchExpression implements Operation, VariableSupport, TouchListener {
+    private static final int OP_CODE = Operations.TOUCH_EXPRESSION;
+    private static final String CLASS_NAME = "TouchExpression";
+    private float mDefValue;
+    private float mOutDefValue;
+    public int mId;
+    public float[] mSrcExp;
+    int mMode = 1; // 0 = delta, 1 = absolute
+    float mMax = 1;
+    float mMin = 1;
+    float mOutMax = 1;
+    float mOutMin = 1;
+    float mValue = 0;
+    boolean mUnmodified = true;
+    public float[] mPreCalcValue;
+    private float mLastChange = Float.NaN;
+    private float mLastCalculatedValue = Float.NaN;
+    AnimatedFloatExpression mExp = new AnimatedFloatExpression();
+    public static final int MAX_EXPRESSION_SIZE = 32;
+    private VelocityEasing mEasyTouch = new VelocityEasing();
+    private boolean mEasingToStop = false;
+    private float mTouchUpTime = 0;
+    private float mCurrentValue = Float.NaN;
+    private boolean mTouchDown = false;
+    float mMaxTime = 1;
+    float mMaxAcceleration = 5;
+    float mMaxVelocity = 7;
+    int mStopMode = 0;
+    boolean mWrapMode = false;
+    float[] mNotches;
+    float[] mStopSpec;
+    int mTouchEffects;
+    float mVelocityId;
+
+    public static final int STOP_GENTLY = 0;
+    public static final int STOP_ENDS = 2;
+    public static final int STOP_INSTANTLY = 1;
+    public static final int STOP_NOTCHES_EVEN = 3;
+    public static final int STOP_NOTCHES_PERCENTS = 4;
+    public static final int STOP_NOTCHES_ABSOLUTE = 5;
+    public static final int STOP_ABSOLUTE_POS = 6;
+
+    public TouchExpression(
+            int id,
+            float[] exp,
+            float defValue,
+            float min,
+            float max,
+            int touchEffects,
+            float velocityId,
+            int stopMode,
+            float[] stopSpec,
+            float[] easingSpec) {
+        this.mId = id;
+        this.mSrcExp = exp;
+        mOutDefValue = mDefValue = defValue;
+        mMode = STOP_ABSOLUTE_POS == stopMode ? 1 : 0;
+        mOutMax = mMax = max;
+        mTouchEffects = touchEffects;
+        mVelocityId = velocityId;
+        if (Float.isNaN(min) && Utils.idFromNan(min) == 0) {
+            mWrapMode = true;
+        } else {
+            mOutMin = mMin = min;
+        }
+        mStopMode = stopMode;
+        mStopSpec = stopSpec;
+        if (easingSpec != null) {
+            Utils.log("easingSpec  " + Arrays.toString(easingSpec));
+            if (easingSpec.length >= 4) {
+                if (Float.floatToRawIntBits(easingSpec[0]) == 0) {
+                    Utils.log("easingSpec[2]  " + easingSpec[2]);
+                    mMaxTime = easingSpec[1];
+                    mMaxAcceleration = easingSpec[2];
+                    mMaxVelocity = easingSpec[3];
+                }
+            }
+        }
+    }
+
+    @Override
+    public void updateVariables(RemoteContext context) {
+
+        if (mPreCalcValue == null || mPreCalcValue.length != mSrcExp.length) {
+            mPreCalcValue = new float[mSrcExp.length];
+        }
+        if (Float.isNaN(mMax)) {
+            mOutMax = context.getFloat(Utils.idFromNan(mMax));
+        }
+        if (Float.isNaN(mMin)) {
+            mOutMin = context.getFloat(Utils.idFromNan(mMin));
+        }
+        if (Float.isNaN(mDefValue)) {
+            mOutDefValue = context.getFloat(Utils.idFromNan(mDefValue));
+        }
+
+        boolean value_changed = false;
+        for (int i = 0; i < mSrcExp.length; i++) {
+            float v = mSrcExp[i];
+            if (Float.isNaN(v)
+                    && !AnimatedFloatExpression.isMathOperator(v)
+                    && !NanMap.isDataVariable(v)) {
+                float newValue = context.getFloat(Utils.idFromNan(v));
+
+                mPreCalcValue[i] = newValue;
+
+            } else {
+                mPreCalcValue[i] = mSrcExp[i];
+            }
+        }
+        float v = mLastCalculatedValue;
+        if (value_changed) { // inputs changed check if output changed
+            v = mExp.eval(mPreCalcValue, mPreCalcValue.length);
+            if (v != mLastCalculatedValue) {
+                mLastChange = context.getAnimationTime();
+                mLastCalculatedValue = v;
+            } else {
+                value_changed = false;
+            }
+        }
+    }
+
+    @Override
+    public void registerListening(RemoteContext context) {
+        if (Float.isNaN(mMax)) {
+            context.listensTo(Utils.idFromNan(mMax), this);
+        }
+        if (Float.isNaN(mMin)) {
+            context.listensTo(Utils.idFromNan(mMin), this);
+        }
+        if (Float.isNaN(mDefValue)) {
+            context.listensTo(Utils.idFromNan(mDefValue), this);
+        }
+        context.addTouchListener(this);
+        for (float v : mSrcExp) {
+            if (Float.isNaN(v)
+                    && !AnimatedFloatExpression.isMathOperator(v)
+                    && !NanMap.isDataVariable(v)) {
+                context.listensTo(Utils.idFromNan(v), this);
+            }
+        }
+    }
+
+    private float wrap(float pos) {
+        if (!mWrapMode) {
+            return pos;
+        }
+        pos = pos % mOutMax;
+        if (pos < 0) {
+            pos += mOutMax;
+        }
+        return pos;
+    }
+
+    private float getStopPosition(float pos, float slope) {
+        float target = pos + slope / mMaxAcceleration;
+        if (mWrapMode) {
+            pos = wrap(pos);
+            target = pos += +slope / mMaxAcceleration;
+        } else {
+            target = Math.max(Math.min(target, mOutMax), mOutMin);
+        }
+        float[] positions = new float[mStopSpec.length];
+        float min = (mWrapMode) ? 0 : mOutMin;
+
+        switch (mStopMode) {
+            case STOP_ENDS:
+                return ((pos + slope) > (mOutMax + min) / 2) ? mOutMax : min;
+            case STOP_INSTANTLY:
+                return pos;
+            case STOP_NOTCHES_EVEN:
+                int evenSpacing = (int) mStopSpec[0];
+                float step = (mOutMax - min) / evenSpacing;
+
+                float notch = min + step * (int) (0.5f + (target - mOutMin) / step);
+
+                notch = Math.max(Math.min(notch, mOutMax), min);
+                return notch;
+            case STOP_NOTCHES_PERCENTS:
+                positions = new float[mStopSpec.length];
+                float minPos = min;
+                float minPosDist = Math.abs(mOutMin - target);
+                for (int i = 0; i < positions.length; i++) {
+                    float p = mOutMin + mStopSpec[i] * (mOutMax - mOutMin);
+                    float dist = Math.abs(p - target);
+                    if (minPosDist > dist) {
+                        minPosDist = dist;
+                        minPos = p;
+                    }
+                }
+                return minPos;
+            case STOP_NOTCHES_ABSOLUTE:
+                positions = mStopSpec;
+                minPos = mOutMin;
+                minPosDist = Math.abs(mOutMin - target);
+                for (int i = 0; i < positions.length; i++) {
+                    float dist = Math.abs(positions[i] - target);
+                    if (minPosDist > dist) {
+                        minPosDist = dist;
+                        minPos = positions[i];
+                    }
+                }
+                return minPos;
+            case STOP_GENTLY:
+            default:
+                return target;
+        }
+    }
+
+    void haptic(RemoteContext context) {
+        int touch = ((mTouchEffects) & 0xFF);
+        if ((mTouchEffects & (1 << 15)) != 0) {
+            touch = context.getInteger(mTouchEffects & 0x7FFF);
+        }
+
+        context.hapticEffect(touch);
+    }
+
+    float mLastValue = 0;
+
+    void crossNotchCheck(RemoteContext context) {
+        float prev = mLastValue;
+        float next = mCurrentValue;
+        mLastValue = next;
+
+        //        System.out.println(mStopMode + "    " + prev + "  -> " + next);
+        float min = (mWrapMode) ? 0 : mOutMin;
+        float max = mOutMax;
+
+        switch (mStopMode) {
+            case STOP_ENDS:
+                if (((min - prev) * (max - prev) < 0) ^ ((min - next) * (max - next)) < 0) {
+                    haptic(context);
+                }
+                break;
+            case STOP_INSTANTLY:
+                haptic(context);
+                break;
+            case STOP_NOTCHES_EVEN:
+                int evenSpacing = (int) mStopSpec[0];
+                float step = (max - min) / evenSpacing;
+                if ((int) ((prev - min) / step) != (int) ((next - min) / step)) {
+                    haptic(context);
+                }
+                break;
+            case STOP_NOTCHES_PERCENTS:
+                for (int i = 0; i < mStopSpec.length; i++) {
+                    float p = mOutMin + mStopSpec[i] * (mOutMax - mOutMin);
+                    if ((prev - p) * (next - p) < 0) {
+                        haptic(context);
+                    }
+                }
+                break;
+            case STOP_NOTCHES_ABSOLUTE:
+                for (int i = 0; i < mStopSpec.length; i++) {
+                    float p = mStopSpec[i];
+                    if ((prev - p) * (next - p) < 0) {
+                        haptic(context);
+                    }
+                }
+                break;
+            case STOP_GENTLY:
+        }
+    }
+
+    float mScrLeft, mScrRight, mScrTop, mScrBottom;
+
+    @Override
+    public void apply(RemoteContext context) {
+        Component comp = context.lastComponent;
+        if (comp != null) {
+            float x = comp.getX();
+            float y = comp.getY();
+            float w = comp.getWidth();
+            float h = comp.getHeight();
+            comp = comp.getParent();
+            while (comp != null) {
+                x += comp.getX();
+                y += comp.getY();
+                comp = comp.getParent();
+            }
+            mScrLeft = x;
+            mScrTop = y;
+            mScrRight = w + x;
+            mScrBottom = h + y;
+        }
+        updateVariables(context);
+        if (mUnmodified) {
+            mCurrentValue = mOutDefValue;
+
+            context.loadFloat(mId, wrap(mCurrentValue));
+            return;
+        }
+        if (mEasingToStop) {
+            float time = context.getAnimationTime() - mTouchUpTime;
+            float value = mEasyTouch.getPos(time);
+            mCurrentValue = value;
+            value = wrap(value);
+            context.loadFloat(mId, value);
+            if (mEasyTouch.getDuration() < time) {
+                mEasingToStop = false;
+            }
+            crossNotchCheck(context);
+            return;
+        }
+        if (mTouchDown) {
+            float value =
+                    mExp.eval(context.getCollectionsAccess(), mPreCalcValue, mPreCalcValue.length);
+            if (mMode == 0) {
+                value = mValueAtDown + (value - mDownTouchValue);
+            }
+            if (mWrapMode) {
+                value = wrap(value);
+            } else {
+                value = Math.min(Math.max(value, mOutMin), mOutMax);
+            }
+            mCurrentValue = value;
+        }
+        crossNotchCheck(context);
+        context.loadFloat(mId, wrap(mCurrentValue));
+    }
+
+    float mValueAtDown; // The currently "displayed" value at down
+    float mDownTouchValue; // The calculated value at down
+
+    @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;
+        }
+        mTouchDown = true;
+        mUnmodified = false;
+        if (mMode == 0) {
+            mValueAtDown = context.getFloat(mId);
+            mDownTouchValue =
+                    mExp.eval(context.getCollectionsAccess(), mPreCalcValue, mPreCalcValue.length);
+        }
+    }
+
+    @Override
+    public void touchUp(RemoteContext context, float x, float y, float dx, float dy) {
+        // calculate the slope (using small changes)
+        if (!mTouchDown) {
+            return;
+        }
+        mTouchDown = false;
+        float dt = 0.0001f;
+        if (mStopMode == STOP_INSTANTLY) {
+            return;
+        }
+        float v = mExp.eval(context.getCollectionsAccess(), mPreCalcValue, mPreCalcValue.length);
+        for (int i = 0; i < mSrcExp.length; i++) {
+            if (Float.isNaN(mSrcExp[i])) {
+                int id = Utils.idFromNan(mSrcExp[i]);
+                if (id == RemoteContext.ID_TOUCH_POS_X) {
+                    mPreCalcValue[i] = x + dx * dt;
+                } else if (id == RemoteContext.ID_TOUCH_POS_Y) {
+                    mPreCalcValue[i] = y + dy * dt;
+                }
+            }
+        }
+        float vdt = mExp.eval(context.getCollectionsAccess(), mPreCalcValue, mPreCalcValue.length);
+        float slope = (vdt - v) / dt; // the rate of change with respect to the dx,dy movement
+        float value = context.getFloat(mId);
+
+        mTouchUpTime = context.getAnimationTime();
+
+        float dest = getStopPosition(value, slope);
+        mEasyTouch.config(value, dest, slope, mMaxTime, mMaxAcceleration, mMaxVelocity, null);
+        mEasingToStop = true;
+    }
+
+    @Override
+    public void touchDrag(RemoteContext context, float x, float y) {
+        if (!mTouchDown) {
+            return;
+        }
+        apply(context);
+        context.getDocument().getRootLayoutComponent().needsRepaint();
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        apply(
+                buffer,
+                mId,
+                mValue,
+                mMin,
+                mMax,
+                mVelocityId,
+                mTouchEffects,
+                mSrcExp,
+                mStopMode,
+                mNotches,
+                null);
+    }
+
+    @Override
+    public String toString() {
+        String[] labels = new String[mSrcExp.length];
+        for (int i = 0; i < mSrcExp.length; i++) {
+            if (Float.isNaN(mSrcExp[i])) {
+                labels[i] = "[" + Utils.idStringFromNan(mSrcExp[i]) + "]";
+            }
+        }
+        if (mPreCalcValue == null) {
+            return CLASS_NAME
+                    + "["
+                    + mId
+                    + "] = ("
+                    + AnimatedFloatExpression.toString(mSrcExp, labels)
+                    + ")";
+        }
+        return CLASS_NAME
+                + "["
+                + mId
+                + "] = ("
+                + AnimatedFloatExpression.toString(mPreCalcValue, labels)
+                + ")";
+    }
+
+    // ===================== static ======================
+
+    public static String name() {
+        return CLASS_NAME;
+    }
+
+    public static int id() {
+        return OP_CODE;
+    }
+
+    /**
+     * Writes out the operation to the buffer
+     *
+     * @param buffer The buffer to write to
+     * @param id the id of the resulting float
+     * @param value the float expression array
+     */
+    public static void apply(
+            WireBuffer buffer,
+            int id,
+            float value,
+            float min,
+            float max,
+            float velocityId,
+            int touchEffects,
+            float[] exp,
+            int touchMode,
+            float[] touchSpec,
+            float[] easingSpec) {
+        buffer.start(OP_CODE);
+        buffer.writeInt(id);
+        buffer.writeFloat(value);
+        buffer.writeFloat(min);
+        buffer.writeFloat(max);
+        buffer.writeFloat(velocityId);
+        buffer.writeInt(touchEffects);
+        buffer.writeInt(exp.length);
+        for (float v : exp) {
+            buffer.writeFloat(v);
+        }
+        int len = 0;
+        if (touchSpec != null) {
+            len = touchSpec.length;
+        }
+        buffer.writeInt((touchMode << 16) | len);
+        for (int i = 0; i < len; i++) {
+            buffer.writeFloat(touchSpec[i]);
+        }
+
+        if (easingSpec != null) {
+            len = easingSpec.length;
+        } else {
+            len = 0;
+        }
+        buffer.writeInt(len);
+        for (int i = 0; i < len; i++) {
+            buffer.writeFloat(easingSpec[i]);
+        }
+    }
+
+    public static void read(WireBuffer buffer, List<Operation> operations) {
+        int id = buffer.readInt();
+        float startValue = buffer.readFloat();
+        float min = buffer.readFloat();
+        float max = buffer.readFloat();
+        float velocityId = buffer.readFloat(); // TODO future support
+        int touchEffects = buffer.readInt();
+        int len = buffer.readInt();
+        int valueLen = len & 0xFFFF;
+        if (valueLen > MAX_EXPRESSION_SIZE) {
+            throw new RuntimeException("Float expression to long");
+        }
+        float[] exp = new float[valueLen];
+        for (int i = 0; i < exp.length; i++) {
+            exp[i] = buffer.readFloat();
+        }
+        int stopLogic = buffer.readInt();
+        int stopLen = stopLogic & 0xFFFF;
+        int stopMode = stopLogic >> 16;
+
+        Utils.log("stopMode " + stopMode + " stopLen " + stopLen);
+        float[] stopsData = new float[stopLen];
+        for (int i = 0; i < stopsData.length; i++) {
+            stopsData[i] = buffer.readFloat();
+        }
+        int easingLen = buffer.readInt();
+
+        float[] easingData = new float[easingLen];
+        for (int i = 0; i < easingData.length; i++) {
+            easingData[i] = buffer.readFloat();
+        }
+
+        operations.add(
+                new TouchExpression(
+                        id,
+                        exp,
+                        startValue,
+                        min,
+                        max,
+                        touchEffects,
+                        velocityId,
+                        stopMode,
+                        stopsData,
+                        easingData));
+    }
+
+    public static void documentation(DocumentationBuilder doc) {
+        doc.operation("Expressions Operations", OP_CODE, CLASS_NAME)
+                .description("A Float expression")
+                .field(INT, "id", "The id of the Color")
+                .field(SHORT, "expression_length", "expression length")
+                .field(SHORT, "animation_length", "animation description length")
+                .field(
+                        FLOAT_ARRAY,
+                        "expression",
+                        "expression_length",
+                        "Sequence of Floats representing and expression")
+                .field(
+                        FLOAT_ARRAY,
+                        "AnimationSpec",
+                        "animation_length",
+                        "Sequence of Floats representing animation curve")
+                .field(FLOAT, "duration", "> time in sec")
+                .field(INT, "bits", "> WRAP|INITALVALUE | TYPE ")
+                .field(FLOAT_ARRAY, "spec", "> [SPEC PARAMETERS] ")
+                .field(FLOAT, "initialValue", "> [Initial value] ")
+                .field(FLOAT, "wrapValue", "> [Wrap value] ");
+    }
+
+    @Override
+    public String deepToString(String indent) {
+        return indent + toString();
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java
index 8ebb40c..03f7e05 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+import android.annotation.NonNull;
+
 /** Utilities to be used across all core operations */
 public class Utils {
     public static float asNan(int v) {
@@ -30,11 +32,13 @@
         return v - 0x100000000L;
     }
 
+    @NonNull
     public static String idStringFromNan(float value) {
         int b = Float.floatToRawIntBits(value) & 0x3FFFFF;
         return idString(b);
     }
 
+    @NonNull
     public static String idString(int b) {
         return (b > 0xFFFFF) ? "A_" + (b & 0xFFFFF) : "" + b;
     }
@@ -50,7 +54,8 @@
      * @param n
      * @return
      */
-    public static String trimString(String str, int n) {
+    @NonNull
+    public static String trimString(@NonNull String str, int n) {
         if (str.length() > n) {
             str = str.substring(0, n - 3) + "...";
         }
@@ -145,6 +150,7 @@
      * @param color
      * @return
      */
+    @NonNull
     public static String colorInt(int color) {
         String str = "000000000000" + Integer.toHexString(color);
         return "0x" + str.substring(str.length() - 8);
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
new file mode 100644
index 0000000..e789710
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/AnimatableValue.java
@@ -0,0 +1,80 @@
+/*
+ * 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;
+
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.operations.Utils;
+import com.android.internal.widget.remotecompose.core.operations.utilities.easing.FloatAnimation;
+import com.android.internal.widget.remotecompose.core.operations.utilities.easing.GeneralEasing;
+
+public class AnimatableValue {
+    boolean mIsVariable = false;
+    int mId = 0;
+    float mValue = 0f;
+
+    boolean mAnimate = false;
+    long mAnimateTargetTime = 0;
+    float mAnimateDuration = 300f;
+    float mTargetRotationX;
+    float mStartRotationX;
+
+    int mMotionEasingType = GeneralEasing.CUBIC_STANDARD;
+    FloatAnimation mMotionEasing;
+
+    public AnimatableValue(float value) {
+        if (Utils.isVariable(value)) {
+            mId = Utils.idFromNan(value);
+            mIsVariable = true;
+        } else {
+            mValue = value;
+        }
+    }
+
+    public float getValue() {
+        return mValue;
+    }
+
+    public float evaluate(PaintContext context) {
+        if (!mIsVariable) {
+            return mValue;
+        }
+        float value = context.getContext().mRemoteComposeState.getFloat(mId);
+
+        if (value != mValue && !mAnimate) {
+            // animate
+            mStartRotationX = mValue;
+            mTargetRotationX = value;
+            mAnimate = true;
+            mAnimateTargetTime = System.currentTimeMillis();
+            mMotionEasing =
+                    new FloatAnimation(
+                            mMotionEasingType, mAnimateDuration / 1000f, null, 0f, Float.NaN);
+            mMotionEasing.setTargetValue(1f);
+        }
+        if (mAnimate) {
+            float elapsed = System.currentTimeMillis() - mAnimateTargetTime;
+            float p = mMotionEasing.get(elapsed / mAnimateDuration);
+            mValue = (1 - p) * mStartRotationX + p * mTargetRotationX;
+            if (p >= 1f) {
+                mAnimate = false;
+            }
+        } else {
+            mValue = mTargetRotationX;
+        }
+
+        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 9d80d3c..9886518 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
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.WireBuffer;
@@ -38,6 +40,7 @@
         super(parent, componentId, animationId, x, y, width, height);
     }
 
+    @NonNull
     public static String name() {
         return "CanvasContent";
     }
@@ -46,29 +49,30 @@
         return Operations.LAYOUT_CANVAS_CONTENT;
     }
 
+    @NonNull
     @Override
     protected String getSerializedName() {
         return "CANVAS_CONTENT";
     }
 
-    public static void apply(WireBuffer buffer, int componentId) {
+    public static void apply(@NonNull WireBuffer buffer, int componentId) {
         buffer.start(Operations.LAYOUT_CANVAS_CONTENT);
         buffer.writeInt(componentId);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int componentId = buffer.readInt();
         operations.add(new CanvasContent(componentId, 0, 0, 0, 0, null, -1));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .field(INT, "COMPONENT_ID", "unique id for this component")
                 .description("Container for canvas commands.");
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mComponentId);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickHandler.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickHandler.java
new file mode 100644
index 0000000..0ca72fa
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickHandler.java
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+import com.android.internal.widget.remotecompose.core.CoreDocument;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+
+/** Interface to represent operations that can handle click events */
+public interface ClickHandler {
+
+    /**
+     * callback for a click event
+     *
+     * @param context the current context
+     * @param document the current document
+     * @param component the component on which the click has been received
+     * @param x the x position of the click in document coordinates
+     * @param y the y position of the click in document coordinates
+     */
+    void onClick(
+            RemoteContext context, CoreDocument document, Component component, float x, float y);
+}
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 d5ff07d..b567538 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
@@ -15,6 +15,9 @@
  */
 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;
 import com.android.internal.widget.remotecompose.core.Operations;
@@ -37,7 +40,7 @@
 
 /** Represents a click modifier + actions */
 public class ClickModifierOperation extends PaintOperation
-        implements ModifierOperation, DecoratorComponent {
+        implements ModifierOperation, DecoratorComponent, ClickHandler {
     private static final int OP_CODE = Operations.MODIFIER_CLICK;
 
     long mAnimateRippleStart = 0;
@@ -48,9 +51,9 @@
     float mWidth = 0;
     float mHeight = 0;
 
-    public float[] locationInWindow = new float[2];
+    @NonNull public float[] locationInWindow = new float[2];
 
-    PaintBundle mPaint = new PaintBundle();
+    @NonNull PaintBundle mPaint = new PaintBundle();
 
     public void animateRipple(float x, float y) {
         mAnimateRippleStart = System.currentTimeMillis();
@@ -58,17 +61,19 @@
         mAnimateRippleY = y;
     }
 
-    public ArrayList<Operation> mList = new ArrayList<>();
+    @NonNull public ArrayList<Operation> mList = new ArrayList<>();
 
+    @NonNull
     public ArrayList<Operation> getList() {
         return mList;
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "ClickModifier";
@@ -83,13 +88,14 @@
         }
     }
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@Nullable String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         if (mAnimateRippleStart == 0) {
             return;
         }
@@ -137,7 +143,7 @@
     }
 
     @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
         serializer.append(indent, "CLICK_MODIFIER");
         for (Operation o : mList) {
             if (o instanceof ActionOperation) {
@@ -148,7 +154,11 @@
 
     @Override
     public void onClick(
-            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+            RemoteContext context,
+            CoreDocument document,
+            @NonNull Component component,
+            float x,
+            float y) {
         if (!component.isVisible()) {
             return;
         }
@@ -163,19 +173,20 @@
         }
     }
 
+    @NonNull
     public static String name() {
         return "ClickModifier";
     }
 
-    public static void apply(WireBuffer buffer) {
+    public static void apply(@NonNull WireBuffer buffer) {
         buffer.start(OP_CODE);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(WireBuffer buffer, @NonNull List<Operation> operations) {
         operations.add(new ClickModifierOperation());
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", OP_CODE, name())
                 .description(
                         "Click modifier. This operation contains"
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 96dffca..f4f4ee2 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
@@ -15,6 +15,9 @@
  */
 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;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -31,7 +34,6 @@
 import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
 import com.android.internal.widget.remotecompose.core.operations.layout.measure.Measurable;
 import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass;
-import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentModifiers;
 import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
 import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
 
@@ -52,16 +54,23 @@
     protected int mAnimationId = -1;
     public Visibility mVisibility = Visibility.VISIBLE;
     public Visibility mScheduledVisibility = Visibility.VISIBLE;
-    public ArrayList<Operation> mList = new ArrayList<>();
+    @NonNull public ArrayList<Operation> mList = new ArrayList<>();
     public PaintOperation mPreTranslate;
     public boolean mNeedsMeasure = true;
     public boolean mNeedsRepaint = false;
-    public AnimateMeasure mAnimateMeasure;
-    public AnimationSpec mAnimationSpec = new AnimationSpec();
+    @Nullable public AnimateMeasure mAnimateMeasure;
+    @NonNull public AnimationSpec mAnimationSpec = new AnimationSpec();
     public boolean mFirstLayout = true;
-    PaintBundle mPaint = new PaintBundle();
-    protected HashSet<ComponentValue> mComponentValues = new HashSet<>();
+    @NonNull PaintBundle mPaint = new PaintBundle();
+    @NonNull protected HashSet<ComponentValue> mComponentValues = new HashSet<>();
 
+    protected float mZIndex = 0f;
+
+    public float getZIndex() {
+        return mZIndex;
+    }
+
+    @NonNull
     public ArrayList<Operation> getList() {
         return mList;
     }
@@ -115,7 +124,7 @@
      *
      * @param context the current context
      */
-    private void updateComponentValues(RemoteContext context) {
+    private void updateComponentValues(@NonNull RemoteContext context) {
         if (DEBUG) {
             System.out.println(
                     "UPDATE COMPONENT VALUES ("
@@ -172,7 +181,7 @@
         this(parent, componentId, -1, x, y, width, height);
     }
 
-    public Component(Component component) {
+    public Component(@NonNull Component component) {
         this(
                 component.mParent,
                 component.mComponentId,
@@ -212,7 +221,10 @@
      *
      * @param context the current context
      */
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
+        Component prev = context.lastComponent;
+        context.lastComponent = this;
+
         if (!mComponentValues.isEmpty()) {
             updateComponentValues(context);
         }
@@ -224,6 +236,7 @@
                 o.apply(context);
             }
         }
+        context.lastComponent = prev;
     }
 
     public void addComponentValue(ComponentValue v) {
@@ -283,14 +296,14 @@
             float maxWidth,
             float minHeight,
             float maxHeight,
-            MeasurePass measure) {
+            @NonNull MeasurePass measure) {
         ComponentMeasure m = measure.get(this);
         m.setW(mWidth);
         m.setH(mHeight);
     }
 
     @Override
-    public void layout(RemoteContext context, MeasurePass measure) {
+    public void layout(@NonNull RemoteContext context, @NonNull MeasurePass measure) {
         ComponentMeasure m = measure.get(this);
         if (!mFirstLayout
                 && context.isAnimationEnabled()
@@ -332,7 +345,7 @@
         mFirstLayout = false;
     }
 
-    public float[] locationInWindow = new float[2];
+    @NonNull public float[] locationInWindow = new float[2];
 
     public boolean contains(float x, float y) {
         locationInWindow[0] = 0f;
@@ -353,13 +366,57 @@
             if (op instanceof Component) {
                 ((Component) op).onClick(context, document, x, y);
             }
-            if (op instanceof ComponentModifiers) {
-                ((ComponentModifiers) op).onClick(context, document, this, x, y);
+            if (op instanceof ClickHandler) {
+                ((ClickHandler) op).onClick(context, document, this, x, y);
             }
         }
     }
 
-    public void getLocationInWindow(float[] value) {
+    public void onTouchDown(RemoteContext context, CoreDocument document, float x, float y) {
+        if (!contains(x, y)) {
+            return;
+        }
+        for (Operation op : mList) {
+            if (op instanceof Component) {
+                ((Component) op).onTouchDown(context, document, x, y);
+            }
+            if (op instanceof TouchHandler) {
+                ((TouchHandler) op).onTouchDown(context, document, this, x, y);
+            }
+        }
+    }
+
+    public void onTouchUp(
+            RemoteContext context, CoreDocument document, float x, float y, boolean force) {
+        if (!force && !contains(x, y)) {
+            return;
+        }
+        for (Operation op : mList) {
+            if (op instanceof Component) {
+                ((Component) op).onTouchUp(context, document, x, y, force);
+            }
+            if (op instanceof TouchHandler) {
+                ((TouchHandler) op).onTouchUp(context, document, this, x, y);
+            }
+        }
+    }
+
+    public void onTouchCancel(
+            RemoteContext context, CoreDocument document, float x, float y, boolean force) {
+        if (!force && !contains(x, y)) {
+            return;
+        }
+        for (Operation op : mList) {
+            if (op instanceof Component) {
+                ((Component) op).onTouchCancel(context, document, x, y, force);
+            }
+            if (op instanceof TouchHandler) {
+                ((TouchHandler) op).onTouchCancel(context, document, this, x, y);
+            }
+        }
+    }
+
+    public void getLocationInWindow(@NonNull float[] value) {
         value[0] += mX;
         value[1] += mY;
         if (mParent != null) {
@@ -372,6 +429,7 @@
         }
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "COMPONENT(<"
@@ -393,14 +451,14 @@
                 + ") ";
     }
 
+    @NonNull
     protected String getSerializedName() {
         return "COMPONENT";
     }
 
     @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
-        serializer.append(
-                indent,
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
+        String content =
                 getSerializedName()
                         + " ["
                         + mComponentId
@@ -416,9 +474,9 @@
                         + ", "
                         + mHeight
                         + "] "
-                        + mVisibility
-                //        + " [" + mNeedsMeasure + ", " + mNeedsRepaint + "]"
-        );
+                        + mVisibility;
+        //        + " [" + mNeedsMeasure + ", " + mNeedsRepaint + "]"
+        serializer.append(indent, content);
     }
 
     @Override
@@ -427,6 +485,7 @@
     }
 
     /** Returns the top-level RootLayoutComponent */
+    @NonNull
     public RootLayoutComponent getRoot() throws Exception {
         if (this instanceof RootLayoutComponent) {
             return (RootLayoutComponent) this;
@@ -441,6 +500,7 @@
         return (RootLayoutComponent) p;
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         StringBuilder builder = new StringBuilder();
@@ -477,6 +537,7 @@
         }
     }
 
+    @NonNull
     public String content() {
         StringBuilder builder = new StringBuilder();
         for (Operation op : mList) {
@@ -487,6 +548,7 @@
         return builder.toString();
     }
 
+    @NonNull
     public String textContent() {
         StringBuilder builder = new StringBuilder();
         for (Operation ignored : mList) {
@@ -499,7 +561,7 @@
         return builder.toString();
     }
 
-    public void debugBox(Component component, PaintContext context) {
+    public void debugBox(@NonNull Component component, @NonNull PaintContext context) {
         float width = component.mWidth;
         float height = component.mHeight;
 
@@ -536,13 +598,15 @@
         return 0f;
     }
 
-    public void paintingComponent(PaintContext context) {
+    public void paintingComponent(@NonNull PaintContext context) {
         if (mPreTranslate != null) {
             mPreTranslate.paint(context);
         }
+        Component prev = context.getContext().lastComponent;
+        context.getContext().lastComponent = this;
         context.save();
         context.translate(mX, mY);
-        if (context.isDebug()) {
+        if (context.isVisualDebug()) {
             debugBox(this, context);
         }
         for (Operation op : mList) {
@@ -554,9 +618,10 @@
             }
         }
         context.restore();
+        context.getContext().lastComponent = prev;
     }
 
-    public boolean applyAnimationAsNeeded(PaintContext context) {
+    public boolean applyAnimationAsNeeded(@NonNull PaintContext context) {
         if (context.isAnimationEnabled() && mAnimateMeasure != null) {
             mAnimateMeasure.apply(context);
             needsRepaint();
@@ -566,8 +631,8 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
-        if (context.isDebug()) {
+    public void paint(@NonNull PaintContext context) {
+        if (context.isVisualDebug()) {
             context.save();
             context.translate(mX, mY);
             context.savePaint();
@@ -594,7 +659,7 @@
         paintingComponent(context);
     }
 
-    public void getComponents(ArrayList<Component> components) {
+    public void getComponents(@NonNull ArrayList<Component> components) {
         for (Operation op : mList) {
             if (op instanceof Component) {
                 components.add((Component) op);
@@ -602,7 +667,7 @@
         }
     }
 
-    public void getData(ArrayList<TextData> data) {
+    public void getData(@NonNull ArrayList<TextData> data) {
         for (Operation op : mList) {
             if (op instanceof TextData) {
                 data.add((TextData) op);
@@ -631,6 +696,7 @@
         return mNeedsRepaint;
     }
 
+    @Nullable
     public Component getComponent(int cid) {
         if (mComponentId == cid || mAnimationId == cid) {
             return this;
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 c83ee487..f370e20 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
@@ -15,6 +15,9 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -26,10 +29,11 @@
 public class ComponentEnd implements Operation {
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "COMPONENT_END";
@@ -40,11 +44,13 @@
         // nothing
     }
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@Nullable String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
+    @NonNull
     public static String name() {
         return "ComponentEnd";
     }
@@ -53,7 +59,7 @@
         return Operations.COMPONENT_END;
     }
 
-    public static void apply(WireBuffer buffer) {
+    public static void apply(@NonNull WireBuffer buffer) {
         buffer.start(Operations.COMPONENT_END);
     }
 
@@ -61,11 +67,11 @@
         return 1 + 4 + 4 + 4;
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(WireBuffer buffer, @NonNull List<Operation> operations) {
         operations.add(new ComponentEnd());
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .description(
                         "End tag for components / layouts. This operation marks the end"
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 72cc9b6..f250d9a 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
@@ -18,6 +18,9 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+import 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;
@@ -69,10 +72,11 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mType, mComponentId, mWidth, mHeight);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "COMPONENT_START (type "
@@ -90,8 +94,9 @@
                 + ")";
     }
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@Nullable String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
@@ -119,6 +124,7 @@
     public static final int LAYOUT_ROW = 15;
     public static final int LAYOUT_COLUMN = 16;
 
+    @NonNull
     public static String typeDescription(int type) {
         switch (type) {
             case DEFAULT:
@@ -152,6 +158,7 @@
         }
     }
 
+    @NonNull
     public static String name() {
         return "ComponentStart";
     }
@@ -161,7 +168,7 @@
     }
 
     public static void apply(
-            WireBuffer buffer, int type, int componentId, float width, float height) {
+            @NonNull WireBuffer buffer, int type, int componentId, float width, float height) {
         buffer.start(Operations.COMPONENT_START);
         buffer.writeInt(type);
         buffer.writeInt(componentId);
@@ -173,7 +180,7 @@
         return 1 + 4 + 4 + 4;
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int type = buffer.readInt();
         int componentId = buffer.readInt();
         float width = buffer.readFloat();
@@ -181,7 +188,7 @@
         operations.add(new ComponentStart(type, componentId, width, height));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .description(
                         "Basic component encapsulating draw commands." + "This is not resizable.")
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 314650f..bb43119 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/DecoratorComponent.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/DecoratorComponent.java
@@ -15,7 +15,6 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout;
 
-import com.android.internal.widget.remotecompose.core.CoreDocument;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
 
 /**
@@ -24,7 +23,4 @@
  */
 public interface DecoratorComponent {
     void layout(RemoteContext context, float width, float height);
-
-    void onClick(
-            RemoteContext context, CoreDocument document, Component component, float x, float y);
 }
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 8172502..e0923dfb 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
@@ -15,6 +15,9 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.PaintContext;
 import com.android.internal.widget.remotecompose.core.operations.BitmapData;
@@ -25,18 +28,22 @@
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentModifiers;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentVisibilityOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.DimensionModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.GraphicsLayerModifierOperation;
 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.WidthModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ZIndexModifierOperation;
 
 import java.util.ArrayList;
 
 /** Component with modifiers and children */
 public class LayoutComponent extends Component {
 
-    protected WidthModifierOperation mWidthModifier = null;
-    protected HeightModifierOperation mHeightModifier = null;
+    @Nullable protected WidthModifierOperation mWidthModifier = null;
+    @Nullable protected HeightModifierOperation mHeightModifier = null;
+    @Nullable protected ZIndexModifierOperation mZIndexModifier = null;
+    @Nullable protected GraphicsLayerModifierOperation mGraphicsLayerModifier = null;
 
     // Margins
     protected float mMarginLeft = 0f;
@@ -49,8 +56,10 @@
     protected float mPaddingTop = 0f;
     protected float mPaddingBottom = 0f;
 
-    protected ComponentModifiers mComponentModifiers = new ComponentModifiers();
-    protected ArrayList<Component> mChildrenComponents = new ArrayList<>();
+    @NonNull protected ComponentModifiers mComponentModifiers = new ComponentModifiers();
+    @NonNull protected ArrayList<Component> mChildrenComponents = new ArrayList<>();
+
+    protected boolean mChildrenHaveZIndex = false;
 
     public LayoutComponent(
             Component parent,
@@ -95,15 +104,25 @@
         return mPaddingBottom;
     }
 
+    @Nullable
     public WidthModifierOperation getWidthModifier() {
         return mWidthModifier;
     }
 
+    @Nullable
     public HeightModifierOperation getHeightModifier() {
         return mHeightModifier;
     }
 
-    protected LayoutComponentContent mContent = null;
+    @Override
+    public float getZIndex() {
+        if (mZIndexModifier != null) {
+            return mZIndexModifier.getValue();
+        }
+        return mZIndex;
+    }
+
+    @Nullable protected LayoutComponentContent mContent = null;
 
     // Should be removed after ImageLayout is in
     private static final boolean USE_IMAGE_TEMP_FIX = true;
@@ -164,6 +183,9 @@
         for (Component c : mChildrenComponents) {
             c.mParent = this;
             mList.add(c);
+            if (c instanceof LayoutComponent && ((LayoutComponent) c).mZIndexModifier != null) {
+                mChildrenHaveZIndex = true;
+            }
         }
 
         mX = 0f;
@@ -209,6 +231,12 @@
                 mHeightModifier = (HeightModifierOperation) op;
                 applyVerticalMargin = false;
             }
+            if (op instanceof ZIndexModifierOperation) {
+                mZIndexModifier = (ZIndexModifierOperation) op;
+            }
+            if (op instanceof GraphicsLayerModifierOperation) {
+                mGraphicsLayerModifier = (GraphicsLayerModifierOperation) op;
+            }
         }
         if (mWidthModifier == null) {
             mWidthModifier = new WidthModifierOperation(DimensionModifierOperation.Type.WRAP);
@@ -220,24 +248,64 @@
         setHeight(computeModifierDefinedHeight());
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "UNKNOWN LAYOUT_COMPONENT";
     }
 
     @Override
-    public void paintingComponent(PaintContext context) {
+    public void paintingComponent(@NonNull PaintContext context) {
+        Component prev = context.getContext().lastComponent;
+        context.getContext().lastComponent = this;
         context.save();
         context.translate(mX, mY);
+        if (mGraphicsLayerModifier != null) {
+            context.startGraphicsLayer((int) getWidth(), (int) getHeight());
+            float scaleX = mGraphicsLayerModifier.getScaleX();
+            float scaleY = mGraphicsLayerModifier.getScaleY();
+            float rotationX = mGraphicsLayerModifier.getRotationX();
+            float rotationY = mGraphicsLayerModifier.getRotationY();
+            float rotationZ = mGraphicsLayerModifier.getRotationZ();
+            float shadowElevation = mGraphicsLayerModifier.getShadowElevation();
+            float transformOriginX = mGraphicsLayerModifier.getTransformOriginX();
+            float transformOriginY = mGraphicsLayerModifier.getTransformOriginY();
+            float alpha = mGraphicsLayerModifier.getAlpha();
+            int renderEffectId = mGraphicsLayerModifier.getRenderEffectId();
+            context.setGraphicsLayer(
+                    scaleX,
+                    scaleY,
+                    rotationX,
+                    rotationY,
+                    rotationZ,
+                    shadowElevation,
+                    transformOriginX,
+                    transformOriginY,
+                    alpha,
+                    renderEffectId);
+        }
         mComponentModifiers.paint(context);
         float tx = mPaddingLeft;
         float ty = mPaddingTop;
         context.translate(tx, ty);
-        for (Component child : mChildrenComponents) {
-            child.paint(context);
+        if (mChildrenHaveZIndex) {
+            // TODO -- should only sort when something has changed
+            ArrayList<Component> sorted = new ArrayList<Component>(mChildrenComponents);
+            sorted.sort((a, b) -> (int) (a.getZIndex() - b.getZIndex()));
+            for (Component child : sorted) {
+                child.paint(context);
+            }
+        } else {
+            for (Component child : mChildrenComponents) {
+                child.paint(context);
+            }
+        }
+        if (mGraphicsLayerModifier != null) {
+            context.endGraphicsLayer();
         }
         context.translate(-tx, -ty);
         context.restore();
+        context.getContext().lastComponent = prev;
     }
 
     /** Traverse the modifiers to compute indicated dimension */
@@ -248,7 +316,8 @@
         for (Operation c : mComponentModifiers.getList()) {
             if (c instanceof WidthModifierOperation) {
                 WidthModifierOperation o = (WidthModifierOperation) c;
-                if (o.getType() == DimensionModifierOperation.Type.EXACT) {
+                if (o.getType() == DimensionModifierOperation.Type.EXACT
+                        || o.getType() == DimensionModifierOperation.Type.EXACT_DP) {
                     w = o.getValue();
                 }
                 break;
@@ -291,7 +360,8 @@
         for (Operation c : mComponentModifiers.getList()) {
             if (c instanceof HeightModifierOperation) {
                 HeightModifierOperation o = (HeightModifierOperation) c;
-                if (o.getType() == DimensionModifierOperation.Type.EXACT) {
+                if (o.getType() == DimensionModifierOperation.Type.EXACT
+                        || o.getType() == DimensionModifierOperation.Type.EXACT_DP) {
                     h = o.getValue();
                 }
                 break;
@@ -326,6 +396,7 @@
         return t + b;
     }
 
+    @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 66fd053..0a085b4 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
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.WireBuffer;
@@ -38,6 +40,7 @@
         super(parent, componentId, animationId, x, y, width, height);
     }
 
+    @NonNull
     public static String name() {
         return "LayoutContent";
     }
@@ -46,22 +49,23 @@
         return Operations.LAYOUT_CONTENT;
     }
 
+    @NonNull
     @Override
     protected String getSerializedName() {
         return "CONTENT";
     }
 
-    public static void apply(WireBuffer buffer, int componentId) {
+    public static void apply(@NonNull WireBuffer buffer, int componentId) {
         buffer.start(Operations.LAYOUT_CONTENT);
         buffer.writeInt(componentId);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int componentId = buffer.readInt();
         operations.add(new LayoutComponentContent(componentId, 0, 0, 0, 0, null, -1));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .field(INT, "COMPONENT_ID", "unique id for this component")
                 .description(
@@ -71,7 +75,7 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mComponentId);
     }
 }
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
new file mode 100644
index 0000000..c4df075
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ListActionsOperation.java
@@ -0,0 +1,109 @@
+/*
+ * 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;
+
+import com.android.internal.widget.remotecompose.core.CoreDocument;
+import com.android.internal.widget.remotecompose.core.Operation;
+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.operations.TextData;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+import java.util.ArrayList;
+
+public abstract class ListActionsOperation extends PaintOperation
+        implements ModifierOperation, DecoratorComponent {
+
+    String mOperationName;
+    float mWidth = 0;
+    float mHeight = 0;
+
+    private final float[] mLocationInWindow = new float[2];
+
+    public ListActionsOperation(String operationName) {
+        mOperationName = operationName;
+    }
+
+    public ArrayList<Operation> mList = new ArrayList<>();
+
+    public ArrayList<Operation> getList() {
+        return mList;
+    }
+
+    @Override
+    public String toString() {
+        return mOperationName;
+    }
+
+    @Override
+    public void apply(RemoteContext context) {
+        for (Operation op : mList) {
+            if (op instanceof TextData) {
+                op.apply(context);
+            }
+        }
+    }
+
+    @Override
+    public String deepToString(String indent) {
+        return (indent != null ? indent : "") + toString();
+    }
+
+    @Override
+    public void paint(PaintContext context) {}
+
+    @Override
+    public void layout(RemoteContext context, float width, float height) {
+        mWidth = width;
+        mHeight = height;
+    }
+
+    @Override
+    public void serializeToString(int indent, StringSerializer serializer) {
+        serializer.append(indent, mOperationName);
+        for (Operation o : mList) {
+            if (o instanceof ActionOperation) {
+                ((ActionOperation) o).serializeToString(indent + 1, serializer);
+            }
+        }
+    }
+
+    public boolean applyActions(
+            RemoteContext context,
+            CoreDocument document,
+            Component component,
+            float x,
+            float y,
+            boolean force) {
+        if (!force && !component.isVisible()) {
+            return false;
+        }
+        if (!force && !component.contains(x, y)) {
+            return false;
+        }
+        mLocationInWindow[0] = 0f;
+        mLocationInWindow[1] = 0f;
+        component.getLocationInWindow(mLocationInWindow);
+        for (Operation o : mList) {
+            if (o instanceof ActionOperation) {
+                ((ActionOperation) o).runAction(context, document, component, x, y);
+            }
+        }
+        return true;
+    }
+}
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 3086d6a..c90077b 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
@@ -15,6 +15,9 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -26,10 +29,11 @@
 public class LoopEnd implements Operation {
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "LOOP_END";
@@ -40,11 +44,13 @@
         // nothing
     }
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@Nullable String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
+    @NonNull
     public static String name() {
         return "LoopEnd";
     }
@@ -53,15 +59,15 @@
         return Operations.LOOP_END;
     }
 
-    public static void apply(WireBuffer buffer) {
+    public static void apply(@NonNull WireBuffer buffer) {
         buffer.start(id());
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(WireBuffer buffer, @NonNull List<Operation> operations) {
         operations.add(new LoopEnd());
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    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 6910008..eeaeafd 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
@@ -15,6 +15,9 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -30,7 +33,7 @@
 public class LoopOperation extends PaintOperation {
     private static final int OP_CODE = Operations.LOOP_START;
 
-    public ArrayList<Operation> mList = new ArrayList<>();
+    @NonNull public ArrayList<Operation> mList = new ArrayList<>();
 
     int mIndexVariableId;
     float mUntil = 12;
@@ -49,27 +52,30 @@
         mIndexVariableId = indexId;
     }
 
+    @NonNull
     public ArrayList<Operation> getList() {
         return mList;
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mUntil, mFrom, mStep, mIndexVariableId);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "LoopOperation";
     }
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@Nullable String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         if (mIndexVariableId == 0) {
             for (float i = mFrom; i < mUntil; i += mStep) {
                 for (Operation op : mList) {
@@ -89,11 +95,13 @@
         }
     }
 
+    @NonNull
     public static String name() {
         return "Loop";
     }
 
-    public static void apply(WireBuffer buffer, float count, float from, float step, int indexId) {
+    public static void apply(
+            @NonNull WireBuffer buffer, float count, float from, float step, int indexId) {
         buffer.start(OP_CODE);
         buffer.writeFloat(count);
         buffer.writeFloat(from);
@@ -101,7 +109,7 @@
         buffer.writeInt(indexId);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         float count = buffer.readFloat();
         float from = buffer.readFloat();
         float step = buffer.readFloat();
@@ -109,7 +117,7 @@
         operations.add(new LoopOperation(count, from, step, indexId));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    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/ClickModifierEnd.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/OperationsListEnd.java
similarity index 68%
rename from core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierEnd.java
rename to core/java/com/android/internal/widget/remotecompose/core/operations/layout/OperationsListEnd.java
index fe726ac..bd8d1f0 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierEnd.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/OperationsListEnd.java
@@ -15,6 +15,9 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -23,16 +26,17 @@
 
 import java.util.List;
 
-public class ClickModifierEnd implements Operation {
+public class OperationsListEnd implements Operation {
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer);
     }
 
+    @NonNull
     @Override
     public String toString() {
-        return "CLICK_END";
+        return "LIST_END";
     }
 
     @Override
@@ -40,31 +44,31 @@
         // nothing
     }
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@Nullable String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
+    @NonNull
     public static String name() {
-        return "ClickModifierEnd";
+        return "ListEnd";
     }
 
     public static int id() {
-        return Operations.MODIFIER_CLICK_END;
+        return Operations.OPERATIONS_LIST_END;
     }
 
-    public static void apply(WireBuffer buffer) {
+    public static void apply(@NonNull WireBuffer buffer) {
         buffer.start(id());
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
-        operations.add(new ClickModifierEnd());
+    public static void read(WireBuffer buffer, @NonNull List<Operation> operations) {
+        operations.add(new OperationsListEnd());
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
-                .description(
-                        "End tag for click modifiers. This operation marks the end"
-                                + "of a click modifier");
+                .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 680bb0b..524ae59 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
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -34,7 +36,8 @@
 
 /** Represents the root layout component. Entry point to the component tree layout/paint. */
 public class RootLayoutComponent extends Component implements ComponentStartOperation {
-    int mCurrentId = -1;
+    private int mCurrentId = -1;
+    private boolean mHasTouchListeners = false;
 
     public RootLayoutComponent(
             int componentId,
@@ -52,6 +55,7 @@
         super(parent, componentId, -1, x, y, width, height);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "ROOT "
@@ -69,7 +73,7 @@
     }
 
     @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
         serializer.append(
                 indent,
                 "ROOT ["
@@ -89,6 +93,15 @@
     }
 
     /**
+     * Set the flag to traverse the tree when touch events happen
+     *
+     * @param value true to indicate that the tree has touch listeners
+     */
+    public void setHasTouchListeners(boolean value) {
+        mHasTouchListeners = value;
+    }
+
+    /**
      * Traverse the hierarchy and assign generated ids to component without ids. Most components
      * would already have ids assigned during the document creation, but this allow us to take care
      * of any components added during the inflation.
@@ -100,7 +113,7 @@
         assignId(this);
     }
 
-    private void assignId(Component component) {
+    private void assignId(@NonNull Component component) {
         if (component.mComponentId == -1) {
             mCurrentId--;
             component.mComponentId = mCurrentId;
@@ -113,7 +126,7 @@
     }
 
     /** This will measure then layout the tree of components */
-    public void layout(RemoteContext context) {
+    public void layout(@NonNull RemoteContext context) {
         if (!mNeedsMeasure) {
             return;
         }
@@ -134,7 +147,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         mNeedsRepaint = false;
         context.getContext().lastComponent = this;
         context.save();
@@ -152,13 +165,15 @@
         context.restore();
     }
 
+    @NonNull
     public String displayHierarchy() {
         StringSerializer serializer = new StringSerializer();
         displayHierarchy(this, 0, serializer);
         return serializer.toString();
     }
 
-    public void displayHierarchy(Component component, int indent, StringSerializer serializer) {
+    public void displayHierarchy(
+            @NonNull Component component, int indent, @NonNull StringSerializer serializer) {
         component.serializeToString(indent, serializer);
         for (Operation c : component.mList) {
             if (c instanceof ComponentModifiers) {
@@ -171,6 +186,7 @@
         }
     }
 
+    @NonNull
     public static String name() {
         return "RootLayout";
     }
@@ -179,17 +195,17 @@
         return Operations.LAYOUT_ROOT;
     }
 
-    public static void apply(WireBuffer buffer, int componentId) {
+    public static void apply(@NonNull WireBuffer buffer, int componentId) {
         buffer.start(Operations.LAYOUT_ROOT);
         buffer.writeInt(componentId);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int componentId = buffer.readInt();
         operations.add(new RootLayoutComponent(componentId, 0, 0, 0, 0, null, -1));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .field(INT, "COMPONENT_ID", "unique id for this component")
                 .description(
@@ -199,7 +215,11 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mComponentId);
     }
+
+    public boolean hasTouchListeners() {
+        return mHasTouchListeners;
+    }
 }
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
new file mode 100644
index 0000000..486efbd
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java
@@ -0,0 +1,91 @@
+/*
+ * 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;
+
+import com.android.internal.widget.remotecompose.core.CoreDocument;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+
+import java.util.List;
+
+/** Represents a touch cancel modifier + actions */
+public class TouchCancelModifierOperation extends ListActionsOperation implements TouchHandler {
+
+    private static final int OP_CODE = Operations.MODIFIER_TOUCH_CANCEL;
+
+    public TouchCancelModifierOperation() {
+        super("TOUCH_CANCEL_MODIFIER");
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        apply(buffer);
+    }
+
+    @Override
+    public String toString() {
+        return "TouchCancelModifier";
+    }
+
+    @Override
+    public void apply(RemoteContext context) {
+        RootLayoutComponent root = context.getDocument().getRootLayoutComponent();
+        if (root != null) {
+            root.setHasTouchListeners(true);
+        }
+        super.apply(context);
+    }
+
+    @Override
+    public void onTouchDown(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+        // nothing
+    }
+
+    @Override
+    public void onTouchUp(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+        // nothing
+    }
+
+    @Override
+    public void onTouchCancel(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+        applyActions(context, document, component, x, y, true);
+    }
+
+    public static String name() {
+        return "TouchCancelModifier";
+    }
+
+    public static void apply(WireBuffer buffer) {
+        buffer.start(OP_CODE);
+    }
+
+    public static void read(WireBuffer buffer, List<Operation> operations) {
+        operations.add(new TouchCancelModifierOperation());
+    }
+
+    public static void documentation(DocumentationBuilder doc) {
+        doc.operation("Modifier Operations", OP_CODE, name())
+                .description(
+                        "Touch cancel modifier. This operation contains"
+                                + " a list of action executed on Touch cancel");
+    }
+}
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
new file mode 100644
index 0000000..5d379fe
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java
@@ -0,0 +1,93 @@
+/*
+ * 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;
+
+import com.android.internal.widget.remotecompose.core.CoreDocument;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+
+import java.util.List;
+
+/** Represents a touch down modifier + actions */
+public class TouchDownModifierOperation extends ListActionsOperation implements TouchHandler {
+
+    private static final int OP_CODE = Operations.MODIFIER_TOUCH_DOWN;
+
+    public TouchDownModifierOperation() {
+        super("TOUCH_DOWN_MODIFIER");
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        apply(buffer);
+    }
+
+    @Override
+    public String toString() {
+        return "TouchDownModifier";
+    }
+
+    @Override
+    public void apply(RemoteContext context) {
+        RootLayoutComponent root = context.getDocument().getRootLayoutComponent();
+        if (root != null) {
+            root.setHasTouchListeners(true);
+        }
+        super.apply(context);
+    }
+
+    @Override
+    public void onTouchDown(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+        if (applyActions(context, document, component, x, y, false)) {
+            document.appliedTouchOperation(component);
+        }
+    }
+
+    @Override
+    public void onTouchUp(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+        // nothing
+    }
+
+    @Override
+    public void onTouchCancel(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+        // nothing
+    }
+
+    public static String name() {
+        return "TouchModifier";
+    }
+
+    public static void apply(WireBuffer buffer) {
+        buffer.start(OP_CODE);
+    }
+
+    public static void read(WireBuffer buffer, List<Operation> operations) {
+        operations.add(new TouchDownModifierOperation());
+    }
+
+    public static void documentation(DocumentationBuilder doc) {
+        doc.operation("Modifier Operations", OP_CODE, name())
+                .description(
+                        "Touch down modifier. This operation contains"
+                                + " a list of action executed on Touch down");
+    }
+}
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
new file mode 100644
index 0000000..5adfc33
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchHandler.java
@@ -0,0 +1,59 @@
+/*
+ * 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;
+
+import com.android.internal.widget.remotecompose.core.CoreDocument;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+
+/** Interface to represent operations that can handle touch events */
+public interface TouchHandler {
+
+    /**
+     * callback for a touch down event
+     *
+     * @param context the current context
+     * @param document the current document
+     * @param component the component on which the touch has been received
+     * @param x the x position of the click in document coordinates
+     * @param y the y position of the click in document coordinates
+     */
+    void onTouchDown(
+            RemoteContext context, CoreDocument document, Component component, float x, float y);
+
+    /**
+     * callback for a touch up event
+     *
+     * @param context the current context
+     * @param document the current document
+     * @param component the component on which the touch has been received
+     * @param x the x position of the click in document coordinates
+     * @param y the y position of the click in document coordinates
+     */
+    void onTouchUp(
+            RemoteContext context, CoreDocument document, Component component, float x, float y);
+
+    /**
+     * callback for a touch cancel event
+     *
+     * @param context the current context
+     * @param document the current document
+     * @param component the component on which the touch has been received
+     * @param x the x position of the click in document coordinates
+     * @param y the y position of the click in document coordinates
+     */
+    void onTouchCancel(
+            RemoteContext context, CoreDocument document, Component component, float x, float y);
+}
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
new file mode 100644
index 0000000..263cc43
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java
@@ -0,0 +1,91 @@
+/*
+ * 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;
+
+import com.android.internal.widget.remotecompose.core.CoreDocument;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+
+import java.util.List;
+
+/** Represents a touch up modifier + actions */
+public class TouchUpModifierOperation extends ListActionsOperation implements TouchHandler {
+
+    private static final int OP_CODE = Operations.MODIFIER_TOUCH_UP;
+
+    public TouchUpModifierOperation() {
+        super("TOUCH_UP_MODIFIER");
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        apply(buffer);
+    }
+
+    @Override
+    public String toString() {
+        return "TouchUpModifier";
+    }
+
+    @Override
+    public void apply(RemoteContext context) {
+        RootLayoutComponent root = context.getDocument().getRootLayoutComponent();
+        if (root != null) {
+            root.setHasTouchListeners(true);
+        }
+        super.apply(context);
+    }
+
+    @Override
+    public void onTouchDown(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+        // nothing
+    }
+
+    @Override
+    public void onTouchUp(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+        applyActions(context, document, component, x, y, true);
+    }
+
+    @Override
+    public void onTouchCancel(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+        // nothing
+    }
+
+    public static String name() {
+        return "TouchUpModifier";
+    }
+
+    public static void apply(WireBuffer buffer) {
+        buffer.start(OP_CODE);
+    }
+
+    public static void read(WireBuffer buffer, List<Operation> operations) {
+        operations.add(new TouchUpModifierOperation());
+    }
+
+    public static void documentation(DocumentationBuilder doc) {
+        doc.operation("Modifier Operations", OP_CODE, name())
+                .description(
+                        "Touch up modifier. This operation contains"
+                                + " a list of action executed on Touch up");
+    }
+}
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 e450585..6036b74 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
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout.animation;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.PaintContext;
 import com.android.internal.widget.remotecompose.core.operations.layout.Component;
@@ -44,18 +46,23 @@
 
     float mP = 0f;
     float mVp = 0f;
+
+    @NonNull
     FloatAnimation mMotionEasing =
             new FloatAnimation(mMotionEasingType, mDuration / 1000f, null, 0f, Float.NaN);
+
+    @NonNull
     FloatAnimation mVisibilityEasing =
             new FloatAnimation(
                     mVisibilityEasingType, mDurationVisibilityChange / 1000f, null, 0f, Float.NaN);
+
     ParticleAnimation mParticleAnimation;
 
     public AnimateMeasure(
             long startTime,
-            Component component,
+            @NonNull Component component,
             ComponentMeasure original,
-            ComponentMeasure target,
+            @NonNull ComponentMeasure target,
             int duration,
             int durationVisibilityChange,
             AnimationSpec.ANIMATION enterAnimation,
@@ -94,9 +101,9 @@
         mVp = mVisibilityEasing.get(visibilityProgress);
     }
 
-    public PaintBundle paint = new PaintBundle();
+    @NonNull public PaintBundle paint = new PaintBundle();
 
-    public void apply(PaintContext context) {
+    public void apply(@NonNull PaintContext context) {
         update(context.getContext().currentTime);
 
         mComponent.setX(getX());
@@ -338,7 +345,7 @@
         }
     }
 
-    public void updateTarget(ComponentMeasure measure, long currentTime) {
+    public void updateTarget(@NonNull ComponentMeasure measure, long currentTime) {
         mOriginal.setX(getX());
         mOriginal.setY(getY());
         mOriginal.setW(getWidth());
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 35533cb..47abade 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
@@ -17,6 +17,9 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -92,6 +95,7 @@
         return mExitAnimation;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "ANIMATION_SPEC (" + mMotionDuration + " ms)";
@@ -109,7 +113,7 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(
                 buffer,
                 mAnimationId,
@@ -126,11 +130,13 @@
         // nothing here
     }
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@Nullable String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
+    @NonNull
     public static String name() {
         return "AnimationSpec";
     }
@@ -139,10 +145,11 @@
         return Operations.ANIMATION_SPEC;
     }
 
-    public static int animationToInt(ANIMATION animation) {
+    public static int animationToInt(@NonNull ANIMATION animation) {
         return animation.ordinal();
     }
 
+    @NonNull
     public static ANIMATION intToAnimation(int value) {
         switch (value) {
             case 0:
@@ -167,14 +174,14 @@
     }
 
     public static void apply(
-            WireBuffer buffer,
+            @NonNull WireBuffer buffer,
             int animationId,
             int motionDuration,
             int motionEasingType,
             int visibilityDuration,
             int visibilityEasingType,
-            ANIMATION enterAnimation,
-            ANIMATION exitAnimation) {
+            @NonNull ANIMATION enterAnimation,
+            @NonNull ANIMATION exitAnimation) {
         buffer.start(Operations.ANIMATION_SPEC);
         buffer.writeInt(animationId);
         buffer.writeInt(motionDuration);
@@ -185,7 +192,7 @@
         buffer.writeInt(animationToInt(exitAnimation));
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int animationId = buffer.readInt();
         int motionDuration = buffer.readInt();
         int motionEasingType = buffer.readInt();
@@ -205,7 +212,7 @@
         operations.add(op);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .description("define the animation")
                 .field(INT, "animationId", "")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/ParticleAnimation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/ParticleAnimation.java
index 686643f..37d2078 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/ParticleAnimation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/ParticleAnimation.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout.animation;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.PaintContext;
 import com.android.internal.widget.remotecompose.core.operations.layout.Component;
 import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
@@ -24,14 +26,14 @@
 import java.util.HashMap;
 
 public class ParticleAnimation {
-    HashMap<Integer, ArrayList<Particle>> mAllParticles = new HashMap<>();
+    @NonNull HashMap<Integer, ArrayList<Particle>> mAllParticles = new HashMap<>();
 
-    PaintBundle mPaint = new PaintBundle();
+    @NonNull PaintBundle mPaint = new PaintBundle();
 
     public void animate(
-            PaintContext context,
-            Component component,
-            ComponentMeasure start,
+            @NonNull PaintContext context,
+            @NonNull Component component,
+            @NonNull ComponentMeasure start,
             ComponentMeasure end,
             float progress) {
         ArrayList<Particle> particles = mAllParticles.get(component.getComponentId());
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 047a968..f3e5509 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
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -75,6 +77,7 @@
                 verticalPositioning);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "BOX ["
@@ -93,6 +96,7 @@
                 + mVisibility;
     }
 
+    @NonNull
     @Override
     protected String getSerializedName() {
         return "BOX";
@@ -100,7 +104,11 @@
 
     @Override
     public void computeWrapSize(
-            PaintContext context, float maxWidth, float maxHeight, MeasurePass measure, Size size) {
+            PaintContext context,
+            float maxWidth,
+            float maxHeight,
+            @NonNull MeasurePass measure,
+            @NonNull Size size) {
         for (Component c : mChildrenComponents) {
             c.measure(context, 0f, maxWidth, 0f, maxHeight, measure);
             ComponentMeasure m = measure.get(c);
@@ -119,14 +127,14 @@
             float maxWidth,
             float minHeight,
             float maxHeight,
-            MeasurePass measure) {
+            @NonNull MeasurePass measure) {
         for (Component child : mChildrenComponents) {
             child.measure(context, minWidth, maxWidth, minHeight, maxHeight, measure);
         }
     }
 
     @Override
-    public void internalLayoutMeasure(PaintContext context, MeasurePass measure) {
+    public void internalLayoutMeasure(PaintContext context, @NonNull MeasurePass measure) {
         ComponentMeasure selfMeasure = measure.get(this);
         float selfWidth = selfMeasure.getW() - mPaddingLeft - mPaddingRight;
         float selfHeight = selfMeasure.getH() - mPaddingTop - mPaddingBottom;
@@ -161,6 +169,7 @@
         }
     }
 
+    @NonNull
     public static String name() {
         return "BoxLayout";
     }
@@ -170,7 +179,7 @@
     }
 
     public static void apply(
-            WireBuffer buffer,
+            @NonNull WireBuffer buffer,
             int componentId,
             int animationId,
             int horizontalPositioning,
@@ -182,7 +191,7 @@
         buffer.writeInt(verticalPositioning);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int componentId = buffer.readInt();
         int animationId = buffer.readInt();
         int horizontalPositioning = buffer.readInt();
@@ -196,7 +205,7 @@
                         verticalPositioning));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .description(
                         "Box layout implementation.\n\n"
@@ -224,7 +233,7 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mComponentId, mAnimationId, mHorizontalPositioning, mVerticalPositioning);
     }
 }
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 f799767..12ff969 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
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -44,6 +46,7 @@
         this(parent, componentId, animationId, 0, 0, 0, 0);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "CANVAS ["
@@ -62,11 +65,13 @@
                 + mVisibility;
     }
 
+    @NonNull
     @Override
     protected String getSerializedName() {
         return "CANVAS";
     }
 
+    @NonNull
     public static String name() {
         return "CanvasLayout";
     }
@@ -75,19 +80,19 @@
         return Operations.LAYOUT_CANVAS;
     }
 
-    public static void apply(WireBuffer buffer, int componentId, int animationId) {
+    public static void apply(@NonNull WireBuffer buffer, int componentId, int animationId) {
         buffer.start(Operations.LAYOUT_CANVAS);
         buffer.writeInt(componentId);
         buffer.writeInt(animationId);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int componentId = buffer.readInt();
         int animationId = buffer.readInt();
         operations.add(new CanvasLayout(null, componentId, animationId));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .description("Canvas implementation. Encapsulate draw operations.\n\n")
                 .field(INT, "COMPONENT_ID", "unique id for this component")
@@ -98,7 +103,7 @@
     }
 
     @Override
-    public void internalLayoutMeasure(PaintContext context, MeasurePass measure) {
+    public void internalLayoutMeasure(PaintContext context, @NonNull MeasurePass measure) {
         ComponentMeasure selfMeasure = measure.get(this);
         float selfWidth = selfMeasure.getW() - mPaddingLeft - mPaddingRight;
         float selfHeight = selfMeasure.getH() - mPaddingTop - mPaddingBottom;
@@ -112,7 +117,7 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mComponentId, mAnimationId);
     }
 }
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 402b784..52bf4c5 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
@@ -18,6 +18,8 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -87,6 +89,7 @@
                 spacedBy);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "COLUMN ["
@@ -105,14 +108,24 @@
                 + mVisibility;
     }
 
+    @NonNull
     @Override
     protected String getSerializedName() {
         return "COLUMN";
     }
 
     @Override
+    public boolean isInVerticalFill() {
+        return super.isInVerticalFill() || childrenHaveVerticalWeights();
+    }
+
+    @Override
     public void computeWrapSize(
-            PaintContext context, float maxWidth, float maxHeight, MeasurePass measure, Size size) {
+            PaintContext context,
+            float maxWidth,
+            float maxHeight,
+            @NonNull MeasurePass measure,
+            @NonNull Size size) {
         DebugLog.s(() -> "COMPUTE WRAP SIZE in " + this + " (" + mComponentId + ")");
         int visibleChildrens = 0;
         for (Component c : mChildrenComponents) {
@@ -137,7 +150,7 @@
             float maxWidth,
             float minHeight,
             float maxHeight,
-            MeasurePass measure) {
+            @NonNull MeasurePass measure) {
         DebugLog.s(() -> "COMPUTE SIZE in " + this + " (" + mComponentId + ")");
         float mh = maxHeight;
         for (Component child : mChildrenComponents) {
@@ -151,7 +164,7 @@
     }
 
     @Override
-    public void internalLayoutMeasure(PaintContext context, MeasurePass measure) {
+    public void internalLayoutMeasure(PaintContext context, @NonNull MeasurePass measure) {
         ComponentMeasure selfMeasure = measure.get(this);
         DebugLog.s(
                 () ->
@@ -302,6 +315,7 @@
         DebugLog.e();
     }
 
+    @NonNull
     public static String name() {
         return "ColumnLayout";
     }
@@ -311,7 +325,7 @@
     }
 
     public static void apply(
-            WireBuffer buffer,
+            @NonNull WireBuffer buffer,
             int componentId,
             int animationId,
             int horizontalPositioning,
@@ -325,7 +339,7 @@
         buffer.writeFloat(spacedBy);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int componentId = buffer.readInt();
         int animationId = buffer.readInt();
         int horizontalPositioning = buffer.readInt();
@@ -341,7 +355,7 @@
                         spacedBy));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .description(
                         "Column layout implementation, positioning components one"
@@ -374,7 +388,7 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(
                 buffer,
                 mComponentId,
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 308ed64..0c4d24a 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
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout.managers;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.PaintContext;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
 import com.android.internal.widget.remotecompose.core.operations.layout.Component;
@@ -27,7 +29,7 @@
 /** Base class for layout managers -- resizable components. */
 public abstract class LayoutManager extends LayoutComponent implements Measurable {
 
-    Size mCachedWrapSize = new Size(0f, 0f);
+    @NonNull Size mCachedWrapSize = new Size(0f, 0f);
 
     public LayoutManager(
             Component parent,
@@ -62,6 +64,38 @@
         // nothing here
     }
 
+    protected boolean childrenHaveHorizontalWeights() {
+        for (Component c : mChildrenComponents) {
+            if (c instanceof LayoutManager) {
+                LayoutManager m = (LayoutManager) c;
+                if (m.getWidthModifier() != null && m.getWidthModifier().hasWeight()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    protected boolean childrenHaveVerticalWeights() {
+        for (Component c : mChildrenComponents) {
+            if (c instanceof LayoutManager) {
+                LayoutManager m = (LayoutManager) c;
+                if (m.getHeightModifier() != null && m.getHeightModifier().hasWeight()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public boolean isInHorizontalFill() {
+        return mWidthModifier.isFill();
+    }
+
+    public boolean isInVerticalFill() {
+        return mHeightModifier.isFill();
+    }
+
     /** Base implementation of the measure resolution */
     @Override
     public void measure(
@@ -70,7 +104,7 @@
             float maxWidth,
             float minHeight,
             float maxHeight,
-            MeasurePass measure) {
+            @NonNull MeasurePass measure) {
         boolean hasWrap = true;
         float measuredWidth =
                 Math.min(maxWidth, computeModifierDefinedWidth() - mMarginLeft - mMarginRight);
@@ -87,7 +121,7 @@
         } else {
             hasWrap = false;
         }
-        if (mWidthModifier.isFill()) {
+        if (isInHorizontalFill()) {
             measuredWidth = insetMaxWidth;
         } else if (mWidthModifier.hasWeight()) {
             measuredWidth = Math.max(measuredWidth, computeModifierDefinedWidth());
@@ -95,7 +129,7 @@
             measuredWidth = Math.max(measuredWidth, minWidth);
             measuredWidth = Math.min(measuredWidth, insetMaxWidth);
         }
-        if (mHeightModifier.isFill()) {
+        if (isInVerticalFill()) {
             measuredHeight = insetMaxHeight;
         } else if (mHeightModifier.hasWeight()) {
             measuredHeight = Math.max(measuredHeight, computeModifierDefinedHeight());
@@ -136,7 +170,7 @@
 
     /** basic layout of internal components */
     @Override
-    public void layout(RemoteContext context, MeasurePass measure) {
+    public void layout(@NonNull RemoteContext context, @NonNull MeasurePass measure) {
         super.layout(context, measure);
         ComponentMeasure self = measure.get(this);
 
@@ -153,7 +187,7 @@
      * @param context
      * @param measure
      */
-    public void selfLayout(RemoteContext context, MeasurePass measure) {
+    public void selfLayout(@NonNull RemoteContext context, @NonNull MeasurePass measure) {
         super.layout(context, measure);
         ComponentMeasure self = measure.get(this);
 
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 b29a05c..a366dc8 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
@@ -18,6 +18,8 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -85,6 +87,7 @@
                 spacedBy);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "ROW ["
@@ -103,14 +106,24 @@
                 + mVisibility;
     }
 
+    @NonNull
     @Override
     protected String getSerializedName() {
         return "ROW";
     }
 
     @Override
+    public boolean isInHorizontalFill() {
+        return super.isInHorizontalFill() || childrenHaveHorizontalWeights();
+    }
+
+    @Override
     public void computeWrapSize(
-            PaintContext context, float maxWidth, float maxHeight, MeasurePass measure, Size size) {
+            PaintContext context,
+            float maxWidth,
+            float maxHeight,
+            @NonNull MeasurePass measure,
+            @NonNull Size size) {
         DebugLog.s(() -> "COMPUTE WRAP SIZE in " + this + " (" + mComponentId + ")");
         //        int visibleChildrens = 0;
         for (Component c : mChildrenComponents) {
@@ -135,7 +148,7 @@
             float maxWidth,
             float minHeight,
             float maxHeight,
-            MeasurePass measure) {
+            @NonNull MeasurePass measure) {
         DebugLog.s(() -> "COMPUTE SIZE in " + this + " (" + mComponentId + ")");
         float mw = maxWidth;
         for (Component child : mChildrenComponents) {
@@ -149,7 +162,7 @@
     }
 
     @Override
-    public void internalLayoutMeasure(PaintContext context, MeasurePass measure) {
+    public void internalLayoutMeasure(PaintContext context, @NonNull MeasurePass measure) {
         ComponentMeasure selfMeasure = measure.get(this);
         DebugLog.s(
                 () ->
@@ -305,6 +318,7 @@
         DebugLog.e();
     }
 
+    @NonNull
     public static String name() {
         return "RowLayout";
     }
@@ -314,7 +328,7 @@
     }
 
     public static void apply(
-            WireBuffer buffer,
+            @NonNull WireBuffer buffer,
             int componentId,
             int animationId,
             int horizontalPositioning,
@@ -328,7 +342,7 @@
         buffer.writeFloat(spacedBy);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int componentId = buffer.readInt();
         int animationId = buffer.readInt();
         int horizontalPositioning = buffer.readInt();
@@ -344,7 +358,7 @@
                         spacedBy));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .description(
                         "Row layout implementation, positioning components one"
@@ -377,7 +391,7 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(
                 buffer,
                 mComponentId,
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java
index b5c7281..e47ffde 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout.managers;
 
+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;
@@ -50,10 +52,10 @@
     // This keep track of all the components associated with a given Id,
     // (the key being the id), and the set of components corresponds to the set of states
     // TODO: we should be able to optimize this
-    public Map<Integer, Component[]> statePaintedComponents = new HashMap<>();
+    @NonNull public Map<Integer, Component[]> statePaintedComponents = new HashMap<>();
 
     public int MAX_CACHE_ELEMENTS = 16;
-    public int[] cacheListElementsId = new int[MAX_CACHE_ELEMENTS];
+    @NonNull public int[] cacheListElementsId = new int[MAX_CACHE_ELEMENTS];
 
     public boolean inTransition = false;
 
@@ -168,7 +170,7 @@
     }
 
     @Override
-    public void layout(RemoteContext context, MeasurePass measure) {
+    public void layout(@NonNull RemoteContext context, @NonNull MeasurePass measure) {
         ComponentMeasure self = measure.get(this);
         super.selfLayout(context, measure);
 
@@ -207,12 +209,12 @@
 
     @Override
     public void measure(
-            PaintContext context,
+            @NonNull PaintContext context,
             float minWidth,
             float maxWidth,
             float minHeight,
             float maxHeight,
-            MeasurePass measure) {
+            @NonNull MeasurePass measure) {
         // The general approach for this widget is to do most of the work/setup in measure.
         // layout and paint then simply use what's been setup in the measure phase.
 
@@ -364,19 +366,20 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         if (mIndexId != 0) {
             int newValue = context.getContext().mRemoteComposeState.getInteger(mIndexId);
             if (newValue != currentLayoutIndex) {
                 previousLayoutIndex = currentLayoutIndex;
                 currentLayoutIndex = newValue;
                 inTransition = true;
-                System.out.println("currentLayout index is $currentLayoutIndex");
+                // System.out.println("currentLayout index is $currentLayoutIndex");
                 // executeValueSetActions(getLayout(currentLayoutIndex));
                 invalidateMeasure();
             }
         }
-        System.out.println("PAINTING LAYOUT STATELAYOUT, CURRENT INDEX " + currentLayoutIndex);
+        //        System.out.println("PAINTING LAYOUT STATELAYOUT, CURRENT INDEX " +
+        // currentLayoutIndex);
         // Make sure to mark any components that are not in either the current or previous layout
         // as being GONE.
         int index = 0;
@@ -529,6 +532,7 @@
     //        }
     //    }
 
+    @NonNull
     @Override
     public String toString() {
         return "STATE_LAYOUT";
@@ -539,7 +543,7 @@
     //    }
 
     public static void apply(
-            WireBuffer buffer,
+            @NonNull WireBuffer buffer,
             int componentId,
             int animationId,
             int horizontalPositioning,
@@ -553,7 +557,7 @@
         buffer.writeInt(indexId);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int componentId = buffer.readInt();
         int animationId = buffer.readInt();
         buffer.readInt(); // horizontalPositioning
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 c1cabcd..8aa7712 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
@@ -18,6 +18,8 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -44,22 +46,24 @@
     private int mFontStyle = 0;
     private float mFontWeight = 400f;
     private int mFontFamilyId = -1;
+    private int mTextAlign = -1;
 
     private int mType = -1;
     private float mTextX;
     private float mTextY;
+    private float mTextW;
 
     private String mCachedString = "";
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         if (mTextId != -1) {
             context.listensTo(mTextId, this);
         }
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         mCachedString = context.getText(mTextId);
         if (mType == -1) {
             if (mFontFamilyId != -1) {
@@ -97,7 +101,8 @@
             float fontSize,
             int fontStyle,
             float fontWeight,
-            int fontFamilyId) {
+            int fontFamilyId,
+            int textAlign) {
         super(parent, componentId, animationId, x, y, width, height);
         mTextId = textId;
         mColor = color;
@@ -105,6 +110,7 @@
         mFontStyle = fontStyle;
         mFontWeight = fontWeight;
         mFontFamilyId = fontFamilyId;
+        mTextAlign = textAlign;
     }
 
     public TextLayout(
@@ -116,7 +122,8 @@
             float fontSize,
             int fontStyle,
             float fontWeight,
-            int fontFamilyId) {
+            int fontFamilyId,
+            int textAlign) {
         this(
                 parent,
                 componentId,
@@ -130,13 +137,14 @@
                 fontSize,
                 fontStyle,
                 fontWeight,
-                fontFamilyId);
+                fontFamilyId,
+                textAlign);
     }
 
-    public PaintBundle mPaint = new PaintBundle();
+    @NonNull public PaintBundle mPaint = new PaintBundle();
 
     @Override
-    public void paintingComponent(PaintContext context) {
+    public void paintingComponent(@NonNull PaintContext context) {
         context.save();
         context.translate(mX, mY);
         mComponentModifiers.paint(context);
@@ -176,6 +184,7 @@
         context.restore();
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "TEXT_LAYOUT ["
@@ -194,13 +203,14 @@
                 + mVisibility;
     }
 
+    @NonNull
     @Override
     protected String getSerializedName() {
         return "TEXT_LAYOUT";
     }
 
     @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
         serializer.append(
                 indent,
                 getSerializedName()
@@ -228,7 +238,11 @@
 
     @Override
     public void computeWrapSize(
-            PaintContext context, float maxWidth, float maxHeight, MeasurePass measure, Size size) {
+            @NonNull PaintContext context,
+            float maxWidth,
+            float maxHeight,
+            MeasurePass measure,
+            @NonNull Size size) {
         context.savePaint();
         mPaint.reset();
         mPaint.setTextSize(mFontSize);
@@ -244,8 +258,10 @@
         mTextX = -bounds[0];
         size.setHeight(h);
         mTextY = -bounds[1];
+        mTextW = w;
     }
 
+    @NonNull
     public static String name() {
         return "TextLayout";
     }
@@ -255,7 +271,7 @@
     }
 
     public static void apply(
-            WireBuffer buffer,
+            @NonNull WireBuffer buffer,
             int componentId,
             int animationId,
             int textId,
@@ -263,7 +279,8 @@
             float fontSize,
             int fontStyle,
             float fontWeight,
-            int fontFamilyId) {
+            int fontFamilyId,
+            int textAlign) {
         buffer.start(id());
         buffer.writeInt(componentId);
         buffer.writeInt(animationId);
@@ -273,9 +290,10 @@
         buffer.writeInt(fontStyle);
         buffer.writeFloat(fontWeight);
         buffer.writeInt(fontFamilyId);
+        buffer.writeInt(textAlign);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int componentId = buffer.readInt();
         int animationId = buffer.readInt();
         int textId = buffer.readInt();
@@ -284,6 +302,7 @@
         int fontStyle = buffer.readInt();
         float fontWeight = buffer.readFloat();
         int fontFamilyId = buffer.readInt();
+        int textAlign = buffer.readInt();
         operations.add(
                 new TextLayout(
                         null,
@@ -294,10 +313,11 @@
                         fontSize,
                         fontStyle,
                         fontWeight,
-                        fontFamilyId));
+                        fontFamilyId,
+                        textAlign));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .description("Text layout implementation.\n\n")
                 .field(INT, "COMPONENT_ID", "unique id for this component")
@@ -313,7 +333,7 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(
                 buffer,
                 mComponentId,
@@ -323,6 +343,7 @@
                 mFontSize,
                 mFontStyle,
                 mFontWeight,
-                mFontFamilyId);
+                mFontFamilyId,
+                mTextAlign);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java
index 285425f..426e023 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout.measure;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.operations.layout.Component;
 
 /** Encapsulate the result of a measure pass for a component */
@@ -80,7 +82,7 @@
         this(id, x, y, w, h, Component.Visibility.VISIBLE);
     }
 
-    public ComponentMeasure(Component component) {
+    public ComponentMeasure(@NonNull Component component) {
         this(
                 component.getComponentId(),
                 component.getX(),
@@ -90,7 +92,7 @@
                 component.mVisibility);
     }
 
-    public void copyFrom(ComponentMeasure m) {
+    public void copyFrom(@NonNull ComponentMeasure m) {
         mX = m.mX;
         mY = m.mY;
         mW = m.mW;
@@ -98,7 +100,7 @@
         mVisibility = m.mVisibility;
     }
 
-    public boolean same(ComponentMeasure m) {
+    public boolean same(@NonNull ComponentMeasure m) {
         return mX == m.mX && mY == m.mY && mW == m.mW && mH == m.mH && mVisibility == m.mVisibility;
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/MeasurePass.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/MeasurePass.java
index 8d01fea..112ab1b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/MeasurePass.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/MeasurePass.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout.measure;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.operations.layout.Component;
 
 import java.util.HashMap;
@@ -24,13 +26,13 @@
  * array vs the current hashmap
  */
 public class MeasurePass {
-    HashMap<Integer, ComponentMeasure> mList = new HashMap<>();
+    @NonNull HashMap<Integer, ComponentMeasure> mList = new HashMap<>();
 
     public void clear() {
         mList.clear();
     }
 
-    public void add(ComponentMeasure measure) throws Exception {
+    public void add(@NonNull ComponentMeasure measure) throws Exception {
         if (measure.mId == -1) {
             throw new Exception("Component has no id!");
         }
@@ -41,7 +43,7 @@
         return mList.containsKey(id);
     }
 
-    public ComponentMeasure get(Component c) {
+    public ComponentMeasure get(@NonNull Component c) {
         if (!mList.containsKey(c.getComponentId())) {
             ComponentMeasure measure =
                     new ComponentMeasure(
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 64e40f7..76a97ca 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
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -42,7 +44,7 @@
     float mA;
     int mShapeType = ShapeType.RECTANGLE;
 
-    public PaintBundle mPaint = new PaintBundle();
+    @NonNull public PaintBundle mPaint = new PaintBundle();
 
     public BackgroundModifierOperation(
             float x,
@@ -66,12 +68,12 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mX, mY, mWidth, mHeight, mR, mG, mB, mA, mShapeType);
     }
 
     @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
         serializer.append(
                 indent,
                 "BACKGROUND = ["
@@ -101,11 +103,13 @@
         this.mHeight = height;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "BackgroundModifierOperation(" + mWidth + " x " + mHeight + ")";
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -115,7 +119,7 @@
     }
 
     public static void apply(
-            WireBuffer buffer,
+            @NonNull WireBuffer buffer,
             float x,
             float y,
             float width,
@@ -138,7 +142,7 @@
         buffer.writeInt(shapeType);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         float x = buffer.readFloat();
         float y = buffer.readFloat();
         float width = buffer.readFloat();
@@ -153,7 +157,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.savePaint();
         mPaint.reset();
         mPaint.setStyle(PaintBundle.STYLE_FILL);
@@ -167,7 +171,7 @@
         context.restorePaint();
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Modifier Operations", OP_CODE, CLASS_NAME)
                 .description("define the Background Modifier")
                 .field(FLOAT, "x", "")
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 92c0a73..d48a9c7 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
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -45,7 +47,7 @@
     float mA;
     int mShapeType = ShapeType.RECTANGLE;
 
-    public PaintBundle paint = new PaintBundle();
+    @NonNull public PaintBundle paint = new PaintBundle();
 
     public BorderModifierOperation(
             float x,
@@ -73,7 +75,7 @@
     }
 
     @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
         serializer.append(
                 indent,
                 "BORDER = ["
@@ -105,7 +107,7 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(
                 buffer,
                 mX,
@@ -127,6 +129,7 @@
         this.mHeight = height;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "BorderModifierOperation("
@@ -152,6 +155,7 @@
                 + ")";
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -161,7 +165,7 @@
     }
 
     public static void apply(
-            WireBuffer buffer,
+            @NonNull WireBuffer buffer,
             float x,
             float y,
             float width,
@@ -188,7 +192,7 @@
         buffer.writeInt(shapeType);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         float x = buffer.readFloat();
         float y = buffer.readFloat();
         float width = buffer.readFloat();
@@ -206,7 +210,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.savePaint();
         paint.reset();
         paint.setColor(mR, mG, mB, mA);
@@ -225,7 +229,7 @@
         context.restorePaint();
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Modifier Operations", OP_CODE, CLASS_NAME)
                 .description("define the Border Modifier")
                 .field(FLOAT, "x", "")
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 0d8aeaa..78b51c3 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
@@ -15,14 +15,14 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout.modifiers;
 
-import com.android.internal.widget.remotecompose.core.CoreDocument;
+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.utilities.StringSerializer;
 
 import java.util.List;
@@ -35,7 +35,7 @@
     float mHeight;
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.clipRect(0f, 0f, mWidth, mHeight);
     }
 
@@ -46,21 +46,16 @@
     }
 
     @Override
-    public void onClick(
-            RemoteContext context, CoreDocument document, Component component, float x, float y) {
-        // nothing
-    }
-
-    @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
         serializer.append(indent, "CLIP_RECT = [" + mWidth + ", " + mHeight + "]");
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer);
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -69,15 +64,15 @@
         return OP_CODE;
     }
 
-    public static void apply(WireBuffer buffer) {
+    public static void apply(@NonNull WireBuffer buffer) {
         buffer.start(OP_CODE);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(WireBuffer buffer, @NonNull List<Operation> operations) {
         operations.add(new ClipRectModifierOperation());
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    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 95786a8..011d7ed 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
@@ -15,6 +15,8 @@
  */
 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.PaintContext;
 import com.android.internal.widget.remotecompose.core.PaintOperation;
@@ -22,29 +24,34 @@
 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;
+import com.android.internal.widget.remotecompose.core.operations.layout.ClickHandler;
 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.DecoratorComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.TouchHandler;
 import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
 
 import java.util.ArrayList;
 
 /** Maintain a list of modifiers */
-public class ComponentModifiers extends PaintOperation implements DecoratorComponent {
-    ArrayList<ModifierOperation> mList = new ArrayList<>();
+public class ComponentModifiers extends PaintOperation
+        implements DecoratorComponent, ClickHandler, TouchHandler {
+    @NonNull ArrayList<ModifierOperation> mList = new ArrayList<>();
 
+    @NonNull
     public ArrayList<ModifierOperation> getList() {
         return mList;
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         super.apply(context);
         for (ModifierOperation op : mList) {
             op.apply(context);
         }
     }
 
+    @NonNull
     @Override
     public String toString() {
         String str = "ComponentModifiers \n";
@@ -59,7 +66,7 @@
         // nothing
     }
 
-    public void serializeToString(int indent, StringSerializer serializer) {
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
         serializer.append(indent, "MODIFIERS");
         for (ModifierOperation m : mList) {
             m.serializeToString(indent + 1, serializer);
@@ -75,7 +82,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         float tx = 0f;
         float ty = 0f;
         for (ModifierOperation op : mList) {
@@ -127,8 +134,38 @@
     public void onClick(
             RemoteContext context, CoreDocument document, Component component, float x, float y) {
         for (ModifierOperation op : mList) {
-            if (op instanceof DecoratorComponent) {
-                ((DecoratorComponent) op).onClick(context, document, component, x, y);
+            if (op instanceof ClickHandler) {
+                ((ClickHandler) op).onClick(context, document, component, x, y);
+            }
+        }
+    }
+
+    @Override
+    public void onTouchDown(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+        for (ModifierOperation op : mList) {
+            if (op instanceof TouchHandler) {
+                ((TouchHandler) op).onTouchDown(context, document, component, x, y);
+            }
+        }
+    }
+
+    @Override
+    public void onTouchUp(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+        for (ModifierOperation op : mList) {
+            if (op instanceof TouchHandler) {
+                ((TouchHandler) op).onTouchUp(context, document, component, x, y);
+            }
+        }
+    }
+
+    @Override
+    public void onTouchCancel(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+        for (ModifierOperation op : mList) {
+            if (op instanceof TouchHandler) {
+                ((TouchHandler) op).onTouchCancel(context, document, component, x, y);
             }
         }
     }
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 312d016..26e737b3 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
@@ -17,7 +17,9 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
-import com.android.internal.widget.remotecompose.core.CoreDocument;
+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;
@@ -37,49 +39,52 @@
     private static final int OP_CODE = Operations.MODIFIER_VISIBILITY;
 
     int mVisibilityId;
-    Component.Visibility mVisibility = Component.Visibility.VISIBLE;
+    @NonNull Component.Visibility mVisibility = Component.Visibility.VISIBLE;
     private LayoutComponent mParent;
 
     public ComponentVisibilityOperation(int id) {
         mVisibilityId = id;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "ComponentVisibilityOperation(" + mVisibilityId + ")";
     }
 
+    @NonNull
     public String serializedName() {
         return "COMPONENT_VISIBILITY";
     }
 
     @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
         serializer.append(indent, serializedName() + " = " + mVisibilityId);
     }
 
     @Override
     public void apply(RemoteContext context) {}
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@Nullable String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
     @Override
     public void write(WireBuffer buffer) {}
 
-    public static void apply(WireBuffer buffer, int valueId) {
+    public static void apply(@NonNull WireBuffer buffer, int valueId) {
         buffer.start(OP_CODE);
         buffer.writeInt(valueId);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int valueId = buffer.readInt();
         operations.add(new ComponentVisibilityOperation(valueId));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", OP_CODE, "ComponentVisibility")
                 .description(
                         "This operation allows setting a component"
@@ -88,12 +93,12 @@
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         context.listensTo(mVisibilityId, this);
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         int visibility = context.getInteger(mVisibilityId);
         if (visibility == Component.Visibility.VISIBLE.ordinal()) {
             mVisibility = Component.Visibility.VISIBLE;
@@ -115,8 +120,4 @@
 
     @Override
     public void layout(RemoteContext context, float width, float height) {}
-
-    @Override
-    public void onClick(
-            RemoteContext context, CoreDocument document, Component component, float x, float y) {}
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DecoratorModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DecoratorModifierOperation.java
index 41e18cb..b4c4108 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DecoratorModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DecoratorModifierOperation.java
@@ -15,10 +15,7 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout.modifiers;
 
-import com.android.internal.widget.remotecompose.core.CoreDocument;
 import com.android.internal.widget.remotecompose.core.PaintOperation;
-import com.android.internal.widget.remotecompose.core.RemoteContext;
-import com.android.internal.widget.remotecompose.core.operations.layout.Component;
 import com.android.internal.widget.remotecompose.core.operations.layout.DecoratorComponent;
 
 /**
@@ -26,11 +23,4 @@
  * output (background, border...)
  */
 public abstract class DecoratorModifierOperation extends PaintOperation
-        implements ModifierOperation, DecoratorComponent {
-
-    @Override
-    public void onClick(
-            RemoteContext context, CoreDocument document, Component component, float x, float y) {
-        // nothing
-    }
-}
+        implements ModifierOperation, DecoratorComponent {}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionModifierOperation.java
index 408bebc..3c2d85c 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionModifierOperation.java
@@ -15,6 +15,9 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout.modifiers;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.RemoteContext;
 import com.android.internal.widget.remotecompose.core.VariableSupport;
 import com.android.internal.widget.remotecompose.core.operations.Utils;
@@ -29,8 +32,10 @@
         WRAP,
         WEIGHT,
         INTRINSIC_MIN,
-        INTRINSIC_MAX;
+        INTRINSIC_MAX,
+        EXACT_DP;
 
+        @NonNull
         static Type fromInt(int value) {
             switch (value) {
                 case 0:
@@ -45,6 +50,8 @@
                     return INTRINSIC_MIN;
                 case 5:
                     return INTRINSIC_MAX;
+                case 6:
+                    return EXACT_DP;
             }
             return EXACT;
         }
@@ -68,19 +75,32 @@
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         if (mType == Type.EXACT) {
             mOutValue = Float.isNaN(mValue) ? context.getFloat(Utils.idFromNan(mValue)) : mValue;
         }
+        if (mType == Type.EXACT_DP) {
+            float pre = mOutValue;
+            mOutValue = Float.isNaN(mValue) ? context.getFloat(Utils.idFromNan(mValue)) : mValue;
+            mOutValue *= context.getDensity();
+            if (pre != mOutValue) {
+                context.getDocument().getRootLayoutComponent().invalidateMeasure();
+            }
+        }
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         if (mType == Type.EXACT) {
             if (Float.isNaN(mValue)) {
                 context.listensTo(Utils.idFromNan(mValue), this);
             }
         }
+        if (mType == Type.EXACT_DP) {
+            if (Float.isNaN(mValue)) {
+                context.listensTo(Utils.idFromNan(mValue), this);
+            }
+        }
     }
 
     public boolean hasWeight() {
@@ -107,25 +127,31 @@
         mOutValue = mValue = value;
     }
 
+    @NonNull
     public String serializedName() {
         return "DIMENSION";
     }
 
     @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
         if (mType == Type.EXACT) {
             serializer.append(indent, serializedName() + " = " + mValue);
         }
+        if (mType == Type.EXACT_DP) {
+            serializer.append(indent, serializedName() + " = " + mValue + " dp");
+        }
     }
 
     @Override
     public void apply(RemoteContext context) {}
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@Nullable String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "DimensionModifierOperation(" + mValue + ")";
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
new file mode 100644
index 0000000..2b30382
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java
@@ -0,0 +1,302 @@
+/*
+ * 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 static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
+
+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.AnimatableValue;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+import java.util.List;
+
+/**
+ * Represents a padding modifier. Padding modifiers can be chained and will impact following
+ * modifiers.
+ */
+public class GraphicsLayerModifierOperation extends DecoratorModifierOperation {
+    private static final int OP_CODE = Operations.MODIFIER_GRAPHICS_LAYER;
+    public static final String CLASS_NAME = "GraphicsLayerModifierOperation";
+
+    AnimatableValue mScaleX;
+    AnimatableValue mScaleY;
+    AnimatableValue mRotationX;
+    AnimatableValue mRotationY;
+    AnimatableValue mRotationZ;
+    AnimatableValue mTransformOriginX;
+    AnimatableValue mTransformOriginY;
+    AnimatableValue mShadowElevation;
+    AnimatableValue mAlpha;
+    AnimatableValue mCameraDistance;
+    int mBlendMode;
+    int mSpotShadowColorId;
+    int mAmbientShadowColorId;
+    int mColorFilterId;
+    int mRenderEffectId;
+
+    public GraphicsLayerModifierOperation(
+            float scaleX,
+            float scaleY,
+            float rotationX,
+            float rotationY,
+            float rotationZ,
+            float shadowElevation,
+            float transformOriginX,
+            float transformOriginY,
+            float alpha,
+            float cameraDistance,
+            int blendMode,
+            int spotShadowColorId,
+            int ambientShadowColorId,
+            int colorFilterId,
+            int renderEffectId) {
+        mScaleX = new AnimatableValue(scaleX);
+        mScaleY = new AnimatableValue(scaleY);
+        mRotationX = new AnimatableValue(rotationX);
+        mRotationY = new AnimatableValue(rotationY);
+        mRotationZ = new AnimatableValue(rotationZ);
+        mShadowElevation = new AnimatableValue(shadowElevation);
+        mTransformOriginX = new AnimatableValue(transformOriginX);
+        mTransformOriginY = new AnimatableValue(transformOriginY);
+        mAlpha = new AnimatableValue(alpha);
+        mCameraDistance = new AnimatableValue(cameraDistance);
+        mBlendMode = blendMode;
+        mSpotShadowColorId = spotShadowColorId;
+        mAmbientShadowColorId = ambientShadowColorId;
+        mColorFilterId = colorFilterId;
+        mRenderEffectId = renderEffectId;
+    }
+
+    public float getScaleX() {
+        return mScaleX.getValue();
+    }
+
+    public float getScaleY() {
+        return mScaleY.getValue();
+    }
+
+    public float getRotationX() {
+        return mRotationX.getValue();
+    }
+
+    public float getRotationY() {
+        return mRotationY.getValue();
+    }
+
+    public float getRotationZ() {
+        return mRotationZ.getValue();
+    }
+
+    public float getShadowElevation() {
+        return mShadowElevation.getValue();
+    }
+
+    public float getTransformOriginX() {
+        return mTransformOriginX.getValue();
+    }
+
+    public float getTransformOriginY() {
+        return mTransformOriginY.getValue();
+    }
+
+    public float getAlpha() {
+        return mAlpha.getValue();
+    }
+
+    public float getCameraDistance() {
+        return mCameraDistance.getValue();
+    }
+
+    // TODO: add implementation for blendmode
+    public int getBlendModeId() {
+        return mBlendMode;
+    }
+
+    // TODO: add implementation for shadow
+    public int getSpotShadowColorId() {
+        return mSpotShadowColorId;
+    }
+
+    public int getAmbientShadowColorId() {
+        return mAmbientShadowColorId;
+    }
+
+    // TODO: add implementation for color filters
+    public int getColorFilterId() {
+        return mColorFilterId;
+    }
+
+    public int getRenderEffectId() {
+        return mRenderEffectId;
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        apply(
+                buffer,
+                mScaleX.getValue(),
+                mScaleY.getValue(),
+                mRotationX.getValue(),
+                mRotationY.getValue(),
+                mRotationZ.getValue(),
+                mShadowElevation.getValue(),
+                mTransformOriginX.getValue(),
+                mTransformOriginY.getValue(),
+                mAlpha.getValue(),
+                mCameraDistance.getValue(),
+                mBlendMode,
+                mSpotShadowColorId,
+                mAmbientShadowColorId,
+                mColorFilterId,
+                mRenderEffectId);
+    }
+
+    @Override
+    public void serializeToString(int indent, StringSerializer serializer) {
+        serializer.append(indent, "GRAPHICS_LAYER = [" + mScaleX + ", " + mScaleY + "]");
+    }
+
+    @Override
+    public String deepToString(String indent) {
+        return (indent != null ? indent : "") + toString();
+    }
+
+    @Override
+    public void paint(PaintContext context) {
+        mScaleX.evaluate(context);
+        mScaleY.evaluate(context);
+        mRotationX.evaluate(context);
+        mRotationY.evaluate(context);
+        mRotationZ.evaluate(context);
+        mTransformOriginX.evaluate(context);
+        mTransformOriginY.evaluate(context);
+        mShadowElevation.evaluate(context);
+        mAlpha.evaluate(context);
+        mCameraDistance.evaluate(context);
+    }
+
+    @Override
+    public String toString() {
+        return "GraphicsLayerModifierOperation(" + mScaleX + ", " + mScaleY + ")";
+    }
+
+    public static String name() {
+        return CLASS_NAME;
+    }
+
+    public static int id() {
+        return OP_CODE;
+    }
+
+    public static void apply(
+            WireBuffer buffer,
+            float scaleX,
+            float scaleY,
+            float rotationX,
+            float rotationY,
+            float rotationZ,
+            float shadowElevation,
+            float transformOriginX,
+            float transformOriginY,
+            float alpha,
+            float cameraDistance,
+            int blendMode,
+            int spotShadowColorId,
+            int ambientShadowColorId,
+            int colorFilterId,
+            int renderEffectId) {
+        buffer.start(OP_CODE);
+        buffer.writeFloat(scaleX);
+        buffer.writeFloat(scaleY);
+        buffer.writeFloat(rotationX);
+        buffer.writeFloat(rotationY);
+        buffer.writeFloat(rotationZ);
+        buffer.writeFloat(shadowElevation);
+        buffer.writeFloat(transformOriginX);
+        buffer.writeFloat(transformOriginY);
+        buffer.writeFloat(alpha);
+        buffer.writeFloat(cameraDistance);
+        buffer.writeInt(blendMode);
+        buffer.writeInt(spotShadowColorId);
+        buffer.writeInt(ambientShadowColorId);
+        buffer.writeInt(colorFilterId);
+        buffer.writeInt(renderEffectId);
+    }
+
+    public static void read(WireBuffer buffer, List<Operation> operations) {
+        float scaleX = buffer.readFloat();
+        float scaleY = buffer.readFloat();
+        float rotationX = buffer.readFloat();
+        float rotationY = buffer.readFloat();
+        float rotationZ = buffer.readFloat();
+        float shadowElevation = buffer.readFloat();
+        float transformOriginX = buffer.readFloat();
+        float transformOriginY = buffer.readFloat();
+        float alpha = buffer.readFloat();
+        float cameraDistance = buffer.readFloat();
+        int blendMode = buffer.readInt();
+        int spotShadowColorId = buffer.readInt();
+        int ambientShadowColorId = buffer.readInt();
+        int colorFilterId = buffer.readInt();
+        int renderEffectId = buffer.readInt();
+        operations.add(
+                new GraphicsLayerModifierOperation(
+                        scaleX,
+                        scaleY,
+                        rotationX,
+                        rotationY,
+                        rotationZ,
+                        shadowElevation,
+                        transformOriginX,
+                        transformOriginY,
+                        alpha,
+                        cameraDistance,
+                        blendMode,
+                        spotShadowColorId,
+                        ambientShadowColorId,
+                        colorFilterId,
+                        renderEffectId));
+    }
+
+    public static void documentation(DocumentationBuilder doc) {
+        doc.operation("Modifier Operations", OP_CODE, CLASS_NAME)
+                .description("define the GraphicsLayer Modifier")
+                .field(FLOAT, "scaleX", "")
+                .field(FLOAT, "scaleY", "")
+                .field(FLOAT, "rotationX", "")
+                .field(FLOAT, "rotationY", "")
+                .field(FLOAT, "rotationZ", "")
+                .field(FLOAT, "shadowElevation", "")
+                .field(FLOAT, "transformOriginX", "")
+                .field(FLOAT, "transformOriginY", "")
+                .field(FLOAT, "alpha", "")
+                .field(FLOAT, "cameraDistance", "")
+                .field(INT, "blendMode", "")
+                .field(INT, "spotShadowColorId", "")
+                .field(INT, "ambientShadowColorId", "")
+                .field(INT, "colorFilterId", "")
+                .field(INT, "renderEffectId", "");
+    }
+
+    @Override
+    public void layout(RemoteContext context, 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 d3613f8..97c76c0 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
@@ -18,6 +18,8 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.WireBuffer;
@@ -30,6 +32,7 @@
     private static final int OP_CODE = Operations.MODIFIER_HEIGHT;
     public static final String CLASS_NAME = "HeightModifierOperation";
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -38,13 +41,13 @@
         return OP_CODE;
     }
 
-    public static void apply(WireBuffer buffer, int type, float value) {
+    public static void apply(@NonNull WireBuffer buffer, int type, float value) {
         buffer.start(OP_CODE);
         buffer.writeInt(type);
         buffer.writeFloat(value);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Type type = Type.fromInt(buffer.readInt());
         float value = buffer.readFloat();
         Operation op = new HeightModifierOperation(type, value);
@@ -52,7 +55,7 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mType.ordinal(), mValue);
     }
 
@@ -68,17 +71,19 @@
         super(value);
     }
 
+    @NonNull
     @Override
     public String toString() {
-        return "Height(" + mValue + ")";
+        return "Height(" + mType + ", " + mValue + ")";
     }
 
+    @NonNull
     @Override
     public String serializedName() {
         return "HEIGHT";
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Modifier Operations", OP_CODE, CLASS_NAME)
                 .description("define the animation")
                 .field(INT, "type", "")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java
index ac42470a..836321f 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
@@ -17,6 +17,9 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.CoreDocument;
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
@@ -39,6 +42,7 @@
         mActionId = id;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "HostActionOperation(" + mActionId + ")";
@@ -48,20 +52,22 @@
         return mActionId;
     }
 
+    @NonNull
     public String serializedName() {
         return "HOST_ACTION";
     }
 
     @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
         serializer.append(indent, serializedName() + " = " + mActionId);
     }
 
     @Override
     public void apply(RemoteContext context) {}
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@Nullable String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
@@ -70,21 +76,25 @@
 
     @Override
     public void runAction(
-            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+            @NonNull RemoteContext context,
+            CoreDocument document,
+            Component component,
+            float x,
+            float y) {
         context.runAction(mActionId, "");
     }
 
-    public static void apply(WireBuffer buffer, int actionId) {
+    public static void apply(@NonNull WireBuffer buffer, int actionId) {
         buffer.start(OP_CODE);
         buffer.writeInt(actionId);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int actionId = buffer.readInt();
         operations.add(new HostActionOperation(actionId));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", OP_CODE, "HostAction")
                 .description("Host action. This operation represents a host action")
                 .field(INT, "ACTION_ID", "Host Action ID");
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 b674a58..e97e897 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
@@ -17,6 +17,9 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.CoreDocument;
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
@@ -33,31 +36,47 @@
 public class HostNamedActionOperation implements ActionOperation {
     private static final int OP_CODE = Operations.HOST_NAMED_ACTION;
 
-    int mTextId = -1;
+    public static final int FLOAT_TYPE = 0;
+    public static final int INT_TYPE = 1;
+    public static final int STRING_TYPE = 2;
+    public static final int NONE_TYPE = -1;
 
-    public HostNamedActionOperation(int id) {
+    int mTextId = -1;
+    int mType = NONE_TYPE;
+    int mValueId = -1;
+
+    public HostNamedActionOperation(int id, int type, int valueId) {
         mTextId = id;
+        mType = type;
+        mValueId = valueId;
     }
 
+    @NonNull
     @Override
     public String toString() {
-        return "HostNamedActionOperation(" + mTextId + ")";
+        return "HostNamedActionOperation(" + mTextId + " : " + mValueId + ")";
     }
 
+    @NonNull
     public String serializedName() {
         return "HOST_NAMED_ACTION";
     }
 
     @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
-        serializer.append(indent, serializedName() + " = " + mTextId);
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
+        if (mValueId != -1) {
+            serializer.append(indent, serializedName() + " = " + mTextId + " : " + mValueId);
+        } else {
+            serializer.append(indent, serializedName() + " = " + mTextId);
+        }
     }
 
     @Override
     public void apply(RemoteContext context) {}
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@Nullable String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
@@ -66,23 +85,42 @@
 
     @Override
     public void runAction(
-            RemoteContext context, CoreDocument document, Component component, float x, float y) {
-        context.runNamedAction(mTextId);
+            @NonNull RemoteContext context,
+            CoreDocument document,
+            Component component,
+            float x,
+            float y) {
+        Object value = null;
+        if (mValueId != -1) {
+            if (mType == INT_TYPE) {
+                value = context.mRemoteComposeState.getInteger(mValueId);
+            } else if (mType == STRING_TYPE) {
+                value = context.mRemoteComposeState.getFromId(mValueId);
+            } else if (mType == FLOAT_TYPE) {
+                value = context.mRemoteComposeState.getFloat(mValueId);
+            }
+        }
+        context.runNamedAction(mTextId, value);
     }
 
-    public static void apply(WireBuffer buffer, int textId) {
+    public static void apply(@NonNull WireBuffer buffer, int textId, int type, int valueId) {
         buffer.start(OP_CODE);
         buffer.writeInt(textId);
+        buffer.writeInt(type);
+        buffer.writeInt(valueId);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int textId = buffer.readInt();
-        operations.add(new HostNamedActionOperation(textId));
+        int type = buffer.readInt();
+        int valueId = buffer.readInt();
+        operations.add(new HostNamedActionOperation(textId, type, valueId));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", OP_CODE, "HostNamedAction")
                 .description("Host Named action. This operation represents a host action")
-                .field(INT, "TEXT_ID", "Named Host Action Text ID");
+                .field(INT, "TEXT_ID", "Named Host Action Text ID")
+                .field(INT, "VALUE_ID", "Named Host Action Value ID");
     }
 }
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
new file mode 100644
index 0000000..65fe345
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java
@@ -0,0 +1,119 @@
+/*
+ * 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 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.utilities.StringSerializer;
+
+import java.util.List;
+
+/** Represents an offset modifier. */
+public class OffsetModifierOperation extends DecoratorModifierOperation {
+    private static final int OP_CODE = Operations.MODIFIER_OFFSET;
+    public static final String CLASS_NAME = "OffsetModifierOperation";
+
+    float mX;
+    float mY;
+
+    public OffsetModifierOperation(float x, float y) {
+        this.mX = x;
+        this.mY = y;
+    }
+
+    public float getX() {
+        return mX;
+    }
+
+    public float getY() {
+        return mY;
+    }
+
+    public void setX(float x) {
+        this.mX = x;
+    }
+
+    public void setY(float y) {
+        this.mY = y;
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        apply(buffer, mX, mY);
+    }
+
+    // @Override
+    public void serializeToString(int indent, StringSerializer serializer) {
+        serializer.append(indent, "OFFSET = [" + mX + ", " + mY + "]");
+    }
+
+    @Override
+    public String deepToString(String indent) {
+        return (indent != null ? indent : "") + toString();
+    }
+
+    @Override
+    public void paint(PaintContext context) {
+        float x = context.getContext().mRemoteComposeState.getFloat(Utils.idFromNan(mX));
+        float y = context.getContext().mRemoteComposeState.getFloat(Utils.idFromNan(mY));
+        float density = context.getContext().getDensity();
+        x *= density;
+        y *= density;
+        context.translate(x, y);
+    }
+
+    @Override
+    public String toString() {
+        return "OffsetModifierOperation(" + mX + ", " + mY + ")";
+    }
+
+    public static String name() {
+        return CLASS_NAME;
+    }
+
+    public static int id() {
+        return OP_CODE;
+    }
+
+    public static void apply(WireBuffer buffer, float x, float y) {
+        buffer.start(OP_CODE);
+        buffer.writeFloat(x);
+        buffer.writeFloat(y);
+    }
+
+    public static void read(WireBuffer buffer, List<Operation> operations) {
+        float x = buffer.readFloat();
+        float y = buffer.readFloat();
+        operations.add(new OffsetModifierOperation(x, y));
+    }
+
+    public static void documentation(DocumentationBuilder doc) {
+        doc.operation("Modifier Operations", OP_CODE, CLASS_NAME)
+                .description("define the Offset Modifier")
+                .field(FLOAT, "x", "")
+                .field(FLOAT, "y", "");
+    }
+
+    @Override
+    public void layout(RemoteContext context, 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 e0ec1a6..ed5522e 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
@@ -17,6 +17,9 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -78,12 +81,12 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mLeft, mTop, mRight, mBottom);
     }
 
     @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
         serializer.append(
                 indent, "PADDING = [" + mLeft + ", " + mTop + ", " + mRight + ", " + mBottom + "]");
     }
@@ -91,11 +94,13 @@
     @Override
     public void apply(RemoteContext context) {}
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@Nullable String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "PaddingModifierOperation("
@@ -109,6 +114,7 @@
                 + ")";
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -117,7 +123,8 @@
         return Operations.MODIFIER_PADDING;
     }
 
-    public static void apply(WireBuffer buffer, float left, float top, float right, float bottom) {
+    public static void apply(
+            @NonNull WireBuffer buffer, float left, float top, float right, float bottom) {
         buffer.start(Operations.MODIFIER_PADDING);
         buffer.writeFloat(left);
         buffer.writeFloat(top);
@@ -125,7 +132,7 @@
         buffer.writeFloat(bottom);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         float left = buffer.readFloat();
         float top = buffer.readFloat();
         float right = buffer.readFloat();
@@ -133,7 +140,7 @@
         operations.add(new PaddingModifierOperation(left, top, right, bottom));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Modifier Operations", OP_CODE, CLASS_NAME)
                 .description("define the Padding Modifier")
                 .field(FLOAT, "left", "")
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 dc95fe7..6218dd5 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
@@ -17,7 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
 
-import com.android.internal.widget.remotecompose.core.CoreDocument;
+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;
@@ -25,7 +26,6 @@
 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;
 
@@ -37,7 +37,7 @@
     public static final int OP_CODE = Operations.MODIFIER_ROUNDED_CLIP_RECT;
     public static final String CLASS_NAME = "RoundedClipRectModifierOperation";
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m = RoundedClipRectModifierOperation::new;
         read(m, buffer, operations);
     }
@@ -46,16 +46,17 @@
         return OP_CODE;
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
     @Override
-    protected void write(WireBuffer buffer, float v1, float v2, float v3, float v4) {
+    protected void write(@NonNull WireBuffer buffer, float v1, float v2, float v3, float v4) {
         apply(buffer, v1, v2, v3, v4);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Modifier Operations", id(), "RoundedClipRectModifierOperation")
                 .description("clip with rectangle")
                 .field(
@@ -90,7 +91,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.roundedClipRect(mWidth, mHeight, mX1, mY1, mX2, mY2);
     }
 
@@ -101,13 +102,7 @@
     }
 
     @Override
-    public void onClick(
-            RemoteContext context, CoreDocument document, Component component, float x, float y) {
-        // nothing
-    }
-
-    @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
         serializer.append(
                 indent,
                 "ROUNDED_CLIP_RECT = ["
@@ -135,7 +130,11 @@
      * @param bottomEnd bottomEnd radius
      */
     public static void apply(
-            WireBuffer buffer, float topStart, float topEnd, float bottomStart, float bottomEnd) {
+            @NonNull WireBuffer buffer,
+            float topStart,
+            float topEnd,
+            float bottomStart,
+            float bottomEnd) {
         write(buffer, OP_CODE, topStart, topEnd, bottomStart, bottomEnd);
     }
 }
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
new file mode 100644
index 0000000..29ec828
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java
@@ -0,0 +1,97 @@
+/*
+ * 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 static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
+
+import com.android.internal.widget.remotecompose.core.CoreDocument;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.operations.layout.ActionOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+import java.util.List;
+
+/** Apply a value change on an float variable. */
+public class ValueFloatChangeActionOperation implements ActionOperation {
+    private static final int OP_CODE = Operations.VALUE_FLOAT_CHANGE_ACTION;
+
+    int mTargetValueId = -1;
+    float mValue = -1;
+
+    public ValueFloatChangeActionOperation(int id, float value) {
+        mTargetValueId = id;
+        mValue = value;
+    }
+
+    @Override
+    public String toString() {
+        return "ValueFloatChangeActionOperation(" + mTargetValueId + ")";
+    }
+
+    public String serializedName() {
+        return "VALUE_FLOAT_CHANGE";
+    }
+
+    @Override
+    public void serializeToString(int indent, StringSerializer serializer) {
+        serializer.append(indent, serializedName() + " = " + mTargetValueId + " -> " + mValue);
+    }
+
+    @Override
+    public void apply(RemoteContext context) {}
+
+    @Override
+    public String deepToString(String indent) {
+        return (indent != null ? indent : "") + toString();
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {}
+
+    @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);
+    }
+
+    public static void apply(WireBuffer buffer, int valueId, float value) {
+        buffer.start(OP_CODE);
+        buffer.writeInt(valueId);
+        buffer.writeFloat(value);
+    }
+
+    public static void read(WireBuffer buffer, List<Operation> operations) {
+        int valueId = buffer.readInt();
+        float value = buffer.readFloat();
+        operations.add(new ValueFloatChangeActionOperation(valueId, value));
+    }
+
+    public static void documentation(DocumentationBuilder doc) {
+        doc.operation("Layout Operations", OP_CODE, "ValueFloatChangeActionOperation")
+                .description(
+                        "ValueIntegerChange action. "
+                                + " This operation represents a value change for the given id")
+                .field(INT, "TARGET_VALUE_ID", "Value ID")
+                .field(FLOAT, "VALUE", "float value to be assigned to the target");
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java
index 8876720..d7ce8ac 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
@@ -17,6 +17,9 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.CoreDocument;
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
@@ -41,25 +44,28 @@
         mValue = value;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "ValueChangeActionOperation(" + mTargetValueId + ")";
     }
 
+    @NonNull
     public String serializedName() {
         return "VALUE_INTEGER_CHANGE";
     }
 
     @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
         serializer.append(indent, serializedName() + " = " + mTargetValueId + " -> " + mValue);
     }
 
     @Override
     public void apply(RemoteContext context) {}
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@Nullable String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
@@ -68,23 +74,27 @@
 
     @Override
     public void runAction(
-            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+            @NonNull RemoteContext context,
+            CoreDocument document,
+            Component component,
+            float x,
+            float y) {
         context.overrideInteger(mTargetValueId, mValue);
     }
 
-    public static void apply(WireBuffer buffer, int valueId, int value) {
+    public static void apply(@NonNull WireBuffer buffer, int valueId, int value) {
         buffer.start(OP_CODE);
         buffer.writeInt(valueId);
         buffer.writeInt(value);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int valueId = buffer.readInt();
         int value = buffer.readInt();
         operations.add(new ValueIntegerChangeActionOperation(valueId, value));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", OP_CODE, "ValueIntegerChangeActionOperation")
                 .description(
                         "ValueIntegerChange action. "
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 fb5e911..75d13e7 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
@@ -17,6 +17,9 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.CoreDocument;
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
@@ -41,17 +44,19 @@
         mValueExpressionId = value;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "ValueIntegerExpressionChangeActionOperation(" + mTargetValueId + ")";
     }
 
+    @NonNull
     public String serializedName() {
         return "VALUE_INTEGER_EXPRESSION_CHANGE";
     }
 
     @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
         serializer.append(
                 indent, serializedName() + " = " + mTargetValueId + " -> " + mValueExpressionId);
     }
@@ -59,8 +64,9 @@
     @Override
     public void apply(RemoteContext context) {}
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@Nullable String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
@@ -69,23 +75,27 @@
 
     @Override
     public void runAction(
-            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+            @NonNull RemoteContext context,
+            @NonNull CoreDocument document,
+            Component component,
+            float x,
+            float y) {
         document.evaluateIntExpression(mValueExpressionId, (int) mTargetValueId, context);
     }
 
-    public static void apply(WireBuffer buffer, long valueId, long value) {
+    public static void apply(@NonNull WireBuffer buffer, long valueId, long value) {
         buffer.start(OP_CODE);
         buffer.writeLong(valueId);
         buffer.writeLong(value);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         long valueId = buffer.readLong();
         long value = buffer.readLong();
         operations.add(new ValueIntegerExpressionChangeActionOperation(valueId, value));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", OP_CODE, "ValueIntegerExpressionChangeActionOperation")
                 .description(
                         "ValueIntegerExpressionChange action. "
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 a64a492..26d7244 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
@@ -17,6 +17,9 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.CoreDocument;
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
@@ -41,6 +44,7 @@
         mValueId = value;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "ValueChangeActionOperation(" + mTargetValueId + ")";
@@ -50,20 +54,22 @@
         return mTargetValueId;
     }
 
+    @NonNull
     public String serializedName() {
         return "VALUE_CHANGE";
     }
 
     @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
         serializer.append(indent, serializedName() + " = " + mTargetValueId + " -> " + mValueId);
     }
 
     @Override
     public void apply(RemoteContext context) {}
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@Nullable String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
@@ -72,23 +78,27 @@
 
     @Override
     public void runAction(
-            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+            @NonNull RemoteContext context,
+            CoreDocument document,
+            Component component,
+            float x,
+            float y) {
         context.overrideText(mTargetValueId, mValueId);
     }
 
-    public static void apply(WireBuffer buffer, int valueId, int value) {
+    public static void apply(@NonNull WireBuffer buffer, int valueId, int value) {
         buffer.start(OP_CODE);
         buffer.writeInt(valueId);
         buffer.writeInt(value);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int valueId = buffer.readInt();
         int value = buffer.readInt();
         operations.add(new ValueStringChangeActionOperation(valueId, value));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", OP_CODE, "ValueStringChangeActionOperation")
                 .description(
                         "ValueStrin gChange action. "
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 62403b3..e2f899c 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
@@ -18,6 +18,8 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.WireBuffer;
@@ -30,6 +32,7 @@
     private static final int OP_CODE = Operations.MODIFIER_WIDTH;
     public static final String CLASS_NAME = "WidthModifierOperation";
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -38,13 +41,13 @@
         return OP_CODE;
     }
 
-    public static void apply(WireBuffer buffer, int type, float value) {
+    public static void apply(@NonNull WireBuffer buffer, int type, float value) {
         buffer.start(OP_CODE);
         buffer.writeInt(type);
         buffer.writeFloat(value);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Type type = Type.fromInt(buffer.readInt());
         float value = buffer.readFloat();
         Operation op = new WidthModifierOperation(type, value);
@@ -56,7 +59,7 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mType.ordinal(), mValue);
     }
 
@@ -68,17 +71,19 @@
         super(value);
     }
 
+    @NonNull
     @Override
     public String toString() {
-        return "Width(" + mValue + ")";
+        return "Width(" + mType + ", " + mValue + ")";
     }
 
+    @NonNull
     @Override
     public String serializedName() {
         return "WIDTH";
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Modifier Operations", OP_CODE, CLASS_NAME)
                 .description("define the animation")
                 .field(INT, "type", "")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java
new file mode 100644
index 0000000..aa20e03
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.layout.modifiers;
+
+import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
+
+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.utilities.StringSerializer;
+
+import java.util.List;
+
+/** Represents a ZIndex modifier, allowing to change the z-index of a component. */
+public class ZIndexModifierOperation extends DecoratorModifierOperation {
+    private static final int OP_CODE = Operations.MODIFIER_ZINDEX;
+    public static final String CLASS_NAME = "ZIndexModifierOperation";
+    float mValue;
+    float mCurrentValue;
+
+    public ZIndexModifierOperation(float value) {
+        this.mValue = value;
+    }
+
+    public float getValue() {
+        return mCurrentValue;
+    }
+
+    public void setmValue(float value) {
+        this.mValue = value;
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        apply(buffer, mValue);
+    }
+
+    // @Override
+    public void serializeToString(int indent, StringSerializer serializer) {
+        serializer.append(indent, "ZINDEX = [" + mValue + "]");
+    }
+
+    @Override
+    public String deepToString(String indent) {
+        return (indent != null ? indent : "") + toString();
+    }
+
+    @Override
+    public void paint(PaintContext context) {
+        mCurrentValue = mValue;
+        if (Utils.isVariable(mValue)) {
+            mCurrentValue =
+                    context.getContext().mRemoteComposeState.getFloat(Utils.idFromNan(mValue));
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "ZIndexModifierOperation(" + mValue + ")";
+    }
+
+    public static String name() {
+        return CLASS_NAME;
+    }
+
+    public static int id() {
+        return OP_CODE;
+    }
+
+    public static void apply(WireBuffer buffer, float value) {
+        buffer.start(OP_CODE);
+        buffer.writeFloat(value);
+    }
+
+    public static void read(WireBuffer buffer, List<Operation> operations) {
+        float value = buffer.readFloat();
+        operations.add(new ZIndexModifierOperation(value));
+    }
+
+    public static void documentation(DocumentationBuilder doc) {
+        doc.operation("Modifier Operations", OP_CODE, CLASS_NAME)
+                .description("define the Z-Index Modifier")
+                .field(FLOAT, "value", "");
+    }
+
+    @Override
+    public void layout(RemoteContext context, float width, float height) {}
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/utils/DebugLog.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/utils/DebugLog.java
index 4849b12..d8e49b0 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/utils/DebugLog.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/utils/DebugLog.java
@@ -15,6 +15,9 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout.utils;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import java.util.ArrayList;
 
 /** Internal utility debug class */
@@ -23,12 +26,12 @@
     public static final boolean DEBUG_LAYOUT_ON = false;
 
     public static class Node {
-        public Node parent;
+        @Nullable public Node parent;
         public String name;
         public String endString;
-        public ArrayList<Node> list = new ArrayList<>();
+        @NonNull public ArrayList<Node> list = new ArrayList<>();
 
-        public Node(Node parent, String name) {
+        public Node(@Nullable Node parent, String name) {
             this.parent = parent;
             this.name = name;
             this.endString = name + " DONE";
@@ -48,21 +51,21 @@
         }
     }
 
-    public static Node node = new Node(null, "Root");
-    public static Node currentNode = node;
+    @NonNull public static Node node = new Node(null, "Root");
+    @NonNull public static Node currentNode = node;
 
     public static void clear() {
         node = new Node(null, "Root");
         currentNode = node;
     }
 
-    public static void s(StringValueSupplier valueSupplier) {
+    public static void s(@NonNull StringValueSupplier valueSupplier) {
         if (DEBUG_LAYOUT_ON) {
             currentNode = new Node(currentNode, valueSupplier.getString());
         }
     }
 
-    public static void log(StringValueSupplier valueSupplier) {
+    public static void log(@NonNull StringValueSupplier valueSupplier) {
         if (DEBUG_LAYOUT_ON) {
             new LogNode(currentNode, valueSupplier.getString());
         }
@@ -78,7 +81,7 @@
         }
     }
 
-    public static void e(StringValueSupplier valueSupplier) {
+    public static void e(@NonNull StringValueSupplier valueSupplier) {
         if (DEBUG_LAYOUT_ON) {
             currentNode.endString = valueSupplier.getString();
             if (currentNode.parent != null) {
@@ -89,7 +92,7 @@
         }
     }
 
-    public static void printNode(int indent, Node node, StringBuilder builder) {
+    public static void printNode(int indent, @NonNull Node node, @NonNull StringBuilder builder) {
         if (DEBUG_LAYOUT_ON) {
             StringBuilder indentationBuilder = new StringBuilder();
             for (int i = 0; i < indent; i++) {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/Painter.java b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/Painter.java
index 9a3cd54..a808cf0 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/Painter.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/Painter.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.paint;
 
+import android.annotation.NonNull;
+
 /** Provides a Builder pattern for a PaintBundle */
 class Painter {
     PaintBundle mPaint;
@@ -24,16 +26,19 @@
         return mPaint;
     }
 
+    @NonNull
     public Painter setAntiAlias(boolean aa) {
         mPaint.setAntiAlias(aa);
         return this;
     }
 
+    @NonNull
     public Painter setColor(int color) {
         mPaint.setColor(color);
         return this;
     }
 
+    @NonNull
     public Painter setColorId(int colorId) {
         mPaint.setColorId(colorId);
         return this;
@@ -44,6 +49,7 @@
      *
      * @param join set the paint's Join, used whenever the paint's style is Stroke or StrokeAndFill.
      */
+    @NonNull
     public Painter setStrokeJoin(int join) {
         mPaint.setStrokeJoin(join);
         return this;
@@ -56,6 +62,7 @@
      * @param width set the paint's stroke width, used whenever the paint's style is Stroke or
      *     StrokeAndFill.
      */
+    @NonNull
     public Painter setStrokeWidth(float width) {
         mPaint.setStrokeWidth(width);
         return this;
@@ -67,6 +74,7 @@
      *
      * @param style The new style to set in the paint
      */
+    @NonNull
     public Painter setStyle(int style) {
         mPaint.setStyle(style);
         return this;
@@ -78,6 +86,7 @@
      * @param cap set the paint's line cap style, used whenever the paint's style is Stroke or
      *     StrokeAndFill.
      */
+    @NonNull
     public Painter setStrokeCap(int cap) {
         mPaint.setStrokeCap(cap);
         return this;
@@ -90,6 +99,7 @@
      * @param miter set the miter limit on the paint, used whenever the paint's style is Stroke or
      *     StrokeAndFill.
      */
+    @NonNull
     public Painter setStrokeMiter(float miter) {
         mPaint.setStrokeMiter(miter);
         return this;
@@ -101,6 +111,7 @@
      *
      * @param alpha set the alpha component [0..1.0] of the paint's color.
      */
+    @NonNull
     public Painter setAlpha(float alpha) {
         mPaint.setAlpha((alpha > 2) ? alpha / 255f : alpha);
         return this;
@@ -112,6 +123,7 @@
      * @param color The ARGB source color used with the specified Porter-Duff mode
      * @param mode The porter-duff mode that is applied
      */
+    @NonNull
     public Painter setPorterDuffColorFilter(int color, int mode) {
         mPaint.setColorFilter(color, mode);
         return this;
@@ -130,6 +142,7 @@
      *     line.
      * @param tileMode The Shader tiling mode
      */
+    @NonNull
     public Painter setLinearGradient(
             float startX,
             float startY,
@@ -155,6 +168,7 @@
      *     circle.
      * @param tileMode The Shader tiling mode
      */
+    @NonNull
     public Painter setRadialGradient(
             float centerX,
             float centerY,
@@ -178,6 +192,7 @@
      *     may produce unexpected results. If positions is NULL, then the colors are automatically
      *     spaced evenly.
      */
+    @NonNull
     public Painter setSweepGradient(float centerX, float centerY, int[] colors, float[] positions) {
         mPaint.setSweepGradient(colors, 0, positions, centerX, centerY);
         return this;
@@ -188,6 +203,7 @@
      *
      * @param size set the paint's text size in pixel units.
      */
+    @NonNull
     public Painter setTextSize(float size) {
         mPaint.setTextSize(size);
         return this;
@@ -215,16 +231,19 @@
      * @param weight The desired weight to be drawn.
      * @param italic {@code true} if italic style is desired to be drawn. Otherwise, {@code false}
      */
+    @NonNull
     public Painter setTypeface(int fontType, int weight, boolean italic) {
         mPaint.setTextStyle(fontType, weight, italic);
         return this;
     }
 
+    @NonNull
     public Painter setFilterBitmap(boolean filter) {
         mPaint.setFilterBitmap(filter);
         return this;
     }
 
+    @NonNull
     public Painter setShader(int id) {
         mPaint.setShader(id);
         return this;
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 1d673c4..b25f4cd 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
@@ -15,11 +15,12 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.utilities;
 
-/**
- * high performance floating point expression evaluator used in animation
- */
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/** high performance floating point expression evaluator used in animation */
 public class AnimatedFloatExpression {
-    static IntMap<String> sNames = new IntMap<>();
+    @NonNull static IntMap<String> sNames = new IntMap<>();
     public static final int OFFSET = 0x310_000;
     public static final float ADD = asNan(OFFSET + 1);
     public static final float SUB = asNan(OFFSET + 2);
@@ -74,7 +75,7 @@
     private static final float FP_TO_DEG = 0.017453292f; // 180/PI
 
     float[] mStack;
-    float[] mLocalStack = new float[128];
+    @NonNull float[] mLocalStack = new float[128];
     float[] mVar;
     CollectionsAccess mCollectionsAccess;
 
@@ -201,7 +202,7 @@
      * @param var
      * @return
      */
-    public float eval(float[] exp, int len, float... var) {
+    public float eval(@NonNull float[] exp, int len, float... var) {
         System.arraycopy(exp, 0, mLocalStack, 0, len);
         mStack = mLocalStack;
         mVar = var;
@@ -224,7 +225,7 @@
      * @param var
      * @return
      */
-    public float evalDB(float[] exp, float... var) {
+    public float evalDB(@NonNull float[] exp, float... var) {
         mStack = exp;
         mVar = var;
         int sp = -1;
@@ -240,195 +241,281 @@
         return mStack[sp];
     }
 
-    Op[] mOps = {
+    @NonNull Op[] mOps;
+
+    {
+        Op mADD =
+                (sp) -> { // ADD
+                    mStack[sp - 1] = mStack[sp - 1] + mStack[sp];
+                    return sp - 1;
+                };
+        Op mSUB =
+                (sp) -> { // SUB
+                    mStack[sp - 1] = mStack[sp - 1] - mStack[sp];
+                    return sp - 1;
+                };
+        Op mMUL =
+                (sp) -> { // MUL
+                    mStack[sp - 1] = mStack[sp - 1] * mStack[sp];
+                    return sp - 1;
+                };
+        Op mDIV =
+                (sp) -> { // DIV
+                    mStack[sp - 1] = mStack[sp - 1] / mStack[sp];
+                    return sp - 1;
+                };
+        Op mMOD =
+                (sp) -> { // MOD
+                    mStack[sp - 1] = mStack[sp - 1] % mStack[sp];
+                    return sp - 1;
+                };
+        Op mMIN =
+                (sp) -> { // MIN
+                    mStack[sp - 1] = (float) Math.min(mStack[sp - 1], mStack[sp]);
+                    return sp - 1;
+                };
+        Op mMAX =
+                (sp) -> { // MAX
+                    mStack[sp - 1] = (float) Math.max(mStack[sp - 1], mStack[sp]);
+                    return sp - 1;
+                };
+        Op mPOW =
+                (sp) -> { // POW
+                    mStack[sp - 1] = (float) Math.pow(mStack[sp - 1], mStack[sp]);
+                    return sp - 1;
+                };
+        Op mSQRT =
+                (sp) -> { // SQRT
+                    mStack[sp] = (float) Math.sqrt(mStack[sp]);
+                    return sp;
+                };
+        Op mABS =
+                (sp) -> { // ABS
+                    mStack[sp] = (float) Math.abs(mStack[sp]);
+                    return sp;
+                };
+        Op mSIGN =
+                (sp) -> { // SIGN
+                    mStack[sp] = (float) Math.signum(mStack[sp]);
+                    return sp;
+                };
+        Op mCOPY_SIGN =
+                (sp) -> { // copySign
+                    mStack[sp - 1] = (float) Math.copySign(mStack[sp - 1], mStack[sp]);
+                    return sp - 1;
+                };
+        Op mEXP =
+                (sp) -> { // EXP
+                    mStack[sp] = (float) Math.exp(mStack[sp]);
+                    return sp;
+                };
+        Op mFLOOR =
+                (sp) -> { // FLOOR
+                    mStack[sp] = (float) Math.floor(mStack[sp]);
+                    return sp;
+                };
+        Op mLOG =
+                (sp) -> { // LOG
+                    mStack[sp] = (float) Math.log10(mStack[sp]);
+                    return sp;
+                };
+        Op mLN =
+                (sp) -> { // LN
+                    mStack[sp] = (float) Math.log(mStack[sp]);
+                    return sp;
+                };
+        Op mROUND =
+                (sp) -> { // ROUND
+                    mStack[sp] = (float) Math.round(mStack[sp]);
+                    return sp;
+                };
+        Op mSIN =
+                (sp) -> { // SIN
+                    mStack[sp] = (float) Math.sin(mStack[sp]);
+                    return sp;
+                };
+        Op mCOS =
+                (sp) -> { // COS
+                    mStack[sp] = (float) Math.cos(mStack[sp]);
+                    return sp;
+                };
+        Op mTAN =
+                (sp) -> { // TAN
+                    mStack[sp] = (float) Math.tan(mStack[sp]);
+                    return sp;
+                };
+        Op mASIN =
+                (sp) -> { // ASIN
+                    mStack[sp] = (float) Math.asin(mStack[sp]);
+                    return sp;
+                };
+        Op mACOS =
+                (sp) -> { // ACOS
+                    mStack[sp] = (float) Math.acos(mStack[sp]);
+                    return sp;
+                };
+        Op mATAN =
+                (sp) -> { // ATAN
+                    mStack[sp] = (float) Math.atan(mStack[sp]);
+                    return sp;
+                };
+        Op mATAN2 =
+                (sp) -> { // ATAN2
+                    mStack[sp - 1] = (float) Math.atan2(mStack[sp - 1], mStack[sp]);
+                    return sp - 1;
+                };
+        Op mMAD =
+                (sp) -> { // MAD
+                    mStack[sp - 2] = mStack[sp] + mStack[sp - 1] * mStack[sp - 2];
+                    return sp - 2;
+                };
+        Op mTERNARY_CONDITIONAL =
+                (sp) -> { // TERNARY_CONDITIONAL
+                    mStack[sp - 2] = (mStack[sp] > 0) ? mStack[sp - 1] : mStack[sp - 2];
+                    return sp - 2;
+                };
+        Op mCLAMP =
+                (sp) -> { // CLAMP
+                    mStack[sp - 2] = Math.min(Math.max(mStack[sp - 2], mStack[sp]), mStack[sp - 1]);
+                    return sp - 2;
+                };
+        Op mCBRT =
+                (sp) -> { // CBRT
+                    mStack[sp] = (float) Math.pow(mStack[sp], 1 / 3.);
+                    return sp;
+                };
+        Op mDEG =
+                (sp) -> { // DEG
+                    mStack[sp] = mStack[sp] * FP_TO_RAD;
+                    return sp;
+                };
+        Op mRAD =
+                (sp) -> { // RAD
+                    mStack[sp] = mStack[sp] * FP_TO_DEG;
+                    return sp;
+                };
+        Op mCEIL =
+                (sp) -> { // CEIL
+                    mStack[sp] = (float) Math.ceil(mStack[sp]);
+                    return sp;
+                };
+        Op mA_DEREF =
+                (sp) -> { // A_DEREF
+                    int id = fromNaN(mStack[sp]);
+                    mStack[sp - 1] = mCollectionsAccess.getFloatValue(id, (int) mStack[sp - 1]);
+                    return sp - 1;
+                };
+        Op mA_MAX =
+                (sp) -> { // A_MAX
+                    int id = fromNaN(mStack[sp]);
+                    float[] array = mCollectionsAccess.getFloats(id);
+                    float max = array[0];
+                    for (int i = 1; i < array.length; i++) {
+                        max = Math.max(max, array[i]);
+                    }
+                    mStack[sp] = max;
+                    return sp;
+                };
+        Op mA_MIN =
+                (sp) -> { // A_MIN
+                    int id = fromNaN(mStack[sp]);
+                    float[] array = mCollectionsAccess.getFloats(id);
+                    float max = array[0];
+                    for (int i = 1; i < array.length; i++) {
+                        max = Math.max(max, array[i]);
+                    }
+                    mStack[sp] = max;
+                    return sp;
+                };
+        Op mA_SUM =
+                (sp) -> { // A_SUM
+                    int id = fromNaN(mStack[sp]);
+                    float[] array = mCollectionsAccess.getFloats(id);
+                    float sum = 0;
+                    for (int i = 0; i < array.length; i++) {
+                        sum += array[i];
+                    }
+                    mStack[sp] = sum;
+                    return sp;
+                };
+        Op mA_AVG =
+                (sp) -> { // A_AVG
+                    int id = fromNaN(mStack[sp]);
+                    float[] array = mCollectionsAccess.getFloats(id);
+                    float sum = 0;
+                    for (int i = 0; i < array.length; i++) {
+                        sum += array[i];
+                    }
+                    mStack[sp] = sum / array.length;
+                    return sp;
+                };
+        Op mA_LEN =
+                (sp) -> { // A_LEN
+                    int id = fromNaN(mStack[sp]);
+                    mStack[sp] = mCollectionsAccess.getListLength(id);
+                    return sp;
+                };
+        Op mFIRST_VAR =
+                (sp) -> { // FIRST_VAR
+                    mStack[sp] = mVar[0];
+                    return sp;
+                };
+        Op mSECOND_VAR =
+                (sp) -> { // SECOND_VAR
+                    mStack[sp] = mVar[1];
+                    return sp;
+                };
+        Op mTHIRD_VAR =
+                (sp) -> { // THIRD_VAR
+                    mStack[sp] = mVar[2];
+                    return sp;
+                };
+
+        Op[] ops = {
             null,
-            (sp) -> { // ADD
-                mStack[sp - 1] = mStack[sp - 1] + mStack[sp];
-                return sp - 1;
-            },
-            (sp) -> { // SUB
-                mStack[sp - 1] = mStack[sp - 1] - mStack[sp];
-                return sp - 1;
-            },
-            (sp) -> { // MUL
-                mStack[sp - 1] = mStack[sp - 1] * mStack[sp];
-                return sp - 1;
-            },
-            (sp) -> { // DIV
-                mStack[sp - 1] = mStack[sp - 1] / mStack[sp];
-                return sp - 1;
-            },
-            (sp) -> { // MOD
-                mStack[sp - 1] = mStack[sp - 1] % mStack[sp];
-                return sp - 1;
-            },
-            (sp) -> { // MIN
-                mStack[sp - 1] = (float) Math.min(mStack[sp - 1], mStack[sp]);
-                return sp - 1;
-            },
-            (sp) -> { // MAX
-                mStack[sp - 1] = (float) Math.max(mStack[sp - 1], mStack[sp]);
-                return sp - 1;
-            },
-            (sp) -> { // POW
-                mStack[sp - 1] = (float) Math.pow(mStack[sp - 1], mStack[sp]);
-                return sp - 1;
-            },
-            (sp) -> { // SQRT
-                mStack[sp] = (float) Math.sqrt(mStack[sp]);
-                return sp;
-            },
-            (sp) -> { // ABS
-                mStack[sp] = (float) Math.abs(mStack[sp]);
-                return sp;
-            },
-            (sp) -> { // SIGN
-                mStack[sp] = (float) Math.signum(mStack[sp]);
-                return sp;
-            },
-            (sp) -> { // copySign
-                mStack[sp - 1] = (float) Math.copySign(mStack[sp - 1], mStack[sp]);
-                return sp - 1;
-            },
-            (sp) -> { // EXP
-                mStack[sp] = (float) Math.exp(mStack[sp]);
-                return sp;
-            },
-            (sp) -> { // FLOOR
-                mStack[sp] = (float) Math.floor(mStack[sp]);
-                return sp;
-            },
-            (sp) -> { // LOG
-                mStack[sp] = (float) Math.log10(mStack[sp]);
-                return sp;
-            },
-            (sp) -> { // LN
-                mStack[sp] = (float) Math.log(mStack[sp]);
-                return sp;
-            },
-            (sp) -> { // ROUND
-                mStack[sp] = (float) Math.round(mStack[sp]);
-                return sp;
-            },
-            (sp) -> { // SIN
-                mStack[sp] = (float) Math.sin(mStack[sp]);
-                return sp;
-            },
-            (sp) -> { // COS
-                mStack[sp] = (float) Math.cos(mStack[sp]);
-                return sp;
-            },
-            (sp) -> { // TAN
-                mStack[sp] = (float) Math.tan(mStack[sp]);
-                return sp;
-            },
-            (sp) -> { // ASIN
-                mStack[sp] = (float) Math.asin(mStack[sp]);
-                return sp;
-            },
-            (sp) -> { // ACOS
-                mStack[sp] = (float) Math.acos(mStack[sp]);
-                return sp;
-            },
-            (sp) -> { // ATAN
-                mStack[sp] = (float) Math.atan(mStack[sp]);
-                return sp;
-            },
-            (sp) -> { // ATAN2
-                mStack[sp - 1] = (float) Math.atan2(mStack[sp - 1], mStack[sp]);
-                return sp - 1;
-            },
-            (sp) -> { // MAD
-                mStack[sp - 2] = mStack[sp] + mStack[sp - 1] * mStack[sp - 2];
-                return sp - 2;
-            },
-            (sp) -> { // Ternary conditional
-                mStack[sp - 2] = (mStack[sp] > 0) ? mStack[sp - 1] : mStack[sp - 2];
-                return sp - 2;
-            },
-            (sp) -> { // CLAMP(min,max, val)
-                mStack[sp - 2] = Math.min(Math.max(mStack[sp - 2], mStack[sp]), mStack[sp - 1]);
-                return sp - 2;
-            },
-            (sp) -> { // CBRT cuberoot
-                mStack[sp] = (float) Math.pow(mStack[sp], 1 / 3.);
-                return sp;
-            },
-            (sp) -> { // DEG
-                mStack[sp] = mStack[sp] * FP_TO_RAD;
-                return sp;
-            },
-            (sp) -> { // RAD
-                mStack[sp] = mStack[sp] * FP_TO_DEG;
-                return sp;
-            },
-            (sp) -> { // CEIL
-                mStack[sp] = (float) Math.ceil(mStack[sp]);
-                return sp;
-            },
-            (sp) -> { // A_DEREF
-                int id = fromNaN(mStack[sp]);
-                mStack[sp] = mCollectionsAccess.getFloatValue(id, (int) mStack[sp - 1]);
-                return sp - 1;
-            },
-            (sp) -> { // A_MAX
-                int id = fromNaN(mStack[sp]);
-                float[] array = mCollectionsAccess.getFloats(id);
-                float max = array[0];
-                for (int i = 1; i < array.length; i++) {
-                    max = Math.max(max, array[i]);
-                }
-                mStack[sp] = max;
-                return sp;
-            },
-            (sp) -> { // A_MIN
-                int id = fromNaN(mStack[sp]);
-                float[] array = mCollectionsAccess.getFloats(id);
-                float max = array[0];
-                for (int i = 1; i < array.length; i++) {
-                    max = Math.max(max, array[i]);
-                }
-                mStack[sp] = max;
-                return sp;
-            },
-            (sp) -> { // A_SUM
-                int id = fromNaN(mStack[sp]);
-                float[] array = mCollectionsAccess.getFloats(id);
-                float sum = 0;
-                for (int i = 0; i < array.length; i++) {
-                    sum += array[i];
-                }
-                mStack[sp] = sum;
-                return sp;
-            },
-            (sp) -> { // A_AVG
-                int id = fromNaN(mStack[sp]);
-                float[] array = mCollectionsAccess.getFloats(id);
-                float sum = 0;
-                for (int i = 0; i < array.length; i++) {
-                    sum += array[i];
-                }
-                mStack[sp] = sum / array.length;
-                return sp;
-            },
-            (sp) -> { // A_LEN
-                int id = fromNaN(mStack[sp]);
-                mStack[sp] = mCollectionsAccess.getListLength(id);
-                return sp;
-            },
-            (sp) -> { // first var =
-                mStack[sp] = mVar[0];
-                return sp;
-            },
-            (sp) -> { // second var y?
-                mStack[sp] = mVar[1];
-                return sp;
-            },
-            (sp) -> { // 3rd var z?
-                mStack[sp] = mVar[2];
-                return sp;
-            },
-    };
+            mADD,
+            mSUB,
+            mMUL,
+            mDIV,
+            mMOD,
+            mMIN,
+            mMAX,
+            mPOW,
+            mSQRT,
+            mABS,
+            mSIGN,
+            mCOPY_SIGN,
+            mEXP,
+            mFLOOR,
+            mLOG,
+            mLN,
+            mROUND,
+            mSIN,
+            mCOS,
+            mTAN,
+            mASIN,
+            mACOS,
+            mATAN,
+            mATAN2,
+            mMAD,
+            mTERNARY_CONDITIONAL,
+            mCLAMP,
+            mCBRT,
+            mDEG,
+            mRAD,
+            mCEIL,
+            mA_DEREF,
+            mA_MAX,
+            mA_MIN,
+            mA_SUM,
+            mA_AVG,
+            mA_LEN,
+            mFIRST_VAR,
+            mSECOND_VAR,
+            mTHIRD_VAR,
+        };
+        mOps = ops;
+    }
 
     static {
         int k = 0;
@@ -483,6 +570,7 @@
      * @param f
      * @return
      */
+    @Nullable
     public static String toMathName(float f) {
         int id = fromNaN(f) - OFFSET;
         return sNames.get(id);
@@ -495,7 +583,8 @@
      * @param labels
      * @return
      */
-    public static String toString(float[] exp, String[] labels) {
+    @NonNull
+    public static String toString(@NonNull float[] exp, @Nullable String[] labels) {
         StringBuilder s = new StringBuilder();
         for (int i = 0; i < exp.length; i++) {
             float v = exp[i];
@@ -525,7 +614,7 @@
         return s.toString();
     }
 
-    static String toString(float[] exp, int sp) {
+    static String toString(@NonNull float[] exp, int sp) {
         //        String[] str = new String[exp.length];
         if (Float.isNaN(exp[sp])) {
             int id = fromNaN(exp[sp]) - OFFSET;
@@ -575,42 +664,42 @@
     }
 
     static final int[] NO_OF_OPS = {
-            -1, // no op
-            2,
-            2,
-            2,
-            2,
-            2, // + - * / %
-            2,
-            2,
-            2, // min max, power
-            1,
-            1,
-            1,
-            1,
-            1,
-            1,
-            1,
-            1, // sqrt,abs,CopySign,exp,floor,log,ln
-            1,
-            1,
-            1,
-            1,
-            1,
-            1,
-            1,
-            2, // round,sin,cos,tan,asin,acos,atan,atan2
-            3,
-            3,
-            3,
-            1,
-            1,
-            1,
-            1,
-            0,
-            0,
-            0 // mad, ?:,
-            // a[0],a[1],a[2]
+        -1, // no op
+        2,
+        2,
+        2,
+        2,
+        2, // + - * / %
+        2,
+        2,
+        2, // min max, power
+        1,
+        1,
+        1,
+        1,
+        1,
+        1,
+        1,
+        1, // sqrt,abs,CopySign,exp,floor,log,ln
+        1,
+        1,
+        1,
+        1,
+        1,
+        1,
+        1,
+        2, // round,sin,cos,tan,asin,acos,atan,atan2
+        3,
+        3,
+        3,
+        1,
+        1,
+        1,
+        1,
+        0,
+        0,
+        0 // mad, ?:,
+        // a[0],a[1],a[2]
     };
 
     /**
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ImageScaling.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ImageScaling.java
index 00c87c1..e74b335 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ImageScaling.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ImageScaling.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.utilities;
 
+import android.annotation.NonNull;
+
 /** Implement the scaling logic for Compose Image or ImageView */
 public class ImageScaling {
 
@@ -97,6 +99,7 @@
         adjustDrawToType();
     }
 
+    @NonNull
     static String str(float v) {
         String s = "  " + (int) v;
         return s.substring(s.length() - 3);
@@ -210,6 +213,7 @@
         }
     }
 
+    @NonNull
     public static String typeToString(int type) {
         String[] typeString = {
             "none",
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntMap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntMap.java
index 84e7843..749c0fe 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntMap.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntMap.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.utilities;
 
+import android.annotation.Nullable;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 
@@ -42,6 +44,7 @@
         mSize = 0;
     }
 
+    @Nullable
     public T put(int key, T value) {
         if (key == NOT_PRESENT) throw new IllegalArgumentException("Key cannot be NOT_PRESENT");
         if (mSize > mKeys.length * LOAD_FACTOR) {
@@ -50,6 +53,7 @@
         return insert(key, value);
     }
 
+    @Nullable
     public T get(int key) {
         int index = findKey(key);
         if (index == -1) {
@@ -61,6 +65,7 @@
         return mSize;
     }
 
+    @Nullable
     private T insert(int key, T value) {
         int index = hash(key) % mKeys.length;
         while (mKeys[index] != NOT_PRESENT && mKeys[index] != key) {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntegerExpressionEvaluator.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntegerExpressionEvaluator.java
index baa144d..8905431 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntegerExpressionEvaluator.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntegerExpressionEvaluator.java
@@ -15,6 +15,9 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.utilities;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 /**
  * High performance Integer expression evaluator
  *
@@ -22,7 +25,7 @@
  * 0)
  */
 public class IntegerExpressionEvaluator {
-    static IntMap<String> sNames = new IntMap<>();
+    @NonNull static IntMap<String> sNames = new IntMap<>();
     public static final int OFFSET = 0x10000;
     // add, sub, mul,div,mod,min,max, shl, shr, ushr, OR, AND , XOR, COPY_SIGN
     public static final int I_ADD = OFFSET + 1;
@@ -57,7 +60,7 @@
     public static final int I_VAR2 = OFFSET + 25;
 
     int[] mStack;
-    int[] mLocalStack = new int[128];
+    @NonNull int[] mLocalStack = new int[128];
     int[] mVar;
 
     interface Op {
@@ -68,8 +71,8 @@
      * Evaluate an integer expression
      *
      * @param mask bits that are operators
-     * @param exp  rpn sequence of values and operators
-     * @param var  variables if the expression is a function
+     * @param exp rpn sequence of values and operators
+     * @param var variables if the expression is a function
      * @return return the results of evaluating the expression
      */
     public int eval(int mask, int[] exp, int... var) {
@@ -91,12 +94,12 @@
      * Evaluate a integer expression
      *
      * @param mask bits that are operators
-     * @param exp  rpn sequence of values and operators
-     * @param len  the number of values in the expression
-     * @param var  variables if the expression is a function
+     * @param exp rpn sequence of values and operators
+     * @param len the number of values in the expression
+     * @param var variables if the expression is a function
      * @return return the results of evaluating the expression
      */
-    public int eval(int mask, int[] exp, int len, int... var) {
+    public int eval(int mask, @NonNull int[] exp, int len, int... var) {
         System.arraycopy(exp, 0, mLocalStack, 0, len);
         mStack = mLocalStack;
         mVar = var;
@@ -116,11 +119,11 @@
      * Evaluate a int expression
      *
      * @param opMask bits that are operators
-     * @param exp    rpn sequence of values and operators
-     * @param var    variables if the expression is a function
+     * @param exp rpn sequence of values and operators
+     * @param var variables if the expression is a function
      * @return return the results of evaluating the expression
      */
-    public int evalDB(int opMask, int[] exp, int... var) {
+    public int evalDB(int opMask, @NonNull int[] exp, int... var) {
         mStack = exp;
         mVar = var;
         int sp = -1;
@@ -137,113 +140,172 @@
         return mStack[sp];
     }
 
-    Op[] mOps = {
+    @NonNull Op[] mOps;
+
+    {
+        Op mADD =
+                (sp) -> { // ADD
+                    mStack[sp - 1] = mStack[sp - 1] + mStack[sp];
+                    return sp - 1;
+                };
+        Op mSUB =
+                (sp) -> { // SUB
+                    mStack[sp - 1] = mStack[sp - 1] - mStack[sp];
+                    return sp - 1;
+                };
+        Op mMUL =
+                (sp) -> { // MUL
+                    mStack[sp - 1] = mStack[sp - 1] * mStack[sp];
+                    return sp - 1;
+                };
+        Op mDIV =
+                (sp) -> { // DIV
+                    mStack[sp - 1] = mStack[sp - 1] / mStack[sp];
+                    return sp - 1;
+                };
+        Op mMOD =
+                (sp) -> { // MOD
+                    mStack[sp - 1] = mStack[sp - 1] % mStack[sp];
+                    return sp - 1;
+                };
+        Op mSHL =
+                (sp) -> { // SHL
+                    mStack[sp - 1] = mStack[sp - 1] << mStack[sp];
+                    return sp - 1;
+                };
+        Op mSHR =
+                (sp) -> { // SHR
+                    mStack[sp - 1] = mStack[sp - 1] >> mStack[sp];
+                    return sp - 1;
+                };
+        Op mUSHR =
+                (sp) -> { // USHR
+                    mStack[sp - 1] = mStack[sp - 1] >>> mStack[sp];
+                    return sp - 1;
+                };
+        Op mOR =
+                (sp) -> { // OR
+                    mStack[sp - 1] = mStack[sp - 1] | mStack[sp];
+                    return sp - 1;
+                };
+        Op mAND =
+                (sp) -> { // AND
+                    mStack[sp - 1] = mStack[sp - 1] & mStack[sp];
+                    return sp - 1;
+                };
+        Op mXOR =
+                (sp) -> { // XOR
+                    mStack[sp - 1] = mStack[sp - 1] ^ mStack[sp];
+                    return sp - 1;
+                };
+        Op mCOPY_SIGN =
+                (sp) -> { // COPY_SIGN
+                    mStack[sp - 1] = (mStack[sp - 1] ^ (mStack[sp] >> 31)) - (mStack[sp] >> 31);
+                    return sp - 1;
+                };
+        Op mMIN =
+                (sp) -> { // MIN
+                    mStack[sp - 1] = Math.min(mStack[sp - 1], mStack[sp]);
+                    return sp - 1;
+                };
+        Op mMAX =
+                (sp) -> { // MAX
+                    mStack[sp - 1] = Math.max(mStack[sp - 1], mStack[sp]);
+                    return sp - 1;
+                };
+        Op mNEG =
+                (sp) -> { // NEG
+                    mStack[sp] = -mStack[sp];
+                    return sp;
+                };
+        Op mABS =
+                (sp) -> { // ABS
+                    mStack[sp] = Math.abs(mStack[sp]);
+                    return sp;
+                };
+        Op mINCR =
+                (sp) -> { // INCR
+                    mStack[sp] = mStack[sp] + 1;
+                    return sp;
+                };
+        Op mDECR =
+                (sp) -> { // DECR
+                    mStack[sp] = mStack[sp] - 1;
+                    return sp;
+                };
+        Op mNOT =
+                (sp) -> { // NOT
+                    mStack[sp] = ~mStack[sp];
+                    return sp;
+                };
+        Op mSIGN =
+                (sp) -> { // SIGN
+                    mStack[sp] = (mStack[sp] >> 31) | (-mStack[sp] >>> 31);
+                    return sp;
+                };
+        Op mCLAMP =
+                (sp) -> { // CLAMP
+                    mStack[sp - 2] = Math.min(Math.max(mStack[sp - 2], mStack[sp]), mStack[sp - 1]);
+                    return sp - 2;
+                };
+        Op mTERNARY_CONDITIONAL =
+                (sp) -> { // TERNARY_CONDITIONAL
+                    mStack[sp - 2] = (mStack[sp] > 0) ? mStack[sp - 1] : mStack[sp - 2];
+                    return sp - 2;
+                };
+        Op mMAD =
+                (sp) -> { // MAD
+                    mStack[sp - 2] = mStack[sp] + mStack[sp - 1] * mStack[sp - 2];
+                    return sp - 2;
+                };
+        Op mFIRST_VAR =
+                (sp) -> { // FIRST_VAR
+                    mStack[sp] = mVar[0];
+                    return sp;
+                };
+        Op mSECOND_VAR =
+                (sp) -> { // SECOND_VAR
+                    mStack[sp] = mVar[1];
+                    return sp;
+                };
+        Op mTHIRD_VAR =
+                (sp) -> { // THIRD_VAR
+                    mStack[sp] = mVar[2];
+                    return sp;
+                };
+
+        Op[] ops = {
             null,
-            (sp) -> { // ADD
-                mStack[sp - 1] = mStack[sp - 1] + mStack[sp];
-                return sp - 1;
-            },
-            (sp) -> { // SUB
-                mStack[sp - 1] = mStack[sp - 1] - mStack[sp];
-                return sp - 1;
-            },
-            (sp) -> { // MUL
-                mStack[sp - 1] = mStack[sp - 1] * mStack[sp];
-                return sp - 1;
-            },
-            (sp) -> { // DIV
-                mStack[sp - 1] = mStack[sp - 1] / mStack[sp];
-                return sp - 1;
-            },
-            (sp) -> { // MOD
-                mStack[sp - 1] = mStack[sp - 1] % mStack[sp];
-                return sp - 1;
-            },
-            (sp) -> { // SHL shift left
-                mStack[sp - 1] = mStack[sp - 1] << mStack[sp];
-                return sp - 1;
-            },
-            (sp) -> { // SHR shift right
-                mStack[sp - 1] = mStack[sp - 1] >> mStack[sp];
-                return sp - 1;
-            },
-            (sp) -> { // USHR unsigned shift right
-                mStack[sp - 1] = mStack[sp - 1] >>> mStack[sp];
-                return sp - 1;
-            },
-            (sp) -> { // OR operator
-                mStack[sp - 1] = mStack[sp - 1] | mStack[sp];
-                return sp - 1;
-            },
-            (sp) -> { // AND operator
-                mStack[sp - 1] = mStack[sp - 1] & mStack[sp];
-                return sp - 1;
-            },
-            (sp) -> { // XOR xor operator
-                mStack[sp - 1] = mStack[sp - 1] ^ mStack[sp];
-                return sp - 1;
-            },
-            (sp) -> { // COPY_SIGN copy the sing of (using bit magic)
-                mStack[sp - 1] = (mStack[sp - 1] ^ (mStack[sp] >> 31)) - (mStack[sp] >> 31);
-                return sp - 1;
-            },
-            (sp) -> { // MIN
-                mStack[sp - 1] = Math.min(mStack[sp - 1], mStack[sp]);
-                return sp - 1;
-            },
-            (sp) -> { // MAX
-                mStack[sp - 1] = Math.max(mStack[sp - 1], mStack[sp]);
-                return sp - 1;
-            },
-            (sp) -> { // NEG
-                mStack[sp] = -mStack[sp];
-                return sp;
-            },
-            (sp) -> { // ABS
-                mStack[sp] = Math.abs(mStack[sp]);
-                return sp;
-            },
-            (sp) -> { // INCR increment
-                mStack[sp] = mStack[sp] + 1;
-                return sp;
-            },
-            (sp) -> { // DECR decrement
-                mStack[sp] = mStack[sp] - 1;
-                return sp;
-            },
-            (sp) -> { // NOT Bit invert
-                mStack[sp] = ~mStack[sp];
-                return sp;
-            },
-            (sp) -> { // SIGN x<0 = -1,x==0 =  0 , x>0 = 1
-                mStack[sp] = (mStack[sp] >> 31) | (-mStack[sp] >>> 31);
-                return sp;
-            },
-            (sp) -> { // CLAMP(min,max, val)
-                mStack[sp - 2] = Math.min(Math.max(mStack[sp - 2], mStack[sp]), mStack[sp - 1]);
-                return sp - 2;
-            },
-            (sp) -> { // Ternary conditional
-                mStack[sp - 2] = (mStack[sp] > 0) ? mStack[sp - 1] : mStack[sp - 2];
-                return sp - 2;
-            },
-            (sp) -> { // MAD
-                mStack[sp - 2] = mStack[sp] + mStack[sp - 1] * mStack[sp - 2];
-                return sp - 2;
-            },
-            (sp) -> { // first var =
-                mStack[sp] = mVar[0];
-                return sp;
-            },
-            (sp) -> { // second var y?
-                mStack[sp] = mVar[1];
-                return sp;
-            },
-            (sp) -> { // 3rd var z?
-                mStack[sp] = mVar[2];
-                return sp;
-            },
-    };
+            mADD,
+            mSUB,
+            mMUL,
+            mDIV,
+            mMOD,
+            mSHL,
+            mSHR,
+            mUSHR,
+            mOR,
+            mAND,
+            mXOR,
+            mCOPY_SIGN,
+            mMIN,
+            mMAX,
+            mNEG,
+            mABS,
+            mINCR,
+            mDECR,
+            mNOT,
+            mSIGN,
+            mCLAMP,
+            mTERNARY_CONDITIONAL,
+            mMAD,
+            mFIRST_VAR,
+            mSECOND_VAR,
+            mTHIRD_VAR,
+        };
+
+        mOps = ops;
+    }
 
     static {
         int k = 0;
@@ -283,6 +345,7 @@
      * @param f the numerical value of the function + offset
      * @return the math name of the function
      */
+    @Nullable
     public static String toMathName(int f) {
         int id = f - OFFSET;
         return sNames.get(id);
@@ -292,11 +355,12 @@
      * Convert an expression encoded as an array of ints int to a string
      *
      * @param opMask bits that are operators
-     * @param exp    rpn sequence of values and operators
+     * @param exp rpn sequence of values and operators
      * @param labels String that represent the variable names
      * @return
      */
-    public static String toString(int opMask, int[] exp, String[] labels) {
+    @NonNull
+    public static String toString(int opMask, @NonNull int[] exp, String[] labels) {
         StringBuilder s = new StringBuilder();
         for (int i = 0; i < exp.length; i++) {
             int v = exp[i];
@@ -324,10 +388,11 @@
      * Convert an expression encoded as an array of ints int ot a string
      *
      * @param opMask bit mask of operators vs commands
-     * @param exp    rpn sequence of values and operators
+     * @param exp rpn sequence of values and operators
      * @return string representation of the expression
      */
-    public static String toString(int opMask, int[] exp) {
+    @NonNull
+    public static String toString(int opMask, @NonNull int[] exp) {
         StringBuilder s = new StringBuilder();
         s.append(Integer.toBinaryString(opMask));
         s.append(" : ");
@@ -355,13 +420,15 @@
      * This creates an infix string expression
      *
      * @param opMask The bits that are operators
-     * @param exp    the array of expressions
+     * @param exp the array of expressions
      * @return infix string
      */
-    public static String toStringInfix(int opMask, int[] exp) {
+    @NonNull
+    public static String toStringInfix(int opMask, @NonNull int[] exp) {
         return toString(opMask, exp, exp.length - 1);
     }
 
+    @NonNull
     static String toString(int mask, int[] exp, int sp) {
         if (((1 << sp) & mask) != 0) {
             int id = exp[sp] - OFFSET;
@@ -412,34 +479,34 @@
     }
 
     static final int[] NO_OF_OPS = {
-            -1, // no op
-            2,
-            2,
-            2,
-            2,
-            2, // + - * / %
-            2,
-            2,
-            2,
-            2,
-            2,
-            2,
-            2,
-            2,
-            2, // <<, >> , >>> , | , &, ^, min max
-            1,
-            1,
-            1,
-            1,
-            1,
-            1, // neg, abs, ++, -- , not , sign
-            3,
-            3,
-            3, // clamp, ifElse, mad,
-            0,
-            0,
-            0 // mad, ?:,
-            // a[0],a[1],a[2]
+        -1, // no op
+        2,
+        2,
+        2,
+        2,
+        2, // + - * / %
+        2,
+        2,
+        2,
+        2,
+        2,
+        2,
+        2,
+        2,
+        2, // <<, >> , >>> , | , &, ^, min max
+        1,
+        1,
+        1,
+        1,
+        1,
+        1, // neg, abs, ++, -- , not , sign
+        3,
+        3,
+        3, // clamp, ifElse, mad,
+        0,
+        0,
+        0 // mad, ?:,
+        // a[0],a[1],a[2]
     };
 
     /**
@@ -456,7 +523,7 @@
      * is it an id or operation
      *
      * @param opMask the bits that mark elements as an operation
-     * @param i      the bit to check
+     * @param i the bit to check
      * @return true if the bit is 1
      */
     public static boolean isOperation(int opMask, int i) {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringSerializer.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringSerializer.java
index ab7576e..92127c1 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringSerializer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringSerializer.java
@@ -15,9 +15,14 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.utilities;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 /** Utility serializer maintaining an indent buffer */
 public class StringSerializer {
-    StringBuffer mBuffer = new StringBuffer();
+    @NonNull StringBuffer mBuffer = new StringBuffer();
+
+    @NonNull
     String mIndentBuffer = "                                                                      ";
 
     /**
@@ -26,7 +31,7 @@
      * @param indent the indentation level to use
      * @param content content to append
      */
-    public void append(int indent, String content) {
+    public void append(int indent, @Nullable String content) {
         String indentation = mIndentBuffer.substring(0, indent);
         mBuffer.append(indentation);
         mBuffer.append(indentation);
@@ -44,6 +49,7 @@
      *
      * @return string representation
      */
+    @NonNull
     @Override
     public String toString() {
         return mBuffer.toString();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringUtils.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringUtils.java
index f2ccb40..a95a175 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringUtils.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringUtils.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.utilities;
 
+import android.annotation.NonNull;
+
 import java.util.Arrays;
 
 /** Utilities for string manipulation */
@@ -30,6 +32,7 @@
      * @param post character to pad width 0 = no pad typically ' ' or '0'
      * @return
      */
+    @NonNull
     public static String floatToString(
             float value, int beforeDecimalPoint, int afterDecimalPoint, char pre, char post) {
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/CubicEasing.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/CubicEasing.java
index 60a59cf..1343345 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/CubicEasing.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/CubicEasing.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.utilities.easing;
 
+import android.annotation.NonNull;
+
 class CubicEasing extends Easing {
     float mX1 = 0f;
     float mY1 = 0f;
@@ -62,7 +64,7 @@
         mType = type;
     }
 
-    void setup(float[] values) {
+    void setup(@NonNull float[] values) {
         setup(values[0], values[1], values[2], values[3]);
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java
index a29b8af..ebb22b6 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java
@@ -15,6 +15,9 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.utilities.easing;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 /** Support Animation of the FloatExpression */
 public class FloatAnimation extends Easing {
     float[] mSpec;
@@ -31,6 +34,7 @@
     //    private float mScale = 1;
     float mOffset = 0;
 
+    @NonNull
     @Override
     public String toString() {
 
@@ -74,7 +78,7 @@
      * @return
      */
     public static float[] packToFloatArray(
-            float duration, int type, float[] spec, float initialValue, float wrap) {
+            float duration, int type, @Nullable float[] spec, float initialValue, float wrap) {
         int count = 0;
 
         if (!Float.isNaN(initialValue)) {
@@ -129,6 +133,90 @@
     }
 
     /**
+     * Useful to debug the packed form of an animation string
+     *
+     * @param description
+     * @return
+     */
+    public static String unpackAnimationToString(float[] description) {
+        float[] mSpec = description;
+        float mDuration = (mSpec.length == 0) ? 1 : mSpec[0];
+        int len = 0;
+        int type = 0;
+        float wrapValue = Float.NaN;
+        float initialValue = Float.NaN;
+        if (mSpec.length > 1) {
+            int num_type = Float.floatToRawIntBits(mSpec[1]);
+            type = num_type & 0xFF;
+            boolean wrap = ((num_type >> 8) & 0x1) > 0;
+            boolean init = ((num_type >> 8) & 0x2) > 0;
+            len = (num_type >> 16) & 0xFFFF;
+            int off = 2 + len;
+            if (init) {
+                initialValue = mSpec[off++];
+            }
+            if (wrap) {
+                wrapValue = mSpec[off];
+            }
+        }
+        float[] params = description;
+        int offset = 2;
+
+        String typeStr = "";
+        switch (type) {
+            case CUBIC_STANDARD:
+                typeStr = "CUBIC_STANDARD";
+                break;
+            case CUBIC_ACCELERATE:
+                typeStr = "CUBIC_ACCELERATE";
+                break;
+            case CUBIC_DECELERATE:
+                typeStr = "CUBIC_DECELERATE";
+                break;
+            case CUBIC_LINEAR:
+                typeStr = "CUBIC_LINEAR";
+                break;
+            case CUBIC_ANTICIPATE:
+                typeStr = "CUBIC_ANTICIPATE";
+                break;
+            case CUBIC_OVERSHOOT:
+                typeStr = "CUBIC_OVERSHOOT";
+
+                break;
+            case CUBIC_CUSTOM:
+                typeStr = "CUBIC_CUSTOM (";
+                typeStr += params[offset + 0] + " ";
+                typeStr += params[offset + 1] + " ";
+                typeStr += params[offset + 2] + " ";
+                typeStr += params[offset + 3] + " )";
+                break;
+            case EASE_OUT_BOUNCE:
+                typeStr = "EASE_OUT_BOUNCE";
+
+                break;
+            case EASE_OUT_ELASTIC:
+                typeStr = "EASE_OUT_ELASTIC";
+                break;
+            case SPLINE_CUSTOM:
+                typeStr = "SPLINE_CUSTOM (";
+                for (int i = offset; i < offset + len; i++) {
+                    typeStr += params[i] + " ";
+                }
+                typeStr += ")";
+                break;
+        }
+
+        String str = mDuration + " " + typeStr;
+        if (!Float.isNaN(initialValue)) {
+            str += " init =" + initialValue;
+        }
+        if (!Float.isNaN(wrapValue)) {
+            str += " wrap =" + wrapValue;
+        }
+        return str;
+    }
+
+    /**
      * Create an animation based on a float encoding of the animation
      *
      * @param description
@@ -208,21 +296,43 @@
         setScaleOffset();
     }
 
+    private static float wrap(float wrap, float value) {
+        value = value % wrap;
+        if (value < 0) {
+            value += wrap;
+        }
+        return value;
+    }
+
+    float wrapDistance(float wrap, float from, float to) {
+        float delta = (to - from) % 360;
+        if (delta < -wrap / 2) {
+            delta += wrap;
+        } else if (delta > wrap / 2) {
+            delta -= wrap;
+        }
+        return delta;
+    }
+
     /**
      * Set the target value to interpolate to
      *
      * @param value
      */
     public void setTargetValue(float value) {
-        if (Float.isNaN(mWrap)) {
-            mTargetValue = value;
-        } else {
-            if (Math.abs((value % mWrap) + mWrap - mInitialValue)
-                    < Math.abs((value % mWrap) - mInitialValue)) {
-                mTargetValue = (value % mWrap) + mWrap;
+        mTargetValue = value;
+        if (!Float.isNaN(mWrap)) {
+            mInitialValue = wrap(mWrap, mInitialValue);
+            mTargetValue = wrap(mWrap, mTargetValue);
+            if (Float.isNaN(mInitialValue)) {
+                mInitialValue = mTargetValue;
+            }
 
-            } else {
-                mTargetValue = value % mWrap;
+            float dist = wrapDistance(mWrap, mInitialValue, mTargetValue);
+            if ((dist > 0) && (mTargetValue < mInitialValue)) {
+                mTargetValue += mWrap;
+            } else if ((dist < 0) && (mTargetValue > mInitialValue)) {
+                mTargetValue -= mWrap;
             }
         }
         setScaleOffset();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java
index 75a6032..90b65bf 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java
@@ -15,10 +15,12 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.utilities.easing;
 
+import android.annotation.NonNull;
+
 /** Provides and interface to create easing functions */
 public class GeneralEasing extends Easing {
     float[] mEasingData = new float[0];
-    Easing mEasingCurve = new CubicEasing(CUBIC_STANDARD);
+    @NonNull Easing mEasingCurve = new CubicEasing(CUBIC_STANDARD);
 
     /**
      * Set the curve based on the float encoding of it
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java
index 9355cac..f540e70 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.utilities.easing;
 
+import android.annotation.NonNull;
+
 import java.util.Arrays;
 
 /** This performs a spline interpolation in multiple dimensions */
@@ -32,7 +34,7 @@
      * @param time the point along the curve
      * @param y the parameter at those points
      */
-    public MonotonicCurveFit(double[] time, double[][] y) {
+    public MonotonicCurveFit(@NonNull double[] time, @NonNull double[][] y) {
         final int n = time.length;
         final int dim = y[0].length;
         mSlopeTemp = new double[dim];
@@ -331,7 +333,8 @@
     }
 
     /** This builds a monotonic spline to be used as a wave function */
-    public static MonotonicCurveFit buildWave(String configString) {
+    @NonNull
+    public static MonotonicCurveFit buildWave(@NonNull String configString) {
         // done this way for efficiency
         String str = configString;
         double[] values = new double[str.length() / 2];
@@ -350,7 +353,8 @@
         return buildWave(Arrays.copyOf(values, count));
     }
 
-    private static MonotonicCurveFit buildWave(double[] values) {
+    @NonNull
+    private static MonotonicCurveFit buildWave(@NonNull double[] values) {
         int length = values.length * 3 - 2;
         int len = values.length - 1;
         double gap = 1.0 / len;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java
index b459689..c7be3ca 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.utilities.easing;
 
+import android.annotation.NonNull;
+
 /**
  * This class translates a series of floating point values into a continuous curve for use in an
  * easing function including quantize functions it is used with the "spline(0,0.3,0.3,0.5,...0.9,1)"
@@ -28,6 +30,7 @@
         mCurveFit = genSpline(params, offset, len);
     }
 
+    @NonNull
     private static MonotonicCurveFit genSpline(float[] values, int off, int arrayLen) {
         int length = arrayLen * 3 - 2;
         int len = arrayLen - 1;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/touch/VelocityEasing.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/touch/VelocityEasing.java
new file mode 100644
index 0000000..3e24372
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/touch/VelocityEasing.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.utilities.touch;
+
+/*
+ * 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.
+ */
+
+public class VelocityEasing {
+    private float mStartPos = 0;
+    private float mStartV = 0;
+    private float mEndPos = 0;
+    private float mDuration = 0;
+
+    private Stage[] mStage = {new Stage(1), new Stage(2), new Stage(3)};
+    private int mNumberOfStages = 0;
+    private Easing mEasing;
+    private double mEasingAdapterDistance = 0;
+    private double mEasingAdapterA = 0;
+    private double mEasingAdapterB = 0;
+    private boolean mOneDimension = true;
+    private float mTotalEasingDuration = 0;
+
+    public float getDuration() {
+        if (mEasing != null) {
+            return mTotalEasingDuration;
+        }
+        return mDuration;
+    }
+
+    public float getV(float t) {
+        if (mEasing == null) {
+            for (int i = 0; i < mNumberOfStages; i++) {
+                if (mStage[i].mEndTime > t) {
+                    return mStage[i].getVel(t);
+                }
+            }
+            return 0f;
+        }
+        int lastStages = mNumberOfStages - 1;
+        for (int i = 0; i < lastStages; i++) {
+            if (mStage[i].mEndTime > t) {
+                return mStage[i].getVel(t);
+            }
+        }
+        return (float) getEasingDiff((t - mStage[lastStages].mStartTime));
+    }
+
+    public float getPos(float t) {
+        if (mEasing == null) {
+            for (int i = 0; i < mNumberOfStages; i++) {
+                if (mStage[i].mEndTime > t) {
+                    return mStage[i].getPos(t);
+                }
+            }
+            return mEndPos;
+        }
+        int lastStages = mNumberOfStages - 1;
+        for (int i = 0; i < lastStages; i++) {
+            if (mStage[i].mEndTime > t) {
+                return mStage[i].getPos(t);
+            }
+        }
+        var ret = (float) getEasing((t - mStage[lastStages].mStartTime));
+        ret += mStage[lastStages].mStartPos;
+        return ret;
+    }
+
+    public String toString() {
+        var s = " ";
+        for (int i = 0; i < mNumberOfStages; i++) {
+            Stage stage = mStage[i];
+            s += " $i $stage";
+        }
+        return s;
+    }
+
+    public void config(
+            float currentPos,
+            float destination,
+            float currentVelocity,
+            float maxTime,
+            float maxAcceleration,
+            float maxVelocity,
+            Easing easing) {
+        float pos = currentPos;
+        float velocity = currentVelocity;
+        if (pos == destination) {
+            pos += 1f;
+        }
+        mStartPos = pos;
+        mEndPos = destination;
+        if (easing != null) {
+            this.mEasing = easing.clone();
+        }
+        float dir = Math.signum(destination - pos);
+        float maxV = maxVelocity * dir;
+        float maxA = maxAcceleration * dir;
+        if (velocity == 0.0) {
+            velocity = 0.0001f * dir;
+        }
+        mStartV = velocity;
+        if (!rampDown(pos, destination, velocity, maxTime)) {
+            if (!(mOneDimension
+                    && cruseThenRampDown(pos, destination, velocity, maxTime, maxA, maxV))) {
+                if (!rampUpRampDown(pos, destination, velocity, maxA, maxV, maxTime)) {
+                    rampUpCruseRampDown(pos, destination, velocity, maxA, maxV, maxTime);
+                }
+            }
+        }
+        if (mOneDimension) {
+            configureEasingAdapter();
+        }
+    }
+
+    private boolean rampDown(
+            float currentPos, float destination, float currentVelocity, float maxTime) {
+        float timeToDestination = 2 * ((destination - currentPos) / currentVelocity);
+        if (timeToDestination > 0 && timeToDestination <= maxTime) { // hit the brakes
+            mNumberOfStages = 1;
+            mStage[0].setUp(currentVelocity, currentPos, 0f, 0f, destination, timeToDestination);
+            mDuration = timeToDestination;
+            return true;
+        }
+        return false;
+    }
+
+    private boolean cruseThenRampDown(
+            float currentPos,
+            float destination,
+            float currentVelocity,
+            float maxTime,
+            float maxA,
+            float maxV) {
+        float timeToBreak = currentVelocity / maxA;
+        float brakeDist = currentVelocity * timeToBreak / 2;
+        float cruseDist = destination - currentPos - brakeDist;
+        float cruseTime = cruseDist / currentVelocity;
+        float totalTime = cruseTime + timeToBreak;
+        if (totalTime > 0 && totalTime < maxTime) {
+            mNumberOfStages = 2;
+            mStage[0].setUp(currentVelocity, currentPos, 0f, currentVelocity, cruseDist, cruseTime);
+            mStage[1].setUp(
+                    currentVelocity,
+                    currentPos + cruseDist,
+                    cruseTime,
+                    0f,
+                    destination,
+                    cruseTime + timeToBreak);
+            mDuration = cruseTime + timeToBreak;
+            return true;
+        }
+        return false;
+    }
+
+    private boolean rampUpRampDown(
+            float currentPos,
+            float destination,
+            float currentVelocity,
+            float maxA,
+            float maxVelocity,
+            float maxTime) {
+        float peak_v =
+                Math.signum(maxA)
+                        * (float)
+                                Math.sqrt(
+                                        (maxA * (destination - currentPos)
+                                                + currentVelocity * currentVelocity / 2));
+        if (maxVelocity / peak_v > 1) {
+            float t1 = (peak_v - currentVelocity) / maxA;
+            float d1 = (peak_v + currentVelocity) * t1 / 2 + currentPos;
+            float t2 = peak_v / maxA;
+            mNumberOfStages = 2;
+            mStage[0].setUp(currentVelocity, currentPos, 0f, peak_v, d1, t1);
+            mStage[1].setUp(peak_v, d1, t1, 0f, destination, t2 + t1);
+            mDuration = t2 + t1;
+            if (mDuration > maxTime) {
+                return false;
+            }
+            if (mDuration < maxTime / 2) {
+                t1 = mDuration / 2;
+                t2 = t1;
+                peak_v = (2 * (destination - currentPos) / t1 - currentVelocity) / 2;
+                d1 = (peak_v + currentVelocity) * t1 / 2 + currentPos;
+                mNumberOfStages = 2;
+                mStage[0].setUp(currentVelocity, currentPos, 0f, peak_v, d1, t1);
+                mStage[1].setUp(peak_v, d1, t1, 0f, destination, t2 + t1);
+                mDuration = t2 + t1;
+                if (mDuration > maxTime) {
+                    System.out.println(" fail ");
+                    return false;
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+    private void rampUpCruseRampDown(
+            float currentPos,
+            float destination,
+            float currentVelocity,
+            float maxA,
+            float maxV,
+            float maxTime) {
+        float t1 = maxTime / 3;
+        float t2 = t1 * 2;
+        float distance = destination - currentPos;
+        float dt2 = t2 - t1;
+        float dt3 = maxTime - t2;
+        float v1 = (2 * distance - currentVelocity * t1) / (t1 + 2 * dt2 + dt3);
+        mDuration = maxTime;
+        float d1 = (currentVelocity + v1) * t1 / 2;
+        float d2 = (v1 + v1) * (t2 - t1) / 2;
+        mNumberOfStages = 3;
+        float acc = (v1 - currentVelocity) / t1;
+        float dec = v1 / dt3;
+        mStage[0].setUp(currentVelocity, currentPos, 0f, v1, currentPos + d1, t1);
+        mStage[1].setUp(v1, currentPos + d1, t1, v1, currentPos + d1 + d2, t2);
+        mStage[2].setUp(v1, currentPos + d1 + d2, t2, 0f, destination, maxTime);
+        mDuration = maxTime;
+    }
+
+    double getEasing(double t) {
+        double gx = t * t * mEasingAdapterA + t * mEasingAdapterB;
+        if (gx > 1) {
+            return mEasingAdapterDistance;
+        } else {
+            return mEasing.get(gx) * mEasingAdapterDistance;
+        }
+    }
+
+    private double getEasingDiff(double t) {
+        double gx = t * t * mEasingAdapterA + t * mEasingAdapterB;
+        if (gx > 1) {
+            return 0.0;
+        } else {
+            return mEasing.getDiff(gx)
+                    * mEasingAdapterDistance
+                    * (t * mEasingAdapterA + mEasingAdapterB);
+        }
+    }
+
+    protected void configureEasingAdapter() {
+        if (mEasing == null) {
+            return;
+        }
+        int last = mNumberOfStages - 1;
+        float initialVelocity = mStage[last].mStartV;
+        float distance = mStage[last].mEndPos - mStage[last].mStartPos;
+        float duration = mStage[last].mEndTime - mStage[last].mStartTime;
+        double baseVel = mEasing.getDiff(0.0);
+        mEasingAdapterB = initialVelocity / (baseVel * distance);
+        mEasingAdapterA = 1 - mEasingAdapterB;
+        mEasingAdapterDistance = distance;
+        double easingDuration =
+                (Math.sqrt(4 * mEasingAdapterA + mEasingAdapterB * mEasingAdapterB)
+                                - mEasingAdapterB)
+                        / (2 * mEasingAdapterA);
+        mTotalEasingDuration = (float) (easingDuration + mStage[last].mStartTime);
+    }
+
+    interface Easing {
+        double get(double t);
+
+        double getDiff(double t);
+
+        Easing clone();
+    }
+
+    class Stage {
+        private float mStartV = 0;
+        private float mStartPos = 0;
+        private float mStartTime = 0;
+        private float mEndV = 0;
+        private float mEndPos = 0;
+        private float mEndTime = 0;
+        private float mDeltaV = 0;
+        private float mDeltaT = 0;
+        final int mStage;
+
+        Stage(int n) {
+            mStage = n;
+        }
+
+        void setUp(
+                float startV,
+                float startPos,
+                float startTime,
+                float endV,
+                float endPos,
+                float endTime) {
+            this.mStartV = startV;
+            this.mStartPos = startPos;
+            this.mStartTime = startTime;
+            this.mEndV = endV;
+            this.mEndTime = endTime;
+            this.mEndPos = endPos;
+            mDeltaV = this.mEndV - this.mStartV;
+            mDeltaT = this.mEndTime - this.mStartTime;
+        }
+
+        float getPos(float t) {
+            float dt = t - mStartTime;
+            float pt = dt / mDeltaT;
+            float v = mStartV + mDeltaV * pt;
+            return dt * (mStartV + v) / 2 + mStartPos;
+        }
+
+        float getVel(float t) {
+            float dt = t - mStartTime;
+            float pt = dt / (mEndTime - mStartTime);
+            return mStartV + mDeltaV * pt;
+        }
+    }
+}
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 57a8042..3fba8ac 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
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.BYTE;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -47,23 +49,26 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mId, mValue);
     }
 
     @Override
     public void apply(RemoteContext context) {}
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return toString();
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "BooleanConstant[" + mId + "] = " + mValue + "";
     }
 
+    @NonNull
     public static String name() {
         return "OrigamiBoolean";
     }
@@ -79,20 +84,20 @@
      * @param id
      * @param value
      */
-    public static void apply(WireBuffer buffer, int id, boolean value) {
+    public static void apply(@NonNull WireBuffer buffer, int id, boolean value) {
         buffer.start(OP_CODE);
         buffer.writeInt(id);
         buffer.writeBoolean(value);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
 
         boolean value = buffer.readBoolean();
         operations.add(new BooleanConstant(id, value));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, "BooleanConstant")
                 .description("A boolean and its associated id")
                 .field(DocumentedOperation.INT, "id", "id of Int")
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 3ef9db9..79f2a8d 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
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -37,25 +39,28 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mId, mValue);
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         context.loadInteger(mId, mValue);
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return toString();
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "IntegerConstant[" + mId + "] = " + mValue + "";
     }
 
+    @NonNull
     public static String name() {
         return "IntegerConstant";
     }
@@ -71,20 +76,20 @@
      * @param textId
      * @param value
      */
-    public static void apply(WireBuffer buffer, int textId, int value) {
+    public static void apply(@NonNull WireBuffer buffer, int textId, int value) {
         buffer.start(Operations.DATA_INT);
         buffer.writeInt(textId);
         buffer.writeInt(value);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
 
         int value = buffer.readInt();
         operations.add(new IntegerConstant(id, value));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", id(), "IntegerConstant")
                 .description("A integer and its associated id")
                 .field(DocumentedOperation.INT, "id", "id of Int")
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 6d51d19..01672b4 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
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.LONG;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -47,7 +49,7 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mId, mValue);
     }
 
@@ -56,11 +58,13 @@
         context.putObject(mId, this);
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return toString();
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "LongConstant[" + mId + "] = " + mValue + "";
@@ -73,20 +77,20 @@
      * @param id
      * @param value
      */
-    public static void apply(WireBuffer buffer, int id, long value) {
+    public static void apply(@NonNull WireBuffer buffer, int id, long value) {
         buffer.start(OP_CODE);
         buffer.writeInt(id);
         buffer.writeLong(value);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
 
         long value = buffer.readLong();
         operations.add(new LongConstant(id, value));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, "LongConstant")
                 .description("A boolean and its associated id")
                 .field(DocumentedOperation.INT, "id", "id of Int")
diff --git a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java
index 906282c..aaee9c5 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java
@@ -112,6 +112,16 @@
     }
 
     /**
+     * Gets a array of Names of the named variables of a specific type defined in the doc.
+     *
+     * @param type the type of variable NamedVariable.COLOR_TYPE, STRING_TYPE, etc
+     * @return array of name or null
+     */
+    public String[] getNamedVariables(int type) {
+        return mDocument.getNamedVariables(type);
+    }
+
+    /**
      * Return a component associated with id
      *
      * @param id the component 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 06bf4cd..cc74b11 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
@@ -21,11 +21,14 @@
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.TypedValue;
+import android.view.HapticFeedbackConstants;
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
 import android.widget.HorizontalScrollView;
 import android.widget.ScrollView;
 
+import com.android.internal.widget.remotecompose.core.CoreDocument;
+import com.android.internal.widget.remotecompose.core.operations.NamedVariable;
 import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior;
 import com.android.internal.widget.remotecompose.player.platform.RemoteComposeCanvas;
 
@@ -57,11 +60,7 @@
      * @param debugFlags 1 to set debug on
      */
     public void setDebug(int debugFlags) {
-        if (debugFlags == 1) {
-            mInner.setDebug(true);
-        } else {
-            mInner.setDebug(false);
-        }
+        mInner.setDebug(debugFlags);
     }
 
     public RemoteComposeDocument getDocument() {
@@ -82,6 +81,14 @@
             mInner.setDocument(null);
         }
         mapColors();
+        mInner.setHapticEngine(
+                new CoreDocument.HapticEngine() {
+
+                    @Override
+                    public void haptic(int type) {
+                        provideHapticFeedback(type);
+                    }
+                });
     }
 
     /**
@@ -259,13 +266,40 @@
     /**
      * This returns a list of colors that have names in the Document.
      *
-     * @return
+     * @return the names of named Strings or null
      */
     public String[] getNamedColors() {
         return mInner.getNamedColors();
     }
 
     /**
+     * This returns a list of floats that have names in the Document.
+     *
+     * @return return the names of named floats in the document
+     */
+    public String[] getNamedFloats() {
+        return mInner.getNamedVariables(NamedVariable.FLOAT_TYPE);
+    }
+
+    /**
+     * This returns a list of string name that have names in the Document.
+     *
+     * @return the name of named string (not the string itself)
+     */
+    public String[] getNamedStrings() {
+        return mInner.getNamedVariables(NamedVariable.STRING_TYPE);
+    }
+
+    /**
+     * This returns a list of images that have names in the Document.
+     *
+     * @return
+     */
+    public String[] getNamedImages() {
+        return mInner.getNamedVariables(NamedVariable.IMAGE_TYPE);
+    }
+
+    /**
      * This sets a color based on its name. Overriding the color set in the document.
      *
      * @param colorName Name of the color
@@ -481,4 +515,32 @@
             return color;
         }
     }
+
+    private static int[] sHapticTable = {
+        HapticFeedbackConstants.NO_HAPTICS,
+        HapticFeedbackConstants.LONG_PRESS,
+        HapticFeedbackConstants.VIRTUAL_KEY,
+        HapticFeedbackConstants.KEYBOARD_TAP,
+        HapticFeedbackConstants.CLOCK_TICK,
+        HapticFeedbackConstants.CONTEXT_CLICK,
+        HapticFeedbackConstants.KEYBOARD_PRESS,
+        HapticFeedbackConstants.KEYBOARD_RELEASE,
+        HapticFeedbackConstants.VIRTUAL_KEY_RELEASE,
+        HapticFeedbackConstants.TEXT_HANDLE_MOVE,
+        HapticFeedbackConstants.GESTURE_START,
+        HapticFeedbackConstants.GESTURE_END,
+        HapticFeedbackConstants.CONFIRM,
+        HapticFeedbackConstants.REJECT,
+        HapticFeedbackConstants.TOGGLE_ON,
+        HapticFeedbackConstants.TOGGLE_OFF,
+        HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE,
+        HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE,
+        HapticFeedbackConstants.DRAG_START,
+        HapticFeedbackConstants.SEGMENT_TICK,
+        HapticFeedbackConstants.SEGMENT_FREQUENT_TICK,
+    };
+
+    private void provideHapticFeedback(int type) {
+        performHapticFeedback(sHapticTable[type % sHapticTable.length]);
+    }
 }
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 f59a0d3..0b650a9 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java
@@ -26,6 +26,8 @@
 import android.graphics.RadialGradient;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.graphics.RenderEffect;
+import android.graphics.RenderNode;
 import android.graphics.RuntimeShader;
 import android.graphics.Shader;
 import android.graphics.SweepGradient;
@@ -51,6 +53,8 @@
     List<Paint> mPaintList = new ArrayList<>();
     Canvas mCanvas;
     Rect mTmpRect = new Rect(); // use in calculation of bounds
+    RenderNode mNode = null;
+    Canvas mPreviousCanvas = null;
 
     public AndroidPaintContext(RemoteContext context, Canvas canvas) {
         super(context);
@@ -122,6 +126,53 @@
     }
 
     @Override
+    public void startGraphicsLayer(int w, int h) {
+        mNode = new RenderNode("layer");
+        mNode.setPosition(0, 0, w, h);
+        mPreviousCanvas = mCanvas;
+        mCanvas = mNode.beginRecording();
+    }
+
+    @Override
+    public void setGraphicsLayer(
+            float scaleX,
+            float scaleY,
+            float rotationX,
+            float rotationY,
+            float rotationZ,
+            float shadowElevation,
+            float transformOriginX,
+            float transformOriginY,
+            float alpha,
+            int renderEffectId) {
+        if (mNode == null) {
+            return;
+        }
+        mNode.setScaleX(scaleX);
+        mNode.setScaleY(scaleY);
+        mNode.setRotationX(rotationX);
+        mNode.setRotationY(rotationY);
+        mNode.setRotationZ(rotationZ);
+        mNode.setPivotX(transformOriginX * mNode.getWidth());
+        mNode.setPivotY(transformOriginY * mNode.getHeight());
+        mNode.setAlpha(alpha);
+        if (renderEffectId == 1) {
+
+            RenderEffect effect = RenderEffect.createBlurEffect(8f, 8f, Shader.TileMode.CLAMP);
+            mNode.setRenderEffect(effect);
+        }
+    }
+
+    @Override
+    public void endGraphicsLayer() {
+        mNode.endRecording();
+        mCanvas = mPreviousCanvas;
+        mCanvas.drawRenderNode(mNode);
+        // node.discardDisplayList();
+        mNode = null;
+    }
+
+    @Override
     public void translate(float translateX, float translateY) {
         mCanvas.translate(translateX, translateY);
     }
@@ -241,6 +292,8 @@
             if (start != 0) {
                 textToPaint = textToPaint.substring(start);
             }
+        } else if (end > textToPaint.length()) {
+            textToPaint = textToPaint.substring(start);
         } else {
             textToPaint = textToPaint.substring(start, end);
         }
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPlatformServices.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPlatformServices.java
index f9b22a2..f28e85a 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPlatformServices.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPlatformServices.java
@@ -18,6 +18,7 @@
 import android.graphics.Bitmap;
 import android.graphics.Path;
 import android.graphics.PathIterator;
+import android.util.Log;
 
 import com.android.internal.widget.remotecompose.core.Platform;
 import com.android.internal.widget.remotecompose.core.operations.PathData;
@@ -27,6 +28,8 @@
 
 /** Services that are needed to be provided by the platform during encoding. */
 public class AndroidPlatformServices implements Platform {
+    private static final String LOG_TAG = "RemoteCompose";
+
     @Override
     public byte[] imageToByteArray(Object image) {
         if (image instanceof Bitmap) {
@@ -67,6 +70,24 @@
         return null;
     }
 
+    @Override
+    public void log(LogCategory category, String message) {
+        switch (category) {
+            case DEBUG:
+                Log.d(LOG_TAG, message);
+                break;
+            case INFO:
+                Log.i(LOG_TAG, message);
+                break;
+            case WARN:
+                Log.w(LOG_TAG, message);
+                break;
+            default:
+                Log.e(LOG_TAG, message);
+                break;
+        }
+    }
+
     private float[] androidPathToFloatArray(Path path) {
         PathIterator i = path.getPathIterator();
         int estimatedSize = 0;
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java
index e7c0cc8..7a7edba 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java
@@ -20,6 +20,7 @@
 import android.graphics.Canvas;
 
 import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.TouchListener;
 import com.android.internal.widget.remotecompose.core.VariableSupport;
 import com.android.internal.widget.remotecompose.core.operations.FloatExpression;
 import com.android.internal.widget.remotecompose.core.operations.ShaderData;
@@ -143,9 +144,9 @@
     }
 
     @Override
-    public void runNamedAction(int id) {
+    public void runNamedAction(int id, Object value) {
         String text = getText(id);
-        mDocument.runNamedAction(text);
+        mDocument.runNamedAction(text, value);
     }
 
     /**
@@ -200,6 +201,11 @@
     }
 
     @Override
+    public void overrideFloat(int id, float value) {
+        mRemoteComposeState.overrideFloat(id, value);
+    }
+
+    @Override
     public void loadInteger(int id, int value) {
         mRemoteComposeState.updateInteger(id, value);
     }
@@ -268,6 +274,11 @@
         return (ShaderData) mRemoteComposeState.getFromId(id);
     }
 
+    @Override
+    public void addTouchListener(TouchListener touchExpression) {
+        mDocument.addTouchListener(touchExpression);
+    }
+
     ///////////////////////////////////////////////////////////////////////////////////////////////
     // Click handling
     ///////////////////////////////////////////////////////////////////////////////////////////////
@@ -285,4 +296,8 @@
         String metadata = (String) mRemoteComposeState.getFromId(metadataId);
         mDocument.addClickArea(id, contentDescription, left, top, right, bottom, metadata);
     }
+
+    public void hapticEffect(int type) {
+        mDocument.haptic(type);
+    }
 }
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 7de6988..b54ed8a 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
@@ -21,6 +21,7 @@
 import android.graphics.Point;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
+import android.view.VelocityTracker;
 import android.view.View;
 import android.widget.FrameLayout;
 
@@ -38,7 +39,7 @@
     RemoteComposeDocument mDocument = null;
     int mTheme = Theme.LIGHT;
     boolean mInActionDown = false;
-    boolean mDebug = false;
+    int mDebug = 0;
     boolean mHasClickAreas = false;
     Point mActionDownPoint = new Point(0, 0);
     AndroidRemoteContext mARContext = new AndroidRemoteContext();
@@ -65,14 +66,14 @@
         }
     }
 
-    public void setDebug(boolean value) {
+    public void setDebug(int value) {
         if (mDebug != value) {
             mDebug = value;
             if (USE_VIEW_AREA_CLICK) {
                 for (int i = 0; i < getChildCount(); i++) {
                     View child = getChildAt(i);
                     if (child instanceof ClickAreaView) {
-                        ((ClickAreaView) child).setDebug(mDebug);
+                        ((ClickAreaView) child).setDebug(mDebug == 1);
                     }
                 }
             }
@@ -107,7 +108,7 @@
                 ClickAreaView viewArea =
                         new ClickAreaView(
                                 getContext(),
-                                mDebug,
+                                mDebug == 1,
                                 area.getId(),
                                 area.getContentDescription(),
                                 area.getMetadata());
@@ -128,6 +129,10 @@
         }
     }
 
+    public void setHapticEngine(CoreDocument.HapticEngine engine) {
+        mDocument.getDocument().setHapticEngine(engine);
+    }
+
     @Override
     public void onViewDetachedFromWindow(View view) {
         removeAllViews();
@@ -138,6 +143,16 @@
     }
 
     /**
+     * Gets a array of Names of the named variables of a specific type defined in the loaded doc.
+     *
+     * @param type the type of variable NamedVariable.COLOR_TYPE, STRING_TYPE, etc
+     * @return array of name or null
+     */
+    public String[] getNamedVariables(int type) {
+        return mDocument.getNamedVariables(type);
+    }
+
+    /**
      * set the color associated with this name.
      *
      * @param colorName Name of color typically "android.xxx"
@@ -198,7 +213,12 @@
         this.mTheme = theme;
     }
 
+    private VelocityTracker mVelocityTracker = null;
+
     public boolean onTouchEvent(MotionEvent event) {
+        int index = event.getActionIndex();
+        int action = event.getActionMasked();
+        int pointerId = event.getPointerId(index);
         if (USE_VIEW_AREA_CLICK && mHasClickAreas) {
             return super.onTouchEvent(event);
         }
@@ -207,15 +227,51 @@
                 mActionDownPoint.x = (int) event.getX();
                 mActionDownPoint.y = (int) event.getY();
                 mInActionDown = true;
+                CoreDocument doc = mDocument.getDocument();
+                if (doc.hasTouchListener()) {
+                    if (mVelocityTracker == null) {
+                        mVelocityTracker = VelocityTracker.obtain();
+                    } else {
+                        mVelocityTracker.clear();
+                    }
+                    mVelocityTracker.addMovement(event);
+                    doc.touchDown(mARContext, event.getX(), event.getY());
+                }
                 return true;
+
             case MotionEvent.ACTION_CANCEL:
                 mInActionDown = false;
+                doc = mDocument.getDocument();
+                if (doc.hasTouchListener()) {
+                    mVelocityTracker.computeCurrentVelocity(1000);
+                    float dx = mVelocityTracker.getXVelocity(pointerId);
+                    float dy = mVelocityTracker.getYVelocity(pointerId);
+                    doc.touchCancel(mARContext, event.getX(), event.getY(), dx, dy);
+                }
                 return true;
             case MotionEvent.ACTION_UP:
                 mInActionDown = false;
                 performClick();
+                doc = mDocument.getDocument();
+                if (doc.hasTouchListener()) {
+                    mVelocityTracker.computeCurrentVelocity(1000);
+                    float dx = mVelocityTracker.getXVelocity(pointerId);
+                    float dy = mVelocityTracker.getYVelocity(pointerId);
+                    doc.touchUp(mARContext, event.getX(), event.getY(), dx, dy);
+                }
                 return true;
+
             case MotionEvent.ACTION_MOVE:
+                if (mInActionDown) {
+                    if (mVelocityTracker != null) {
+                        mVelocityTracker.addMovement(event);
+                        doc = mDocument.getDocument();
+                        boolean repaint = doc.touchDrag(mARContext, event.getX(), event.getY());
+                        if (repaint) {
+                            invalidate();
+                        }
+                    }
+                }
         }
         return false;
     }
@@ -292,7 +348,7 @@
         mARContext.mWidth = getWidth();
         mARContext.mHeight = getHeight();
         mDocument.paint(mARContext, mTheme);
-        if (mDebug) {
+        if (mDebug == 1) {
             mCount++;
             if (System.nanoTime() - mTime > 1000000000L) {
                 System.out.println(" count " + mCount + " fps");
diff --git a/core/res/res/values/stoppable_fgs_system_apps.xml b/core/res/res/values/stoppable_fgs_system_apps.xml
new file mode 100644
index 0000000..165ff61
--- /dev/null
+++ b/core/res/res/values/stoppable_fgs_system_apps.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources>
+    <!-- A list of system apps whose FGS can be stopped in the task manager. -->
+    <string-array translatable="false" name="stoppable_fgs_system_apps">
+    </string-array>
+    <!-- stoppable_fgs_system_apps which is supposed to be overridden by vendor -->
+    <string-array translatable="false" name="vendor_stoppable_fgs_system_apps">
+    </string-array>
+</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index aa08d5e..db81a3b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1306,6 +1306,8 @@
   <java-symbol type="array" name="vendor_policy_exempt_apps" />
   <java-symbol type="array" name="cloneable_apps" />
   <java-symbol type="array" name="config_securityStatePackages" />
+  <java-symbol type="array" name="stoppable_fgs_system_apps" />
+  <java-symbol type="array" name="vendor_stoppable_fgs_system_apps" />
 
   <java-symbol type="drawable" name="default_wallpaper" />
   <java-symbol type="drawable" name="default_lock_wallpaper" />
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
index 31a4f16..911b7ce 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
@@ -120,7 +120,8 @@
         doReturn(newDisplayInfo).when(mIDisplayManager).getDisplayInfo(123);
 
         mDisplayManager.registerDisplayListener(mListener, mHandler,
-                DisplayManager.EVENT_FLAG_DISPLAY_CHANGED, null /* packageName */);
+                DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED,
+                null /* packageName */);
 
         mController.onDisplayChanged(123);
         mHandler.runWithScissors(() -> { }, 0);
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
index 5a0dacb..9552c88 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
+++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
@@ -55,9 +55,10 @@
 @RunWith(AndroidJUnit4.class)
 public class DisplayManagerGlobalTest {
 
-    private static final long ALL_DISPLAY_EVENTS = DisplayManager.EVENT_FLAG_DISPLAY_ADDED
-            | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-            | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
+    private static final long ALL_DISPLAY_EVENTS =
+            DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
+            | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED
+            | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED;
 
     @Mock
     private IDisplayManager mDisplayManager;
@@ -127,19 +128,22 @@
 
         int displayId = 1;
         mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
-                ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_ADDED, null);
+                ALL_DISPLAY_EVENTS
+                        & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED, null);
         callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED);
         waitForHandler();
         Mockito.verifyZeroInteractions(mListener);
 
         mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
-                ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_CHANGED, null);
+                ALL_DISPLAY_EVENTS
+                        & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED, null);
         callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
         waitForHandler();
         Mockito.verifyZeroInteractions(mListener);
 
         mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
-                ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_REMOVED, null);
+                ALL_DISPLAY_EVENTS
+                        & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED, null);
         callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
         waitForHandler();
         Mockito.verifyZeroInteractions(mListener);
@@ -162,22 +166,25 @@
     public void testDisplayManagerGlobalRegistersWithDisplayManager_WhenThereAreListeners()
             throws RemoteException {
         mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
-                DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS, null);
+                DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED,
+                null);
         InOrder inOrder = Mockito.inOrder(mDisplayManager);
 
         inOrder.verify(mDisplayManager)
                 .registerCallbackWithEventMask(mCallbackCaptor.capture(),
-                        eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+                        eq(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED));
 
         mDisplayManagerGlobal.registerNativeChoreographerForRefreshRateCallbacks();
         inOrder.verify(mDisplayManager)
                 .registerCallbackWithEventMask(mCallbackCaptor.capture(),
-                        eq(ALL_DISPLAY_EVENTS | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+                        eq(ALL_DISPLAY_EVENTS
+                                | DisplayManagerGlobal
+                                .INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED));
 
         mDisplayManagerGlobal.unregisterNativeChoreographerForRefreshRateCallbacks();
         inOrder.verify(mDisplayManager)
                 .registerCallbackWithEventMask(mCallbackCaptor.capture(),
-                        eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+                        eq(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED));
 
         mDisplayManagerGlobal.unregisterDisplayListener(mListener);
         inOrder.verify(mDisplayManager)
@@ -196,10 +203,12 @@
 
         // One listener listens on add/remove, and the other one listens on change.
         mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
-                DisplayManager.EVENT_FLAG_DISPLAY_ADDED
-                        | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED, null /* packageName */);
+                DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
+                        | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED,
+                null /* packageName */);
         mDisplayManagerGlobal.registerDisplayListener(mListener2, mHandler,
-                DisplayManager.EVENT_FLAG_DISPLAY_CHANGED, null /* packageName */);
+                DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED,
+                null /* packageName */);
 
         mDisplayManagerGlobal.handleDisplayChangeFromWindowManager(321);
         waitForHandler();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index b9a3050..c92a278 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -21,6 +21,7 @@
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION;
+import static android.window.BackEvent.EDGE_NONE;
 import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
 import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
@@ -533,7 +534,15 @@
 
         if (keyAction == MotionEvent.ACTION_DOWN) {
             if (!mBackGestureStarted) {
-                mShouldStartOnNextMoveEvent = true;
+                if (swipeEdge == EDGE_NONE) {
+                    // start animation immediately for non-gestural sources (without ACTION_MOVE
+                    // events)
+                    mThresholdCrossed = true;
+                    onGestureStarted(touchX, touchY, swipeEdge);
+                    mShouldStartOnNextMoveEvent = false;
+                } else {
+                    mShouldStartOnNextMoveEvent = true;
+                }
             }
         } else if (keyAction == MotionEvent.ACTION_MOVE) {
             if (!mBackGestureStarted && mShouldStartOnNextMoveEvent) {
@@ -1074,6 +1083,11 @@
             mCurrentTracker.updateStartLocation();
             BackMotionEvent startEvent = mCurrentTracker.createStartEvent(mApps[0]);
             dispatchOnBackStarted(mActiveCallback, startEvent);
+            // TODO(b/373544911): onBackStarted is dispatched here so that
+            //  WindowOnBackInvokedDispatcher knows about the back navigation and intercepts touch
+            //  events while it's active. It would be cleaner and safer to disable multitouch
+            //  altogether (same as in gesture-nav).
+            dispatchOnBackStarted(mBackNavigationInfo.getOnBackInvokedCallback(), startEvent);
         }
     }
 
diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl
index 1346bd34..40fd068 100644
--- a/nfc/java/android/nfc/INfcAdapter.aidl
+++ b/nfc/java/android/nfc/INfcAdapter.aidl
@@ -119,4 +119,5 @@
     boolean getSettingStatus();
     boolean isTagPresent();
     List<Entry> getRoutingTableEntryList();
+    void indicateDataMigration(boolean inProgress, String pkg);
 }
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index d9fd42f..c5d8191 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -2795,11 +2795,8 @@
             @IntRange(from = 0, to = 15) int gid, @IntRange(from = 0) int oid,
             @NonNull byte[] payload) {
         Objects.requireNonNull(payload, "Payload must not be null");
-        try {
-            return sService.sendVendorNciMessage(mt, gid, oid, payload);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return callServiceReturn(() ->  sService.sendVendorNciMessage(mt, gid, oid, payload),
+                SEND_VENDOR_NCI_STATUS_FAILED);
     }
 
     /**
@@ -2873,6 +2870,18 @@
     }
 
     /**
+     * Used by data migration to indicate data migration is in progrerss or not.
+     *
+     * Note: This is @hide intentionally since the client is inside the NFC apex.
+     * @param inProgress true if migration is in progress, false once done.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    public void indicateDataMigration(boolean inProgress) {
+        callService(() -> sService.indicateDataMigration(inProgress, mContext.getPackageName()));
+    }
+
+    /**
      * Returns an instance of {@link NfcOemExtension} associated with {@link NfcAdapter} instance.
      * @hide
      */
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java
index 4f315a2..76aa5bf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java
@@ -75,6 +75,24 @@
                 @Override
                 public void onAudioDevicesAdded(@NonNull AudioDeviceInfo[] addedDevices) {
                     applyDefaultSelectedTypeToAllPresets();
+
+                    // Activate the last hot plugged valid input device, to match the output device
+                    // behavior.
+                    @AudioDeviceType int deviceTypeToActivate = mSelectedInputDeviceType;
+                    for (AudioDeviceInfo info : addedDevices) {
+                        if (InputMediaDevice.isSupportedInputDevice(info.getType())) {
+                            deviceTypeToActivate = info.getType();
+                        }
+                    }
+
+                    // Only activate if we find a different valid input device. e.g. if none of the
+                    // addedDevices is supported input device, we don't need to activate anything.
+                    if (mSelectedInputDeviceType != deviceTypeToActivate) {
+                        mSelectedInputDeviceType = deviceTypeToActivate;
+                        AudioDeviceAttributes deviceAttributes =
+                                createInputDeviceAttributes(mSelectedInputDeviceType);
+                        setPreferredDeviceForAllPresets(deviceAttributes);
+                    }
                 }
 
                 @Override
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java
index 782cee2..d808a25 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java
@@ -24,6 +24,7 @@
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -138,6 +139,18 @@
                 /* address= */ "");
     }
 
+    private AudioDeviceAttributes getUsbHeadsetDeviceAttributes() {
+        return new AudioDeviceAttributes(
+                AudioDeviceAttributes.ROLE_INPUT,
+                AudioDeviceInfo.TYPE_USB_HEADSET,
+                /* address= */ "");
+    }
+
+    private AudioDeviceAttributes getHdmiDeviceAttributes() {
+        return new AudioDeviceAttributes(
+                AudioDeviceAttributes.ROLE_INPUT, AudioDeviceInfo.TYPE_HDMI, /* address= */ "");
+    }
+
     private void onPreferredDevicesForCapturePresetChanged(InputRouteManager inputRouteManager) {
         final List<AudioDeviceAttributes> audioDeviceAttributesList =
                 new ArrayList<AudioDeviceAttributes>();
@@ -303,21 +316,47 @@
     }
 
     @Test
-    public void onAudioDevicesAdded_shouldApplyDefaultSelectedDeviceToAllPresets() {
+    public void onAudioDevicesAdded_shouldActivateAddedDevice() {
         final AudioManager audioManager = mock(AudioManager.class);
-        AudioDeviceAttributes wiredHeadsetDeviceAttributes = getWiredHeadsetDeviceAttributes();
-        when(audioManager.getDevicesForAttributes(INPUT_ATTRIBUTES))
-                .thenReturn(Collections.singletonList(wiredHeadsetDeviceAttributes));
-
         InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager);
         AudioDeviceInfo[] devices = {mockWiredHeadsetInfo()};
         inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices);
 
-        // Called twice, one after initiation, the other after onAudioDevicesAdded call.
-        verify(audioManager, atLeast(2)).getDevicesForAttributes(INPUT_ATTRIBUTES);
+        // The only added wired headset will be activated.
         for (@MediaRecorder.Source int preset : PRESETS) {
-            verify(audioManager, atLeast(2))
-                    .setPreferredDeviceForCapturePreset(preset, wiredHeadsetDeviceAttributes);
+            verify(audioManager, atLeast(1))
+                    .setPreferredDeviceForCapturePreset(preset, getWiredHeadsetDeviceAttributes());
+        }
+    }
+
+    @Test
+    public void onAudioDevicesAdded_shouldActivateLastAddedDevice() {
+        final AudioManager audioManager = mock(AudioManager.class);
+        InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager);
+        AudioDeviceInfo[] devices = {mockWiredHeadsetInfo(), mockUsbHeadsetInfo()};
+        inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices);
+
+        // When adding multiple valid input devices, the last added device (usb headset in this
+        // case) will be activated.
+        for (@MediaRecorder.Source int preset : PRESETS) {
+            verify(audioManager, never())
+                    .setPreferredDeviceForCapturePreset(preset, getWiredHeadsetDeviceAttributes());
+            verify(audioManager, atLeast(1))
+                    .setPreferredDeviceForCapturePreset(preset, getUsbHeadsetDeviceAttributes());
+        }
+    }
+
+    @Test
+    public void onAudioDevicesAdded_doNotActivateInvalidAddedDevice() {
+        final AudioManager audioManager = mock(AudioManager.class);
+        InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager);
+        AudioDeviceInfo[] devices = {mockHdmiInfo()};
+        inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices);
+
+        // Do not activate since HDMI is not a valid input device.
+        for (@MediaRecorder.Source int preset : PRESETS) {
+            verify(audioManager, never())
+                    .setPreferredDeviceForCapturePreset(preset, getHdmiDeviceAttributes());
         }
     }
 
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/TEST_MAPPING b/packages/SystemUI/accessibility/accessibilitymenu/TEST_MAPPING
index 1820f39..1903d22 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/TEST_MAPPING
+++ b/packages/SystemUI/accessibility/accessibilitymenu/TEST_MAPPING
@@ -1,8 +1,7 @@
 {
-  // TODO: b/324945360 - Re-enable on presubmit after fixing failures
   "postsubmit": [
     {
       "name": "AccessibilityMenuServiceTests"
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig
index 5251246..b5eba08 100644
--- a/packages/SystemUI/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/aconfig/accessibility.aconfig
@@ -118,3 +118,10 @@
       purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "floating_menu_hearing_device_status_icon"
+    namespace: "accessibility"
+    description: "Update hearing device icon in floating menu according to the connection status."
+    bug: "357882387"
+}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 3bf3e24..87ea2a7 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1743,3 +1743,13 @@
     description: "An implementation of shortcut customizations through shortcut helper."
     bug: "365064144"
 }
+
+flag {
+    name: "stoppable_fgs_system_app"
+    namespace: "systemui"
+    description: "System app with foreground service can opt in to be stoppable."
+    bug: "376564917"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt
index e9b7335..d58e1bf 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt
@@ -98,19 +98,20 @@
     theme: Int = SystemUIDialog.DEFAULT_THEME,
     dismissOnDeviceLock: Boolean = SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK,
     @GravityInt dialogGravity: Int? = null,
+    dialogDelegate: DialogDelegate<SystemUIDialog> =
+        object : DialogDelegate<SystemUIDialog> {
+            override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
+                super.onCreate(dialog, savedInstanceState)
+                dialogGravity?.let { dialog.window?.setGravity(it) }
+            }
+        },
     content: @Composable (SystemUIDialog) -> Unit,
 ): ComponentSystemUIDialog {
     return create(
         context = context,
         theme = theme,
         dismissOnDeviceLock = dismissOnDeviceLock,
-        delegate =
-            object : DialogDelegate<SystemUIDialog> {
-                override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
-                    super.onCreate(dialog, savedInstanceState)
-                    dialogGravity?.let { dialog.window?.setGravity(it) }
-                }
-            },
+        delegate = dialogDelegate,
         content = content,
     )
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt
index 0983105..f4cffc5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt
@@ -20,7 +20,7 @@
 import android.hardware.display.BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE
 import android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF
 import android.hardware.display.DisplayManager
-import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
+import android.hardware.display.DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS
 import android.view.Display
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -119,7 +119,8 @@
                     .registerDisplayListener(
                         capture(listenerCaptor),
                         eq(null),
-                        eq(EVENT_FLAG_DISPLAY_BRIGHTNESS),
+                        eq(0),
+                        eq(PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS),
                     )
 
                 val newBrightness = BrightnessInfo(0.6f, 0.3f, 0.9f)
@@ -157,7 +158,8 @@
                     .registerDisplayListener(
                         capture(listenerCaptor),
                         eq(null),
-                        eq(EVENT_FLAG_DISPLAY_BRIGHTNESS),
+                        eq(0),
+                        eq(PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS),
                     )
 
                 changeBrightnessInfoAndNotify(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
index cd8b2e1..e6e5665 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
@@ -540,7 +540,8 @@
             .registerDisplayListener(
                 connectedDisplayListener.capture(),
                 eq(testHandler),
-                eq(DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED),
+                eq(0),
+                eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED),
             )
         return flowValue
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarterTest.kt
index d1431ee..1580ea5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarterTest.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyboard.shortcut.data.source.FakeKeyboardShortcutGroupsSource
 import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts
+import com.android.systemui.keyboard.shortcut.shortcutCustomizationDialogStarterFactory
 import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource
 import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource
 import com.android.systemui.keyboard.shortcut.shortcutHelperInputShortcutsSource
@@ -71,7 +72,13 @@
 
     private val starter: ShortcutHelperDialogStarter =
         with(kosmos) {
-            ShortcutHelperDialogStarter(coroutineScope, viewModel, dialogFactory, activityStarter)
+            ShortcutHelperDialogStarter(
+                coroutineScope,
+                viewModel,
+                shortcutCustomizationDialogStarterFactory,
+                dialogFactory,
+                activityStarter,
+            )
         }
 
     @Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/FgsManagerControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/FgsManagerControllerTest.java
index 16ae466..0356422 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/FgsManagerControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/FgsManagerControllerTest.java
@@ -42,6 +42,7 @@
 import android.os.Binder;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
 import android.provider.DeviceConfig;
 import android.testing.TestableLooper;
 
@@ -49,6 +50,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
+import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.DialogTransitionAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -315,13 +317,36 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_STOPPABLE_FGS_SYSTEM_APP)
+    public void testButtonVisibilityOfStoppableApps() throws Exception {
+        setUserProfiles(0);
+        setBackgroundRestrictionExemptionReason("pkg", 12345, REASON_ALLOWLISTED_PACKAGE);
+        setBackgroundRestrictionExemptionReason("vendor_pkg", 67890, REASON_ALLOWLISTED_PACKAGE);
+
+        // Same as above, but apps are opt-in to be stoppable
+        setStoppableApps(new String[] {"pkg"}, /* vendor */ false);
+        setStoppableApps(new String[] {"vendor_pkg"}, /* vendor */ true);
+
+        final Binder binder = new Binder();
+        setShowStopButtonForUserAllowlistedApps(true);
+        // Both are foreground.
+        mIForegroundServiceObserver.onForegroundStateChanged(binder, "pkg", 0, true);
+        mIForegroundServiceObserver.onForegroundStateChanged(binder, "vendor_pkg", 0, true);
+        Assert.assertEquals(2, mFmc.visibleButtonsCount());
+
+        // The vendor package is no longer foreground. Only `pkg` remains.
+        mIForegroundServiceObserver.onForegroundStateChanged(binder, "vendor_pkg", 0, false);
+        Assert.assertEquals(1, mFmc.visibleButtonsCount());
+    }
+
+    @Test
     public void testShowUserVisibleJobsOnCreation() {
         // Test when the default is on.
         mDeviceConfigProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
                 SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_USER_VISIBLE_JOBS,
                 "true", false);
         FgsManagerController fmc = new FgsManagerControllerImpl(
-                mContext,
+                mContext.getResources(),
                 mMainExecutor,
                 mBackgroundExecutor,
                 mSystemClock,
@@ -348,7 +373,7 @@
                 SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_USER_VISIBLE_JOBS,
                 "false", false);
         fmc = new FgsManagerControllerImpl(
-                mContext,
+                mContext.getResources(),
                 mMainExecutor,
                 mBackgroundExecutor,
                 mSystemClock,
@@ -446,6 +471,11 @@
                 .getBackgroundRestrictionExemptionReason(uid);
     }
 
+    private void setStoppableApps(String[] packageNames, boolean vendor) throws Exception {
+        overrideResource(vendor ? com.android.internal.R.array.vendor_stoppable_fgs_system_apps
+                    : com.android.internal.R.array.stoppable_fgs_system_apps, packageNames);
+    }
+
     FgsManagerController createFgsManagerController() throws RemoteException {
         ArgumentCaptor<IForegroundServiceObserver> iForegroundServiceObserverArgumentCaptor =
                 ArgumentCaptor.forClass(IForegroundServiceObserver.class);
@@ -455,7 +485,7 @@
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
 
         FgsManagerController result = new FgsManagerControllerImpl(
-                mContext,
+                mContext.getResources(),
                 mMainExecutor,
                 mBackgroundExecutor,
                 mSystemClock,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
index 740abf3..76390fd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
@@ -18,12 +18,13 @@
 
 import android.content.res.mainResources
 import android.platform.test.annotations.DisableFlags
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
 import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.Flags
+import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -51,17 +52,20 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(AndroidJUnit4::class)
-class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class NotificationIconContainerAlwaysOnDisplayViewModelTest(flags: FlagsParameterization) :
+    SysuiTestCase() {
     private val kosmos =
         testKosmos().apply {
             fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, value = false) }
         }
 
-    val underTest =
+    val underTest by lazy {
         NotificationIconContainerAlwaysOnDisplayViewModel(
             kosmos.testDispatcher,
             kosmos.alwaysOnDisplayNotificationIconsInteractor,
@@ -70,11 +74,24 @@
             kosmos.mainResources,
             kosmos.shadeInteractor,
         )
+    }
     val testScope = kosmos.testScope
     val keyguardRepository = kosmos.fakeKeyguardRepository
     val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
     val powerRepository = kosmos.fakePowerRepository
 
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
+
     @Before
     fun setup() {
         keyguardRepository.setKeyguardShowing(true)
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 5a8417d..d30f73f 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3743,6 +3743,11 @@
          is a component that shows the user which keyboard shortcuts they can use.
          [CHAR LIMIT=NONE] -->
     <string name="shortcut_helper_customize_mode_title">Customize keyboard shortcuts</string>
+    <!-- Sub title at the top of the keyboard shortcut helper customization dialog. Explains to the
+         user what action they need to take in the customization dialog to assign a new custom shortcut.
+         The helper is a component that shows the user which keyboard shortcuts they can use.
+         [CHAR LIMIT=NONE] -->
+    <string name="shortcut_helper_customize_mode_sub_title">Press key to assign shortcut</string>
     <!-- Placeholder text shown in the search box of the keyboard shortcut helper, when the user
          hasn't typed in anything in the search box yet. The helper is a  component that shows the
          user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
@@ -3754,6 +3759,16 @@
          use. The helper shows shortcuts in categories, which can be collapsed or expanded.
          [CHAR LIMIT=NONE] -->
     <string name="shortcut_helper_content_description_collapse_icon">Collapse icon</string>
+    <!-- Content description of the Meta key (also called Action Key) icon that prompts users to
+         press some key combination starting with meta key to assign new key combination to shortcut
+         in shortcut helper customization dialog. The helper is a  component that shows the  user
+         which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
+    <string name="shortcut_helper_content_description_meta_key">Action or Meta key icon</string>
+    <!-- Content description of the plus icon after the meta key icon prompts users to
+         press some key combination starting with meta key to assign new key combination to shortcut
+         in shortcut helper customization dialog. The helper is a  component that shows the  user
+         which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
+    <string name="shortcut_helper_content_description_plus_icon">Plus icon</string>
     <!-- Description text of the button that allows user to customize shortcuts in keyboard
          shortcut helper The helper is a  component that shows the  user which keyboard shortcuts
          they can use. [CHAR LIMIT=NONE] -->
@@ -3784,6 +3799,24 @@
          open keyboard settings while in shortcut helper. The helper is a  component that shows the
          user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
     <string name="shortcut_helper_keyboard_settings_buttons_label">Keyboard Settings</string>
+    <!-- Label on the set shortcut button in keyboard shortcut helper customize dialog, that allows user to
+         confirm and assign key combination to selected shortcut. The helper is a  component that
+         shows the user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
+    <string name="shortcut_helper_customize_dialog_set_shortcut_button_label">Set shortcut</string>
+    <!-- Label on the cancel button in keyboard shortcut helper customize dialog, that allows user to
+         cancel and exit shortcut customization dialog, returning to the main shortcut helper page.
+         The helper is a  component that shows the user which keyboard shortcuts they can use.
+         [CHAR LIMIT=NONE] -->
+    <string name="shortcut_helper_customize_dialog_cancel_button_label">Cancel</string>
+    <!-- Placeholder text, prompting user to Press key combination assign to shortcut. This is shown
+         in shortcut helper's "Add Custom Shortcut" Dialog text field when user hasn't pressed
+         any key  yet. The helper is a component that shows the user which keyboard shortcuts
+         they can use. [CHAR LIMIT=NONE] -->
+    <string name="shortcut_helper_add_shortcut_dialog_placeholder">Press key</string>
+    <!-- Error message displayed when the user select a key combination that is already in use while
+         assigning a new custom key combination to a shortcut in shortcut helper. The helper is a
+         component that shows the user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
+    <string name="shortcut_helper_customize_dialog_error_message">Key combination already in use. Try another key.</string>
 
 
     <!-- Keyboard touchpad tutorial scheduler-->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 6209ed8..e332280 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -21,6 +21,7 @@
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.UserHandle;
+import android.view.KeyEvent;
 import android.view.MotionEvent;
 import com.android.internal.util.ScreenshotRequest;
 
@@ -102,9 +103,9 @@
     oneway void expandNotificationPanel() = 29;
 
     /**
-     * Notifies SystemUI to invoke Back.
+     * Notifies SystemUI of a back KeyEvent.
      */
-    oneway void onBackPressed() = 44;
+    oneway void onBackEvent(in KeyEvent keyEvent) = 44;
 
     /** Sets home rotation enabled. */
     oneway void setHomeRotationEnabled(boolean enabled) = 45;
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt
index 06d3917..3270c71 100644
--- a/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt
@@ -19,6 +19,7 @@
 import android.annotation.SuppressLint
 import android.hardware.display.BrightnessInfo
 import android.hardware.display.DisplayManager
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.brightness.shared.model.BrightnessLog
 import com.android.systemui.brightness.shared.model.LinearBrightness
 import com.android.systemui.brightness.shared.model.formatBrightness
@@ -46,7 +47,6 @@
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
 import kotlinx.coroutines.withContext
 
 /**
@@ -90,10 +90,7 @@
     @Background private val backgroundContext: CoroutineContext,
 ) : ScreenBrightnessRepository {
 
-    private val apiQueue =
-        Channel<SetBrightnessMethod>(
-            capacity = UNLIMITED,
-        )
+    private val apiQueue = Channel<SetBrightnessMethod>(capacity = UNLIMITED)
 
     init {
         applicationScope.launch(context = backgroundContext) {
@@ -132,7 +129,8 @@
                 displayManager.registerDisplayListener(
                     listener,
                     null,
-                    DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS,
+                    /* eventFlags */ 0,
+                    DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS,
                 )
 
                 awaitClose { displayManager.unregisterDisplayListener(listener) }
@@ -190,8 +188,10 @@
 
     private sealed interface SetBrightnessMethod {
         val value: LinearBrightness
+
         @JvmInline
         value class Temporary(override val value: LinearBrightness) : SetBrightnessMethod
+
         @JvmInline
         value class Permanent(override val value: LinearBrightness) : SetBrightnessMethod
     }
@@ -201,7 +201,7 @@
             LOG_BUFFER_BRIGHTNESS_CHANGE_TAG,
             if (permanent) LogLevel.DEBUG else LogLevel.VERBOSE,
             { str1 = value.formatBrightness() },
-            { "Change requested: $str1" }
+            { "Change requested: $str1" },
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index 034cb31..1fa829a 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -264,7 +264,8 @@
                 displayManager.registerDisplayListener(
                     callback,
                     backgroundHandler,
-                    DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED,
+                    /* eventFlags */ 0,
+                    DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED,
                 )
                 awaitClose { displayManager.unregisterDisplayListener(callback) }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt
new file mode 100644
index 0000000..85d2214
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.systemui.keyboard.shortcut.domain.interactor
+
+import android.view.KeyEvent.META_META_ON
+import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperKeys
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
+import javax.inject.Inject
+
+class ShortcutCustomizationInteractor @Inject constructor() {
+    fun getDefaultCustomShortcutModifierKey(): ShortcutKey.Icon.ResIdIcon {
+        return ShortcutKey.Icon.ResIdIcon(ShortcutHelperKeys.keyIcons[META_META_ON]!!)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutInfo.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutInfo.kt
new file mode 100644
index 0000000..e4ccc2c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutInfo.kt
@@ -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.systemui.keyboard.shortcut.shared.model
+
+data class ShortcutInfo(
+    val label: String,
+    val categoryType: ShortcutCategoryType,
+    val subCategoryLabel: String,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogDelegate.kt
new file mode 100644
index 0000000..c98472e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogDelegate.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.systemui.keyboard.shortcut.ui
+
+import android.os.Bundle
+import android.view.Gravity
+import android.view.WindowManager
+import com.android.systemui.statusbar.phone.DialogDelegate
+import com.android.systemui.statusbar.phone.SystemUIDialog
+
+class ShortcutCustomizationDialogDelegate : DialogDelegate<SystemUIDialog> {
+
+    override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
+        super.onCreate(dialog, savedInstanceState)
+        dialog.window?.apply { setGravity(Gravity.CENTER) }
+    }
+
+    override fun getWidth(dialog: SystemUIDialog): Int {
+        return WindowManager.LayoutParams.WRAP_CONTENT
+    }
+
+    override fun getHeight(dialog: SystemUIDialog): Int {
+        return WindowManager.LayoutParams.WRAP_CONTENT
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt
new file mode 100644
index 0000000..02e206e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt
@@ -0,0 +1,84 @@
+/*
+ * 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.systemui.keyboard.shortcut.ui
+
+import android.app.Dialog
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutInfo
+import com.android.systemui.keyboard.shortcut.ui.composable.AssignNewShortcutDialog
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState
+import com.android.systemui.keyboard.shortcut.ui.viewmodel.ShortcutCustomizationViewModel
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
+import com.android.systemui.statusbar.phone.create
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+class ShortcutCustomizationDialogStarter
+@AssistedInject
+constructor(
+    viewModelFactory: ShortcutCustomizationViewModel.Factory,
+    private val dialogFactory: SystemUIDialogFactory,
+) : ExclusiveActivatable() {
+
+    private var dialog: Dialog? = null
+    private val viewModel = viewModelFactory.create()
+
+    override suspend fun onActivated(): Nothing {
+        viewModel.shortcutCustomizationUiState.collect { uiState ->
+            if (
+                uiState is ShortcutCustomizationUiState.AddShortcutDialog &&
+                    !uiState.isDialogShowing
+            ) {
+                dialog = createAddShortcutDialog().also { it.show() }
+                viewModel.onAddShortcutDialogShown()
+            } else if (uiState is ShortcutCustomizationUiState.Inactive) {
+                dialog?.dismiss()
+                dialog = null
+            }
+        }
+    }
+
+    fun onAddShortcutDialogRequested(shortcutBeingCustomized: ShortcutInfo) {
+        viewModel.onAddShortcutDialogRequested(shortcutBeingCustomized)
+    }
+
+    private fun createAddShortcutDialog(): Dialog {
+        return dialogFactory.create(dialogDelegate = ShortcutCustomizationDialogDelegate()) { dialog
+            ->
+            val uiState by viewModel.shortcutCustomizationUiState.collectAsStateWithLifecycle()
+            AssignNewShortcutDialog(
+                uiState = uiState,
+                modifier = Modifier.width(364.dp).wrapContentHeight().padding(vertical = 24.dp),
+                onKeyPress = { viewModel.onKeyPressed(it) },
+                onCancel = { dialog.dismiss() },
+            )
+            dialog.setOnDismissListener { viewModel.onAddShortcutDialogDismissed() }
+        }
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): ShortcutCustomizationDialogStarter
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarter.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarter.kt
index d33ab2a..807c70b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarter.kt
@@ -24,6 +24,7 @@
 import android.provider.Settings
 import androidx.annotation.VisibleForTesting
 import androidx.compose.foundation.layout.width
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -47,15 +48,18 @@
 @Inject
 constructor(
     @Application private val applicationScope: CoroutineScope,
-    private val viewModel: ShortcutHelperViewModel,
+    private val shortcutHelperViewModel: ShortcutHelperViewModel,
+    shortcutCustomizationDialogStarterFactory: ShortcutCustomizationDialogStarter.Factory,
     private val dialogFactory: SystemUIDialogFactory,
     private val activityStarter: ActivityStarter,
 ) : CoreStartable {
 
     @VisibleForTesting var dialog: Dialog? = null
+    private val shortcutCustomizationDialogStarter =
+        shortcutCustomizationDialogStarterFactory.create()
 
     override fun start() {
-        viewModel.shouldShow
+        shortcutHelperViewModel.shouldShow
             .map { shouldShow ->
                 if (shouldShow) {
                     dialog = createShortcutHelperDialog().also { it.show() }
@@ -69,16 +73,21 @@
     private fun createShortcutHelperDialog(): Dialog {
         return dialogFactory.createBottomSheet(
             content = { dialog ->
-                val shortcutsUiState by viewModel.shortcutsUiState.collectAsStateWithLifecycle()
+                val shortcutsUiState by
+                    shortcutHelperViewModel.shortcutsUiState.collectAsStateWithLifecycle()
+                LaunchedEffect(Unit) { shortcutCustomizationDialogStarter.activate() }
                 ShortcutHelper(
                     modifier = Modifier.width(getWidth()),
                     shortcutsUiState = shortcutsUiState,
                     onKeyboardSettingsClicked = { onKeyboardSettingsClicked(dialog) },
-                    onSearchQueryChanged = { viewModel.onSearchQueryChanged(it) },
+                    onSearchQueryChanged = { shortcutHelperViewModel.onSearchQueryChanged(it) },
+                    onCustomizationRequested = {
+                        shortcutCustomizationDialogStarter.onAddShortcutDialogRequested(it)
+                    },
                 )
-                dialog.setOnDismissListener { viewModel.onViewClosed() }
+                dialog.setOnDismissListener { shortcutHelperViewModel.onViewClosed() }
             },
-            maxWidth = ShortcutHelperBottomSheet.LargeScreenWidthLandscape
+            maxWidth = ShortcutHelperBottomSheet.LargeScreenWidthLandscape,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
new file mode 100644
index 0000000..43f0f20
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
@@ -0,0 +1,272 @@
+/*
+ * 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.systemui.keyboard.shortcut.ui.composable
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.collectIsFocusedAsState
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.sizeIn
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.ErrorOutline
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.key.KeyEvent
+import androidx.compose.ui.input.key.onPreviewKeyEvent
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState
+import com.android.systemui.res.R
+
+@Composable
+fun AssignNewShortcutDialog(
+    uiState: ShortcutCustomizationUiState,
+    modifier: Modifier = Modifier,
+    onKeyPress: (KeyEvent) -> Boolean,
+    onCancel: () -> Unit,
+) {
+    if (uiState is ShortcutCustomizationUiState.AddShortcutDialog) {
+        Column(modifier = modifier) {
+            Title(
+                uiState.shortcutLabel,
+                modifier = Modifier.padding(horizontal = 24.dp).width(316.dp),
+            )
+            Description(
+                modifier = Modifier.padding(top = 24.dp, start = 24.dp, end = 24.dp).width(316.dp)
+            )
+            PromptShortcutModifier(
+                modifier =
+                    Modifier.padding(top = 24.dp, start = 116.5.dp, end = 116.5.dp)
+                        .width(131.dp)
+                        .height(48.dp),
+                defaultModifierKey = uiState.defaultCustomShortcutModifierKey,
+            )
+            SelectedKeyCombinationContainer(
+                shouldShowErrorMessage = uiState.shouldShowErrorMessage,
+                onKeyPress = onKeyPress,
+            )
+            KeyCombinationAlreadyInUseErrorMessage(uiState.shouldShowErrorMessage)
+            DialogButtons(onCancel, isValidKeyCombination = uiState.isValidKeyCombination)
+        }
+    }
+}
+
+@Composable
+fun DialogButtons(onCancel: () -> Unit, isValidKeyCombination: Boolean) {
+    Row(
+        modifier =
+            Modifier.padding(top = 24.dp, start = 24.dp, end = 24.dp)
+                .sizeIn(minWidth = 316.dp, minHeight = 48.dp),
+        verticalAlignment = Alignment.Bottom,
+        horizontalArrangement = Arrangement.End,
+    ) {
+        ShortcutHelperButton(
+            shape = RoundedCornerShape(50.dp),
+            onClick = onCancel,
+            color = Color.Transparent,
+            width = 80.dp,
+            contentColor = MaterialTheme.colorScheme.primary,
+            text = stringResource(R.string.shortcut_helper_customize_dialog_cancel_button_label),
+        )
+        Spacer(modifier = Modifier.width(8.dp))
+        ShortcutHelperButton(
+            onClick = {},
+            color = MaterialTheme.colorScheme.primary,
+            width = 116.dp,
+            contentColor = MaterialTheme.colorScheme.onPrimary,
+            text =
+                stringResource(R.string.shortcut_helper_customize_dialog_set_shortcut_button_label),
+            enabled = isValidKeyCombination,
+        )
+    }
+}
+
+@Composable
+fun KeyCombinationAlreadyInUseErrorMessage(shouldShowErrorMessage: Boolean) {
+    if (shouldShowErrorMessage) {
+        Box(modifier = Modifier.padding(horizontal = 16.dp).width(332.dp).height(40.dp)) {
+            Text(
+                text = stringResource(R.string.shortcut_helper_customize_dialog_error_message),
+                style = MaterialTheme.typography.bodyMedium,
+                fontSize = 14.sp,
+                lineHeight = 20.sp,
+                fontWeight = FontWeight.W500,
+                color = MaterialTheme.colorScheme.error,
+                modifier = Modifier.padding(start = 24.dp).width(252.dp),
+            )
+        }
+    }
+}
+
+@Composable
+fun SelectedKeyCombinationContainer(
+    keyCombination: String =
+        stringResource(R.string.shortcut_helper_add_shortcut_dialog_placeholder),
+    shouldShowErrorMessage: Boolean,
+    onKeyPress: (KeyEvent) -> Boolean,
+) {
+    val interactionSource = remember { MutableInteractionSource() }
+    val isFocused by interactionSource.collectIsFocusedAsState()
+    val outlineColor =
+        if (!isFocused) MaterialTheme.colorScheme.outline
+        else if (shouldShowErrorMessage) MaterialTheme.colorScheme.error
+        else MaterialTheme.colorScheme.primary
+
+    ClickableShortcutSurface(
+        onClick = {},
+        color = Color.Transparent,
+        shape = RoundedCornerShape(50.dp),
+        modifier =
+            Modifier.padding(all = 16.dp)
+                .sizeIn(minWidth = 332.dp, minHeight = 56.dp)
+                .border(width = 2.dp, color = outlineColor, shape = RoundedCornerShape(50.dp))
+                .onPreviewKeyEvent { onKeyPress(it) },
+        interactionSource = interactionSource,
+    ) {
+        Row(
+            modifier = Modifier.padding(start = 24.dp, top = 16.dp, end = 16.dp, bottom = 16.dp),
+            verticalAlignment = Alignment.CenterVertically,
+        ) {
+            Text(
+                text = keyCombination,
+                style = MaterialTheme.typography.headlineSmall,
+                fontSize = 16.sp,
+                lineHeight = 24.sp,
+                fontWeight = FontWeight.W500,
+                color = MaterialTheme.colorScheme.onSurfaceVariant,
+                modifier = Modifier.width(252.dp),
+            )
+            Spacer(modifier = Modifier.weight(1f))
+            if (shouldShowErrorMessage) {
+                Icon(
+                    imageVector = Icons.Default.ErrorOutline,
+                    contentDescription = null,
+                    modifier = Modifier.size(20.dp),
+                    tint = MaterialTheme.colorScheme.error,
+                )
+            }
+        }
+    }
+}
+
+@Composable
+private fun Title(title: String, modifier: Modifier = Modifier) {
+    Text(
+        text = title,
+        style = MaterialTheme.typography.headlineSmall,
+        fontSize = 24.sp,
+        modifier = modifier.wrapContentSize(Alignment.Center),
+        color = MaterialTheme.colorScheme.onSurface,
+        lineHeight = 32.sp,
+    )
+}
+
+@Composable
+private fun Description(modifier: Modifier = Modifier) {
+    Text(
+        text = stringResource(id = R.string.shortcut_helper_customize_mode_sub_title),
+        style = MaterialTheme.typography.bodyMedium,
+        fontSize = 14.sp,
+        lineHeight = 20.sp,
+        modifier = modifier.wrapContentSize(Alignment.Center),
+        color = MaterialTheme.colorScheme.onSurfaceVariant,
+    )
+}
+
+@Composable
+private fun PromptShortcutModifier(
+    modifier: Modifier,
+    defaultModifierKey: ShortcutKey.Icon.ResIdIcon,
+) {
+    Row(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(2.dp)) {
+        ActionKeyContainer(defaultModifierKey)
+        PlusIconContainer()
+    }
+}
+
+@Composable
+private fun ActionKeyContainer(defaultModifierKey: ShortcutKey.Icon.ResIdIcon) {
+    Row(
+        modifier =
+            Modifier.height(48.dp)
+                .width(105.dp)
+                .background(
+                    color = MaterialTheme.colorScheme.surface,
+                    shape = RoundedCornerShape(16.dp),
+                )
+                .padding(all = 12.dp),
+        horizontalArrangement = Arrangement.spacedBy(8.dp),
+    ) {
+        ActionKeyIcon(defaultModifierKey)
+        ActionKeyText()
+    }
+}
+
+@Composable
+fun ActionKeyText() {
+    Text(
+        text = "Action",
+        style = MaterialTheme.typography.titleMedium,
+        fontSize = 16.sp,
+        lineHeight = 24.sp,
+        modifier = Modifier.wrapContentSize(Alignment.Center),
+        color = MaterialTheme.colorScheme.onSurface,
+    )
+}
+
+@Composable
+private fun ActionKeyIcon(defaultModifierKey: ShortcutKey.Icon.ResIdIcon) {
+    Icon(
+        painter = painterResource(id = defaultModifierKey.drawableResId),
+        contentDescription = stringResource(R.string.shortcut_helper_content_description_meta_key),
+        modifier = Modifier.size(24.dp).wrapContentSize(Alignment.Center),
+    )
+}
+
+@Composable
+private fun PlusIconContainer() {
+    Icon(
+        tint = MaterialTheme.colorScheme.onSurface,
+        imageVector = Icons.Default.Add,
+        contentDescription =
+            stringResource(id = R.string.shortcut_helper_content_description_plus_icon),
+        modifier = Modifier.padding(vertical = 12.dp).size(24.dp).wrapContentSize(Alignment.Center),
+    )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index abddc70..13934ea 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -110,6 +110,7 @@
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutIcon
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutInfo
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
 import com.android.systemui.keyboard.shortcut.ui.model.IconSource
@@ -124,6 +125,7 @@
     modifier: Modifier = Modifier,
     shortcutsUiState: ShortcutsUiState,
     useSinglePane: @Composable () -> Boolean = { shouldUseSinglePane() },
+    onCustomizationRequested: (ShortcutInfo) -> Unit = {},
 ) {
     when (shortcutsUiState) {
         is ShortcutsUiState.Active -> {
@@ -133,6 +135,7 @@
                 onSearchQueryChanged,
                 modifier,
                 onKeyboardSettingsClicked,
+                onCustomizationRequested,
             )
         }
         else -> {
@@ -148,6 +151,7 @@
     onSearchQueryChanged: (String) -> Unit,
     modifier: Modifier,
     onKeyboardSettingsClicked: () -> Unit,
+    onCustomizationRequested: (ShortcutInfo) -> Unit = {},
 ) {
     var selectedCategoryType by
         remember(shortcutsUiState.defaultSelectedCategory) {
@@ -173,6 +177,7 @@
             onCategorySelected = { selectedCategoryType = it },
             onKeyboardSettingsClicked,
             shortcutsUiState.isShortcutCustomizerFlagEnabled,
+            onCustomizationRequested,
         )
     }
 }
@@ -362,6 +367,7 @@
     onCategorySelected: (ShortcutCategoryType?) -> Unit,
     onKeyboardSettingsClicked: () -> Unit,
     isShortcutCustomizerFlagEnabled: Boolean,
+    onCustomizationRequested: (ShortcutInfo) -> Unit = {},
 ) {
     val selectedCategory = categories.fastFirstOrNull { it.type == selectedCategoryType }
     var isCustomizeModeEntered by remember { mutableStateOf(false) }
@@ -400,6 +406,7 @@
                 Modifier.fillMaxSize().padding(top = 8.dp),
                 selectedCategory,
                 isCustomizing = isCustomizing,
+                onCustomizationRequested = onCustomizationRequested,
             )
         }
     }
@@ -434,6 +441,7 @@
     modifier: Modifier,
     category: ShortcutCategoryUi?,
     isCustomizing: Boolean,
+    onCustomizationRequested: (ShortcutInfo) -> Unit = {},
 ) {
     val listState = rememberLazyListState()
     LaunchedEffect(key1 = category) { if (category != null) listState.animateScrollToItem(0) }
@@ -447,6 +455,15 @@
                 searchQuery = searchQuery,
                 subCategory = subcategory,
                 isCustomizing = isCustomizing,
+                onCustomizationRequested = { label, subCategoryLabel ->
+                    onCustomizationRequested(
+                        ShortcutInfo(
+                            label = label,
+                            subCategoryLabel = subCategoryLabel,
+                            categoryType = category.type,
+                        )
+                    )
+                },
             )
             Spacer(modifier = Modifier.height(8.dp))
         }
@@ -476,6 +493,7 @@
     searchQuery: String,
     subCategory: ShortcutSubCategory,
     isCustomizing: Boolean,
+    onCustomizationRequested: (String, String) -> Unit = { _: String, _: String -> },
 ) {
     Surface(
         modifier = Modifier.fillMaxWidth(),
@@ -497,6 +515,7 @@
                     searchQuery = searchQuery,
                     shortcut = shortcut,
                     isCustomizing = isCustomizing,
+                    onCustomizationRequested = { onCustomizationRequested(it, subCategory.label) },
                 )
             }
         }
@@ -518,6 +537,7 @@
     searchQuery: String,
     shortcut: ShortcutModel,
     isCustomizing: Boolean = false,
+    onCustomizationRequested: (String) -> Unit = {},
 ) {
     val interactionSource = remember { MutableInteractionSource() }
     val isFocused by interactionSource.collectIsFocusedAsState()
@@ -541,7 +561,12 @@
             ShortcutDescriptionText(searchQuery = searchQuery, shortcut = shortcut)
         }
         Spacer(modifier = Modifier.width(24.dp))
-        ShortcutKeyCombinations(modifier = Modifier.weight(1f), shortcut = shortcut, isCustomizing)
+        ShortcutKeyCombinations(
+            modifier = Modifier.weight(1f),
+            shortcut = shortcut,
+            isCustomizing = isCustomizing,
+            onAddShortcutClicked = { onCustomizationRequested(shortcut.label) },
+        )
     }
 }
 
@@ -569,6 +594,7 @@
     modifier: Modifier = Modifier,
     shortcut: ShortcutModel,
     isCustomizing: Boolean = false,
+    onAddShortcutClicked: () -> Unit = {},
 ) {
     FlowRow(
         modifier = modifier,
@@ -590,7 +616,7 @@
                         color = MaterialTheme.colorScheme.outline,
                         shape = CircleShape,
                     ),
-                onClick = {},
+                onClick = { onAddShortcutClicked() },
                 color = Color.Transparent,
                 width = 32.dp,
                 height = 32.dp,
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
index 435968e..e761c73 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
@@ -44,6 +44,7 @@
 import androidx.compose.material3.LocalContentColor
 import androidx.compose.material3.LocalTonalElevationEnabled
 import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
 import androidx.compose.material3.Text
 import androidx.compose.material3.contentColorFor
 import androidx.compose.material3.minimumInteractiveComponentSize
@@ -283,6 +284,108 @@
 }
 
 @Composable
+fun ShortcutHelperButton(
+    modifier: Modifier = Modifier,
+    onClick: () -> Unit,
+    shape: Shape = RoundedCornerShape(360.dp),
+    color: Color,
+    width: Dp,
+    height: Dp = 40.dp,
+    iconSource: IconSource = IconSource(),
+    text: String? = null,
+    contentColor: Color,
+    contentPaddingHorizontal: Dp = 16.dp,
+    contentPaddingVertical: Dp = 10.dp,
+    enabled: Boolean = true,
+) {
+    ShortcutHelperButtonSurface(
+        onClick = onClick,
+        shape = shape,
+        color = color,
+        modifier = modifier,
+        enabled = enabled,
+        width = width,
+        height = height,
+    ) {
+        Row(
+            modifier =
+                Modifier.padding(
+                    horizontal = contentPaddingHorizontal,
+                    vertical = contentPaddingVertical,
+                ),
+            verticalAlignment = Alignment.CenterVertically,
+            horizontalArrangement = Arrangement.Center,
+        ) {
+            if (iconSource.imageVector != null) {
+                Icon(
+                    tint = contentColor,
+                    imageVector = iconSource.imageVector,
+                    contentDescription =
+                        null, // TODO this probably should not be null for accessibility.
+                    modifier = Modifier.size(20.dp).wrapContentSize(Alignment.Center),
+                )
+            }
+
+            if (iconSource.imageVector != null && text != null)
+                Spacer(modifier = Modifier.weight(1f))
+
+            if (text != null) {
+                Text(
+                    text,
+                    color = contentColor,
+                    fontSize = 14.sp,
+                    style = MaterialTheme.typography.labelLarge,
+                    modifier = Modifier.wrapContentSize(Alignment.Center),
+                )
+            }
+        }
+    }
+}
+
+@Composable
+fun ShortcutHelperButtonSurface(
+    onClick: () -> Unit,
+    shape: Shape,
+    color: Color,
+    modifier: Modifier = Modifier,
+    enabled: Boolean,
+    width: Dp,
+    height: Dp,
+    content: @Composable () -> Unit,
+) {
+    if (enabled) {
+        ClickableShortcutSurface(
+            onClick = onClick,
+            shape = shape,
+            color = color,
+            modifier = modifier.semantics { role = Role.Button }.width(width).height(height),
+            interactionsConfig =
+                InteractionsConfig(
+                    hoverOverlayColor = MaterialTheme.colorScheme.onSurface,
+                    hoverOverlayAlpha = 0.11f,
+                    pressedOverlayColor = MaterialTheme.colorScheme.onSurface,
+                    pressedOverlayAlpha = 0.15f,
+                    focusOutlineColor = MaterialTheme.colorScheme.secondary,
+                    focusOutlineStrokeWidth = 3.dp,
+                    focusOutlinePadding = 2.dp,
+                    surfaceCornerRadius = 28.dp,
+                    focusOutlineCornerRadius = 33.dp,
+                ),
+        ) {
+            content()
+        }
+    } else {
+        Surface(
+            shape = shape,
+            color = color.copy(0.38f),
+            modifier = modifier.semantics { role = Role.Button }.width(width).height(height),
+        ) {
+            content()
+        }
+    }
+}
+
+@Composable
 private fun surfaceColorAtElevation(color: Color, elevation: Dp): Color {
     return MaterialTheme.colorScheme.applyTonalElevation(color, elevation)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt
new file mode 100644
index 0000000..e9f2a3b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt
@@ -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.systemui.keyboard.shortcut.ui.model
+
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
+
+sealed interface ShortcutCustomizationUiState {
+    data class AddShortcutDialog(
+        val shortcutLabel: String,
+        val shouldShowErrorMessage: Boolean,
+        val isValidKeyCombination: Boolean,
+        val defaultCustomShortcutModifierKey: ShortcutKey.Icon.ResIdIcon,
+        val isDialogShowing: Boolean,
+    ) : ShortcutCustomizationUiState
+
+    data object Inactive : ShortcutCustomizationUiState
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt
new file mode 100644
index 0000000..b925387
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.systemui.keyboard.shortcut.ui.viewmodel
+
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.input.key.KeyEvent
+import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutCustomizationInteractor
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutInfo
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+
+class ShortcutCustomizationViewModel
+@AssistedInject
+constructor(private val shortcutCustomizationInteractor: ShortcutCustomizationInteractor) {
+    private val _shortcutBeingCustomized = mutableStateOf<ShortcutInfo?>(null)
+
+    private val _shortcutCustomizationUiState =
+        MutableStateFlow<ShortcutCustomizationUiState>(ShortcutCustomizationUiState.Inactive)
+
+    val shortcutCustomizationUiState = _shortcutCustomizationUiState.asStateFlow()
+
+    fun onAddShortcutDialogRequested(shortcutBeingCustomized: ShortcutInfo) {
+        _shortcutCustomizationUiState.value =
+            ShortcutCustomizationUiState.AddShortcutDialog(
+                shortcutLabel = shortcutBeingCustomized.label,
+                shouldShowErrorMessage = false,
+                isValidKeyCombination = false,
+                defaultCustomShortcutModifierKey =
+                    shortcutCustomizationInteractor.getDefaultCustomShortcutModifierKey(),
+                isDialogShowing = false,
+            )
+
+        _shortcutBeingCustomized.value = shortcutBeingCustomized
+    }
+
+    fun onAddShortcutDialogShown() {
+        _shortcutCustomizationUiState.update { uiState ->
+            (uiState as? ShortcutCustomizationUiState.AddShortcutDialog)
+                ?.let { it.copy(isDialogShowing = true) }
+                ?: uiState
+        }
+    }
+
+    fun onAddShortcutDialogDismissed() {
+        _shortcutBeingCustomized.value = null
+        _shortcutCustomizationUiState.value = ShortcutCustomizationUiState.Inactive
+    }
+
+    fun onKeyPressed(keyEvent: KeyEvent): Boolean {
+        // TODO Not yet implemented b/373638584
+        return false
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): ShortcutCustomizationViewModel
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 53177de..f49693a 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -1191,11 +1191,13 @@
     }
 
     private void pilferPointers() {
-        // Capture inputs
-        mInputMonitor.pilferPointers();
-        // Notify FalsingManager that an intentional gesture has occurred.
-        mFalsingManager.isFalseTouch(BACK_GESTURE);
-        mInputEventReceiver.setBatchingEnabled(true);
+        if (mInputMonitor != null) {
+            // Capture inputs
+            mInputMonitor.pilferPointers();
+            // Notify FalsingManager that an intentional gesture has occurred.
+            mFalsingManager.isFalseTouch(BACK_GESTURE);
+            mInputEventReceiver.setBatchingEnabled(true);
+        }
     }
 
     private boolean isButtonPressFromTrackpad(MotionEvent ev) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
index a1071907..2a5ffc6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
@@ -27,6 +27,7 @@
 import android.content.IntentFilter
 import android.content.pm.PackageManager
 import android.content.pm.UserInfo
+import android.content.res.Resources
 import android.graphics.drawable.Drawable
 import android.os.IBinder
 import android.os.PowerExemptionManager
@@ -54,6 +55,7 @@
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_USER_VISIBLE_JOBS
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.Dumpable
+import com.android.systemui.Flags;
 import com.android.systemui.res.R
 import com.android.systemui.animation.DialogCuj
 import com.android.systemui.animation.DialogTransitionAnimator
@@ -137,7 +139,7 @@
 
 @SysUISingleton
 class FgsManagerControllerImpl @Inject constructor(
-    private val context: Context,
+    @Main private val resources: Resources,
     @Main private val mainExecutor: Executor,
     @Background private val backgroundExecutor: Executor,
     private val systemClock: SystemClock,
@@ -223,6 +225,14 @@
 
     private val userVisibleJobObserver = UserVisibleJobObserver()
 
+    private val stoppableApps by lazy { resources
+        .getStringArray(com.android.internal.R.array.stoppable_fgs_system_apps)
+    }
+
+    private val vendorStoppableApps by lazy { resources
+        .getStringArray(com.android.internal.R.array.vendor_stoppable_fgs_system_apps)
+    }
+
     override fun init() {
         synchronized(lock) {
             if (initialized) {
@@ -725,9 +735,22 @@
                     }
                 else -> UIControl.NORMAL
             }
+            // If the app wants to be a good citizen by being stoppable, even if the category it
+            // belongs to is exempted for background restriction, let it be stoppable by user.
+            if (Flags.stoppableFgsSystemApp()) {
+                if (isStoppableApp(packageName)) {
+                    uiControl = UIControl.NORMAL
+                }
+            }
+
             uiControlInitialized = true
         }
 
+        fun isStoppableApp(packageName: String): Boolean {
+            return stoppableApps.contains(packageName) ||
+                vendorStoppableApps.contains(packageName)
+        }
+
         override fun equals(other: Any?): Boolean {
             if (other !is UserPackage) {
                 return false
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index ce9c441..a5eb92b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -24,7 +24,10 @@
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_UP;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
+import static android.window.BackEvent.EDGE_NONE;
 
+import static com.android.window.flags.Flags.predictiveBackSwipeEdgeNoneApi;
+import static com.android.window.flags.Flags.predictiveBackThreeButtonNav;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER;
@@ -41,6 +44,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_WAKEFULNESS_TRANSITION;
 
 import android.annotation.FloatRange;
+import android.annotation.Nullable;
 import android.app.ActivityTaskManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -114,6 +118,7 @@
 import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
 import com.android.systemui.statusbar.policy.CallbackController;
 import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder;
+import com.android.wm.shell.back.BackAnimation;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.sysui.ShellInterface;
 
@@ -174,6 +179,7 @@
     private Region mActiveNavBarRegion;
 
     private final BroadcastDispatcher mBroadcastDispatcher;
+    private final BackAnimation mBackAnimation;
 
     private IOverviewProxy mOverviewProxy;
     private int mConnectionBackoffAttempts;
@@ -287,11 +293,18 @@
         }
 
         @Override
-        public void onBackPressed() {
-            verifyCallerAndClearCallingIdentityPostMain("onBackPressed", () -> {
-                sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
-                sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
-            });
+        public void onBackEvent(@Nullable KeyEvent keyEvent) throws RemoteException {
+            if (predictiveBackThreeButtonNav() && predictiveBackSwipeEdgeNoneApi()
+                    && mBackAnimation != null && keyEvent != null) {
+                mBackAnimation.setTriggerBack(!keyEvent.isCanceled());
+                mBackAnimation.onBackMotion(/* touchX */ 0, /* touchY */ 0, keyEvent.getAction(),
+                        EDGE_NONE);
+            } else {
+                verifyCallerAndClearCallingIdentityPostMain("onBackPressed", () -> {
+                    sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
+                    sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
+                });
+            }
         }
 
         @Override
@@ -657,7 +670,8 @@
             AssistUtils assistUtils,
             DumpManager dumpManager,
             Optional<UnfoldTransitionProgressForwarder> unfoldTransitionProgressForwarder,
-            BroadcastDispatcher broadcastDispatcher
+            BroadcastDispatcher broadcastDispatcher,
+            Optional<BackAnimation> backAnimation
     ) {
         // b/241601880: This component should only be running for primary users or
         // secondaryUsers when visibleBackgroundUsers are supported.
@@ -695,6 +709,7 @@
         mDisplayTracker = displayTracker;
         mUnfoldTransitionProgressForwarder = unfoldTransitionProgressForwarder;
         mBroadcastDispatcher = broadcastDispatcher;
+        mBackAnimation = backAnimation.orElse(null);
 
         if (!KeyguardWmStateRefactor.isEnabled()) {
             mSysuiUnlockAnimationController = sysuiUnlockAnimationController;
diff --git a/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt
index 2ef27a8..60ed2de 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.settings
 
 import android.hardware.display.DisplayManager
-import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
+import android.hardware.display.DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS
 import android.os.Handler
 import android.view.Display
 import androidx.annotation.GuardedBy
@@ -32,7 +32,7 @@
 class DisplayTrackerImpl
 internal constructor(
     val displayManager: DisplayManager,
-    @Background val backgroundHandler: Handler
+    @Background val backgroundHandler: Handler,
 ) : DisplayTracker {
     override val defaultDisplayId: Int = Display.DEFAULT_DISPLAY
     override val allDisplays: Array<Display>
@@ -47,27 +47,21 @@
     val displayChangedListener: DisplayManager.DisplayListener =
         object : DisplayManager.DisplayListener {
             override fun onDisplayAdded(displayId: Int) {
-                traceSection(
-                    "DisplayTrackerImpl.displayChangedDisplayListener#onDisplayAdded",
-                ) {
+                traceSection("DisplayTrackerImpl.displayChangedDisplayListener#onDisplayAdded") {
                     val list = synchronized(displayCallbacks) { displayCallbacks.toList() }
                     onDisplayAdded(displayId, list)
                 }
             }
 
             override fun onDisplayRemoved(displayId: Int) {
-                traceSection(
-                    "DisplayTrackerImpl.displayChangedDisplayListener#onDisplayRemoved",
-                ) {
+                traceSection("DisplayTrackerImpl.displayChangedDisplayListener#onDisplayRemoved") {
                     val list = synchronized(displayCallbacks) { displayCallbacks.toList() }
                     onDisplayRemoved(displayId, list)
                 }
             }
 
             override fun onDisplayChanged(displayId: Int) {
-                traceSection(
-                    "DisplayTrackerImpl.displayChangedDisplayListener#onDisplayChanged",
-                ) {
+                traceSection("DisplayTrackerImpl.displayChangedDisplayListener#onDisplayChanged") {
                     val list = synchronized(displayCallbacks) { displayCallbacks.toList() }
                     onDisplayChanged(displayId, list)
                 }
@@ -83,7 +77,7 @@
 
             override fun onDisplayChanged(displayId: Int) {
                 traceSection(
-                    "DisplayTrackerImpl.displayBrightnessChangedDisplayListener#onDisplayChanged",
+                    "DisplayTrackerImpl.displayBrightnessChangedDisplayListener#onDisplayChanged"
                 ) {
                     val list = synchronized(brightnessCallbacks) { brightnessCallbacks.toList() }
                     onDisplayChanged(displayId, list)
@@ -102,14 +96,15 @@
 
     override fun addBrightnessChangeCallback(
         callback: DisplayTracker.Callback,
-        executor: Executor
+        executor: Executor,
     ) {
         synchronized(brightnessCallbacks) {
             if (brightnessCallbacks.isEmpty()) {
                 displayManager.registerDisplayListener(
                     displayBrightnessChangedListener,
                     backgroundHandler,
-                    EVENT_FLAG_DISPLAY_BRIGHTNESS
+                    /* eventFlags */ 0,
+                    PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS,
                 )
             }
             brightnessCallbacks.add(DisplayTrackerDataItem(WeakReference(callback), executor))
@@ -159,7 +154,7 @@
 
     private inline fun notifySubscribers(
         crossinline action: DisplayTracker.Callback.() -> Unit,
-        list: List<DisplayTrackerDataItem>
+        list: List<DisplayTrackerDataItem>,
     ) {
         list.forEach {
             if (it.callback.get() != null) {
@@ -170,7 +165,7 @@
 
     private data class DisplayTrackerDataItem(
         val callback: WeakReference<DisplayTracker.Callback>,
-        val executor: Executor
+        val executor: Executor,
     ) {
         fun sameOrEmpty(other: DisplayTracker.Callback): Boolean {
             return callback.get()?.equals(other) ?: true
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 3d9eb53..a39ca5d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -103,6 +103,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.FlagsParameterization;
 import android.service.dreams.IDreamManager;
 import android.service.trust.TrustAgentService;
@@ -129,6 +130,7 @@
 import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
 import com.android.keyguard.logging.SimLogger;
 import com.android.settingslib.fuelgauge.BatteryStatus;
+import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
@@ -190,6 +192,7 @@
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4.class)
 @TestableLooper.RunWithLooper
+@EnableFlags(Flags.FLAG_USER_ENCRYPTED_SOURCE)
 public class KeyguardUpdateMonitorTest extends SysuiTestCase {
     private static final String PKG_ALLOWING_FP_LISTEN_ON_OCCLUDING_ACTIVITY =
             "test_app_fp_listen_on_occluding_activity";
@@ -1292,12 +1295,15 @@
 
     @Test
     public void testIsUserUnlocked() {
+        when(mUserManager.isUserUnlocked(mSelectedUserInteractor.getSelectedUserId())).thenReturn(
+                true);
         // mUserManager will report the user as unlocked on @Before
         assertThat(
                 mKeyguardUpdateMonitor.isUserUnlocked(mSelectedUserInteractor.getSelectedUserId()))
                 .isTrue();
         // Invalid user should not be unlocked.
         int randomUser = 99;
+        when(mUserManager.isUserUnlocked(randomUser)).thenReturn(false);
         assertThat(mKeyguardUpdateMonitor.isUserUnlocked(randomUser)).isFalse();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
index 3bfde68..9096808 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
@@ -59,6 +59,7 @@
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
+import com.android.wm.shell.back.BackAnimation
 import com.android.wm.shell.sysui.ShellInterface
 import com.google.common.util.concurrent.MoreExecutors
 import java.util.Optional
@@ -120,6 +121,7 @@
     private lateinit var unfoldTransitionProgressForwarder:
         Optional<UnfoldTransitionProgressForwarder>
     @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+    @Mock private lateinit var backAnimation: Optional<BackAnimation>
 
     @Before
     fun setUp() {
@@ -289,6 +291,7 @@
             dumpManager,
             unfoldTransitionProgressForwarder,
             broadcastDispatcher,
+            backAnimation,
         )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/DisplayTrackerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/DisplayTrackerImplTest.kt
index ae976a0..9fb752a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/DisplayTrackerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/DisplayTrackerImplTest.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.settings
 
 import android.hardware.display.DisplayManager
-import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
+import android.hardware.display.DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS
 import android.hardware.display.DisplayManagerGlobal
 import android.os.Handler
 import android.testing.AndroidTestingRunner
@@ -59,14 +59,14 @@
                 DisplayManagerGlobal.getInstance(),
                 Display.DEFAULT_DISPLAY,
                 DisplayInfo(),
-                DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS
+                DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS,
             )
         mSecondaryDisplay =
             Display(
                 DisplayManagerGlobal.getInstance(),
                 Display.DEFAULT_DISPLAY + 1,
                 DisplayInfo(),
-                DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS
+                DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS,
             )
 
         `when`(displayManager.displays).thenReturn(arrayOf(mDefaultDisplay, mSecondaryDisplay))
@@ -94,7 +94,12 @@
     fun registerBrightnessCallback_registersDisplayListener() {
         tracker.addBrightnessChangeCallback(TestCallback(), executor)
         verify(displayManager)
-            .registerDisplayListener(any(), any(), eq(EVENT_FLAG_DISPLAY_BRIGHTNESS))
+            .registerDisplayListener(
+                any(),
+                any(),
+                eq(0L),
+                eq(PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS),
+            )
     }
 
     @Test
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
index c41493e..8022e6e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
@@ -31,8 +31,11 @@
 import com.android.systemui.keyboard.shortcut.data.source.KeyboardShortcutGroupsSource
 import com.android.systemui.keyboard.shortcut.data.source.MultitaskingShortcutsSource
 import com.android.systemui.keyboard.shortcut.data.source.SystemShortcutsSource
+import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutCustomizationInteractor
 import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperCategoriesInteractor
 import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperStateInteractor
+import com.android.systemui.keyboard.shortcut.ui.ShortcutCustomizationDialogStarter
+import com.android.systemui.keyboard.shortcut.ui.viewmodel.ShortcutCustomizationViewModel
 import com.android.systemui.keyboard.shortcut.ui.viewmodel.ShortcutHelperViewModel
 import com.android.systemui.keyguard.data.repository.fakeCommandQueue
 import com.android.systemui.kosmos.Kosmos
@@ -42,6 +45,7 @@
 import com.android.systemui.model.sysUiState
 import com.android.systemui.settings.displayTracker
 import com.android.systemui.settings.userTracker
+import com.android.systemui.statusbar.phone.systemUIDialogFactory
 
 var Kosmos.shortcutHelperAppCategoriesShortcutsSource: KeyboardShortcutGroupsSource by
     Kosmos.Fixture { AppCategoriesShortcutsSource(windowManager, testDispatcher) }
@@ -121,3 +125,26 @@
             shortcutHelperCategoriesInteractor,
         )
     }
+
+val Kosmos.shortcutCustomizationDialogStarterFactory by
+    Kosmos.Fixture {
+        object : ShortcutCustomizationDialogStarter.Factory {
+            override fun create(): ShortcutCustomizationDialogStarter {
+                return ShortcutCustomizationDialogStarter(
+                    shortcutCustomizationViewModelFactory,
+                    systemUIDialogFactory,
+                )
+            }
+        }
+    }
+
+val Kosmos.shortcutCustomizationInteractor by Kosmos.Fixture { ShortcutCustomizationInteractor() }
+
+val Kosmos.shortcutCustomizationViewModelFactory by
+    Kosmos.Fixture {
+        object : ShortcutCustomizationViewModel.Factory {
+            override fun create(): ShortcutCustomizationViewModel {
+                return ShortcutCustomizationViewModel(shortcutCustomizationInteractor)
+            }
+        }
+    }
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 4731cfb..0c2ce8d 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -376,6 +376,7 @@
         ":ravenwood-empty-res",
         ":framework-platform-compat-config",
         ":services-platform-compat-config",
+        "texts/ravenwood-build.prop",
     ],
     device_first_srcs: [
         ":apex_icu.dat",
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
index e61a054..678a97b 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
@@ -22,7 +22,6 @@
 import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_RESOURCE_APK;
 import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING;
 import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERSION_JAVA_SYSPROP;
-import static com.android.ravenwood.common.RavenwoodCommonUtils.getRavenwoodRuntimePath;
 
 import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
@@ -95,8 +94,6 @@
     private static final String LIBRAVENWOOD_INITIALIZER_NAME = "ravenwood_initializer";
     private static final String RAVENWOOD_NATIVE_SYSPROP_NAME = "ravenwood_sysprop";
     private static final String RAVENWOOD_NATIVE_RUNTIME_NAME = "ravenwood_runtime";
-    private static final String RAVENWOOD_BUILD_PROP =
-            getRavenwoodRuntimePath() + "ravenwood-data/build.prop";
 
     /**
      * When enabled, attempt to dump all thread stacks just before we hit the
@@ -209,7 +206,7 @@
         System.load(RavenwoodCommonUtils.getJniLibraryPath(RAVENWOOD_NATIVE_RUNTIME_NAME));
 
         // Do the basic set up for the android sysprops.
-        RavenwoodSystemProperties.initialize(RAVENWOOD_BUILD_PROP);
+        RavenwoodSystemProperties.initialize();
         setSystemProperties(null);
 
         // Do this after loading RAVENWOOD_NATIVE_RUNTIME_NAME (which backs Os.setenv()),
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
index 9bc45be..3e4619f 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
@@ -16,21 +16,30 @@
 
 package android.platform.test.ravenwood;
 
-import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_SYSPROP;
+import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING;
+import static com.android.ravenwood.common.RavenwoodCommonUtils.getRavenwoodRuntimePath;
 
-import com.android.ravenwood.common.RavenwoodCommonUtils;
+import android.util.Log;
 
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Set;
 
 public class RavenwoodSystemProperties {
     private static final String TAG = "RavenwoodSystemProperties";
 
+    /** We pull in propeties from this file. */
+    private static final String RAVENWOOD_BUILD_PROP = "ravenwood-data/ravenwood-build.prop";
+
+    /** This is the actual build.prop we use to build the device (contents depends on lunch). */
+    private static final String DEVICE_BUILD_PROP = "ravenwood-data/build.prop";
+
+    /** The default values. */
     private static final Map<String, String> sDefaultValues = new HashMap<>();
 
     private static final String[] PARTITIONS = {
@@ -43,52 +52,54 @@
             "vendor_dlkm",
     };
 
-    /**
-     * More info about property file loading: system/core/init/property_service.cpp
-     * In the following logic, the only partition we would need to consider is "system",
-     * since we only read from system-build.prop
-     */
-    static void initialize(String propFile) {
-        // Load all properties from build.prop
+    private static Map<String, String> readProperties(String propFile) {
+        // Use an ordered map just for cleaner dump log.
+        final Map<String, String> ret = new LinkedHashMap<>();
         try {
             Files.readAllLines(Path.of(propFile)).stream()
                     .map(String::trim)
                     .filter(s -> !s.startsWith("#"))
                     .map(s -> s.split("\\s*=\\s*", 2))
                     .filter(a -> a.length == 2)
-                    .forEach(a -> sDefaultValues.put(a[0], a[1]));
+                    .forEach(a -> ret.put(a[0], a[1]));
         } catch (IOException e) {
             throw new RuntimeException(e);
         }
+        return ret;
+    }
 
-        // If ro.product.${name} is not set, derive from ro.product.${partition}.${name}
-        // If ro.product.cpu.abilist* is not set, derive from ro.${partition}.product.cpu.abilist*
-        for (var entry : Set.copyOf(sDefaultValues.entrySet())) {
-            final String key;
-            if (entry.getKey().startsWith("ro.product.system.")) {
-                var name = entry.getKey().substring(18);
-                key = "ro.product." + name;
+    /**
+     * Load default sysprops from {@link #RAVENWOOD_BUILD_PROP}. We also pull in
+     * certain properties from the acutual device's build.prop {@link #DEVICE_BUILD_PROP} too.
+     *
+     * More info about property file loading: system/core/init/property_service.cpp
+     * In the following logic, the only partition we would need to consider is "system",
+     * since we only read from system-build.prop
+     */
+    static void initialize() {
+        var path = getRavenwoodRuntimePath();
+        var ravenwoodProps = readProperties(path + RAVENWOOD_BUILD_PROP);
+        var deviceProps = readProperties(path + DEVICE_BUILD_PROP);
 
-            } else if (entry.getKey().startsWith("ro.system.product.cpu.abilist")) {
-                var name = entry.getKey().substring(22);
-                key = "ro.product.cpu." + name;
+        Log.i(TAG, "Default system properties:");
+        ravenwoodProps.forEach((key, origValue) -> {
+            final String value;
+
+            // If a value starts with "$$$", then this is a reference to the device-side value.
+            if (origValue.startsWith("$$$")) {
+                var deviceKey = origValue.substring(3);
+                var deviceValue = deviceProps.get(deviceKey);
+                if (deviceValue == null) {
+                    throw new RuntimeException("Failed to initialize system properties. Key '"
+                             + deviceKey + "' doesn't exist in the device side build.prop");
+                }
+                value = deviceValue;
             } else {
-                continue;
+                value = origValue;
             }
-            if (!sDefaultValues.containsKey(key)) {
-                sDefaultValues.put(key, entry.getValue());
-            }
-        }
-
-        // Some other custom values
-        sDefaultValues.put("ro.board.first_api_level", "1");
-        sDefaultValues.put("ro.product.first_api_level", "1");
-        sDefaultValues.put("ro.soc.manufacturer", "Android");
-        sDefaultValues.put("ro.soc.model", "Ravenwood");
-        sDefaultValues.put(RAVENWOOD_SYSPROP, "1");
-
-        // Log all values
-        sDefaultValues.forEach((key, value) -> RavenwoodCommonUtils.log(TAG, key + "=" + value));
+            Log.i(TAG, key + "=" + value);
+            sDefaultValues.put(key, value);
+        });
 
         // Copy ro.product.* and ro.build.* to all partitions, just in case
         // We don't want to log these because these are just a lot of duplicate values
@@ -104,6 +115,13 @@
                 }
             }
         }
+        if (RAVENWOOD_VERBOSE_LOGGING) {
+            // Dump all properties for local debugging.
+            Log.v(TAG, "All system properties:");
+            for (var key : sDefaultValues.keySet().stream().sorted().toList()) {
+                Log.v(TAG, "" + key + "=" + sDefaultValues.get(key));
+            }
+        }
     }
 
     private volatile boolean mIsImmutable;
diff --git a/ravenwood/scripts/run-ravenwood-tests.sh b/ravenwood/scripts/run-ravenwood-tests.sh
index 1910100..fe2269a 100755
--- a/ravenwood/scripts/run-ravenwood-tests.sh
+++ b/ravenwood/scripts/run-ravenwood-tests.sh
@@ -33,7 +33,7 @@
 exclude_re=""
 smoke_exclude_re=""
 dry_run=""
-while getopts "sx:f:d" opt; do
+while getopts "sx:f:dt" opt; do
 case "$opt" in
     s)
         # Remove slow tests.
@@ -51,6 +51,9 @@
         # Dry run
         dry_run="echo"
         ;;
+    t)
+        export RAVENWOOD_LOG_OUT=$(tty)
+        ;;
     '?')
         exit 1
         ;;
diff --git a/ravenwood/texts/build.prop-sample-cuttlefish b/ravenwood/texts/build.prop-sample-cuttlefish
new file mode 100644
index 0000000..f78b727
--- /dev/null
+++ b/ravenwood/texts/build.prop-sample-cuttlefish
@@ -0,0 +1,132 @@
+# This is file is generated with `aosp_cf_x86_64_phone-trunk_staging-eng` on 2024-11-06.
+# We have this file here only as a reference. We don't actually use this file anywhere.
+
+####################################
+# from generate_common_build_props
+# These properties identify this partition image.
+####################################
+ro.product.system.brand=Android
+ro.product.system.device=generic
+ro.product.system.manufacturer=Android
+ro.product.system.model=mainline
+ro.product.system.name=mainline
+ro.system.product.cpu.abilist=x86_64,x86,arm64-v8a,armeabi-v7a,armeabi
+ro.system.product.cpu.abilist32=x86,armeabi-v7a,armeabi
+ro.system.product.cpu.abilist64=x86_64,arm64-v8a
+ro.system.build.date=Tue Nov  5 13:25:43 PST 2024
+ro.system.build.date.utc=1730841943
+ro.system.build.fingerprint=generic/aosp_cf_x86_64_phone/vsoc_x86_64:Baklava/MAIN/eng.omakot:eng/test-keys
+ro.system.build.id=MAIN
+ro.system.build.tags=test-keys
+ro.system.build.type=eng
+ro.system.build.version.incremental=eng.omakot
+ro.system.build.version.release=15
+ro.system.build.version.release_or_codename=Baklava
+ro.system.build.version.sdk=35
+####################################
+# from gen_build_prop.py:generate_build_info
+####################################
+# begin build properties
+ro.build.legacy.id=MAIN
+ro.build.display.id=aosp_cf_x86_64_phone-eng Baklava MAIN eng.omakot test-keys
+ro.build.version.incremental=eng.omakot
+ro.build.version.sdk=35
+ro.build.version.preview_sdk=1
+ro.build.version.preview_sdk_fingerprint=2ef06129940d459014cf4dede3950d71
+ro.build.version.codename=Baklava
+ro.build.version.all_codenames=Baklava
+ro.build.version.known_codenames=Base,Base11,Cupcake,Donut,Eclair,Eclair01,EclairMr1,Froyo,Gingerbread,GingerbreadMr1,Honeycomb,HoneycombMr1,HoneycombMr2,IceCreamSandwich,IceCreamSandwichMr1,JellyBean,JellyBeanMr1,JellyBeanMr2,Kitkat,KitkatWatch,Lollipop,LollipopMr1,M,N,NMr1,O,OMr1,P,Q,R,S,Sv2,Tiramisu,UpsideDownCake,VanillaIceCream,Baklava
+ro.build.version.release=15
+ro.build.version.release_or_codename=Baklava
+ro.build.version.release_or_preview_display=Baklava
+ro.build.version.security_patch=2024-08-05
+ro.build.version.base_os=
+ro.build.version.min_supported_target_sdk=28
+ro.build.date=Tue Nov  5 13:25:43 PST 2024
+ro.build.date.utc=1730841943
+ro.build.type=eng
+ro.build.user=omakoto
+ro.build.host=omakoto-ct1.c.googlers.com
+ro.build.tags=test-keys
+ro.build.flavor=aosp_cf_x86_64_phone-eng
+# ro.product.cpu.abi and ro.product.cpu.abi2 are obsolete,
+# use ro.product.cpu.abilist instead.
+ro.product.cpu.abi=x86_64
+ro.product.locale=en-US
+ro.wifi.channels=
+# ro.build.product is obsolete; use ro.product.device
+ro.build.product=vsoc_x86_64
+# Do not try to parse description or thumbprint
+ro.build.description=aosp_cf_x86_64_phone-eng Baklava MAIN eng.omakot test-keys
+# end build properties
+####################################
+# from variable ADDITIONAL_SYSTEM_PROPERTIES
+####################################
+ro.treble.enabled=true
+ro.llndk.api_level=202504
+ro.actionable_compatible_property.enabled=true
+persist.debug.dalvik.vm.core_platform_api_policy=just-warn
+ro.postinstall.fstab.prefix=/system
+ro.kernel.android.checkjni=1
+ro.secure=0
+ro.allow.mock.location=1
+dalvik.vm.lockprof.threshold=500
+ro.debuggable=1
+dalvik.vm.image-dex2oat-filter=extract
+init.svc_debug.no_fatal.zygote=true
+net.bt.name=Android
+ro.force.debuggable=0
+####################################
+# from variable PRODUCT_SYSTEM_PROPERTIES
+####################################
+debug.atrace.tags.enableflags=0
+persist.traced.enable=1
+dalvik.vm.image-dex2oat-Xms=64m
+dalvik.vm.image-dex2oat-Xmx=64m
+dalvik.vm.dex2oat-Xms=64m
+dalvik.vm.dex2oat-Xmx=512m
+dalvik.vm.usejit=true
+dalvik.vm.dexopt.secondary=true
+dalvik.vm.dexopt.thermal-cutoff=2
+dalvik.vm.appimageformat=lz4
+ro.dalvik.vm.native.bridge=0
+pm.dexopt.post-boot=verify
+pm.dexopt.first-boot=verify
+pm.dexopt.boot-after-ota=verify
+pm.dexopt.boot-after-mainline-update=verify
+pm.dexopt.install=speed-profile
+pm.dexopt.install-fast=skip
+pm.dexopt.install-bulk=speed-profile
+pm.dexopt.install-bulk-secondary=verify
+pm.dexopt.install-bulk-downgraded=verify
+pm.dexopt.install-bulk-secondary-downgraded=verify
+pm.dexopt.bg-dexopt=speed-profile
+pm.dexopt.ab-ota=speed-profile
+pm.dexopt.inactive=verify
+pm.dexopt.cmdline=verify
+pm.dexopt.shared=speed
+dalvik.vm.disable-art-service-dexopt=true
+dalvik.vm.disable-odrefresh=true
+dalvik.vm.dex2oat-resolve-startup-strings=true
+dalvik.vm.dex2oat-max-image-block-size=524288
+dalvik.vm.minidebuginfo=true
+dalvik.vm.dex2oat-minidebuginfo=true
+dalvik.vm.madvise.vdexfile.size=104857600
+dalvik.vm.madvise.odexfile.size=104857600
+dalvik.vm.madvise.artfile.size=4294967295
+dalvik.vm.usap_pool_enabled=false
+dalvik.vm.usap_refill_threshold=1
+dalvik.vm.usap_pool_size_max=3
+dalvik.vm.usap_pool_size_min=1
+dalvik.vm.usap_pool_refill_delay_ms=3000
+dalvik.vm.useartservice=true
+dalvik.vm.enable_pr_dexopt=true
+ro.cp_system_other_odex=1
+ro.apex.updatable=true
+ro.launcher.depth.widget=0
+####################################
+# from variable PRODUCT_SYSTEM_DEFAULT_PROPERTIES
+####################################
+# Auto-added by post_process_props.py
+persist.sys.usb.config=adb
+# end of file
diff --git a/ravenwood/texts/ravenwood-build.prop b/ravenwood/texts/ravenwood-build.prop
new file mode 100644
index 0000000..93a18cf
--- /dev/null
+++ b/ravenwood/texts/ravenwood-build.prop
@@ -0,0 +1,44 @@
+# This file contains system properties used on ravenwood.
+
+ro.is_on_ravenwood=1
+
+ro.board.first_api_level=1
+ro.product.first_api_level=1
+ro.soc.manufacturer=Android
+ro.soc.model=Ravenwood
+ro.debuggable=1
+
+# The ones starting with "ro.product" or "ro.bild" will be copied to all "partitions" too.
+# See RavenwoodSystemProperties.
+ro.product.brand=Android
+ro.product.device=Ravenwood
+ro.product.manufacturer=Android
+ro.product.model=Ravenwood
+ro.product.name=Ravenwood
+ro.product.cpu.abilist=x86_64
+ro.product.cpu.abilist32=
+ro.product.cpu.abilist64=x86_64
+
+ro.build.date=Thu Jan 01 00:00:00 GMT 2024
+ro.build.date.utc=1704092400
+ro.build.id=MAIN
+ro.build.tags=dev-keys
+ro.build.type=userdebug
+ro.build.version.incremental=userdebug.ravenwood.20240101
+
+# These are what we used to use on Ravenwood, copied here as a reference.
+#ro.build.version.codename=REL
+#ro.build.version.all_codenames=REL
+#ro.build.version.known_codenames=REL
+#ro.build.version.release=14
+#ro.build.version.release_or_codename=VanillaIceCream
+#ro.build.version.sdk=34
+
+# We pull in the following values from the real build.prop file.
+ro.build.version.codename=$$$ro.build.version.codename
+ro.build.version.all_codenames=$$$ro.build.version.codename
+ro.build.version.known_codenames=$$$ro.build.version.codename
+ro.build.version.release=$$$ro.build.version.release
+ro.build.version.release_or_codename=$$$ro.build.version.release_or_codename
+ro.build.version.release_or_preview_display=$$$ro.build.version.release_or_preview_display
+ro.build.version.sdk=$$$ro.build.version.sdk
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 78bc658..3dcca14 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -22,6 +22,9 @@
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import static com.android.server.health.Utils.copyV1Battery;
 
+import static java.lang.Math.abs;
+
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.app.ActivityManager;
@@ -48,6 +51,7 @@
 import android.os.Handler;
 import android.os.IBatteryPropertiesRegistrar;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.OsProtoEnums;
 import android.os.PowerManager;
 import android.os.RemoteException;
@@ -67,6 +71,7 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.os.SomeArgs;
@@ -84,6 +89,7 @@
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.NoSuchElementException;
+import java.util.Objects;
 import java.util.concurrent.CopyOnWriteArraySet;
 
 /**
@@ -149,19 +155,112 @@
     private HealthInfo mHealthInfo;
     private final HealthInfo mLastHealthInfo = new HealthInfo();
     private boolean mBatteryLevelCritical;
-    private int mLastBatteryStatus;
-    private int mLastBatteryHealth;
-    private boolean mLastBatteryPresent;
-    private int mLastBatteryLevel;
-    private int mLastBatteryVoltage;
-    private int mLastBatteryTemperature;
-    private boolean mLastBatteryLevelCritical;
-    private int mLastMaxChargingCurrent;
-    private int mLastMaxChargingVoltage;
-    private int mLastChargeCounter;
-    private int mLastBatteryCycleCount;
-    private int mLastChargingState;
-    private int mLastBatteryCapacityLevel;
+
+    /**
+     * {@link HealthInfo#batteryStatus} value when {@link Intent#ACTION_BATTERY_CHANGED}
+     * broadcast was sent last.
+     * Note: This value may be used for internal operations and/or to determine whether to trigger
+     * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+     */
+    private int mLastBroadcastBatteryStatus;
+    /**
+     * {@link HealthInfo#batteryHealth} value when {@link Intent#ACTION_BATTERY_CHANGED}
+     * broadcast was sent last.
+     * Note: This value may be used for internal operations and/or to determine whether to trigger
+     * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+     */
+    private int mLastBroadcastBatteryHealth;
+    /**
+     * {@link HealthInfo#batteryPresent} value when {@link Intent#ACTION_BATTERY_CHANGED}
+     * broadcast was sent last.
+     * Note: This value may be used for internal operations and/or to determine whether to trigger
+     * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+     */
+    private boolean mLastBroadcastBatteryPresent;
+    /**
+     * {@link HealthInfo#batteryLevel} value when {@link Intent#ACTION_BATTERY_CHANGED}
+     * broadcast was sent last.
+     * Note: This value may be used for internal operations and/or to determine whether to trigger
+     * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+     */
+    private int mLastBroadcastBatteryLevel;
+    /**
+     * {@link HealthInfo#batteryVoltageMillivolts} value when {@link Intent#ACTION_BATTERY_CHANGED}
+     * broadcast was sent last.
+     * Note: This value may be used for internal operations and/or to determine whether to trigger
+     * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+     */
+    private int mLastBroadcastBatteryVoltage;
+    /**
+     * {@link HealthInfo#batteryTemperatureTenthsCelsius} value when
+     * {@link Intent#ACTION_BATTERY_CHANGED} broadcast was sent last.
+     * Note: This value may be used for internal operations and/or to determine whether to trigger
+     * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+     */
+    private int mLastBroadcastBatteryTemperature;
+    /**
+     * {@link #mBatteryLevelCritical} value when {@link Intent#ACTION_BATTERY_CHANGED}
+     * broadcast was sent last.
+     * Note: These values may be used for internal operations and/or to determine whether to trigger
+     * the broadcast or not.
+     */
+    private boolean mLastBroadcastBatteryLevelCritical;
+    /**
+     * {@link HealthInfo#maxChargingCurrentMicroamps} value when
+     * {@link Intent#ACTION_BATTERY_CHANGED} broadcast was sent last.
+     * Note: This value may be used for internal operations and/or to determine whether to trigger
+     * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+     */
+    private int mLastBroadcastMaxChargingCurrent;
+    /**
+     * {@link HealthInfo#maxChargingVoltageMicrovolts} value when
+     * {@link Intent#ACTION_BATTERY_CHANGED} broadcast was sent last.
+     * Note: This value may be used for internal operations and/or to determine whether to trigger
+     * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+     */
+    private int mLastBroadcastMaxChargingVoltage;
+    /**
+     * {@link HealthInfo#batteryChargeCounterUah} value when {@link Intent#ACTION_BATTERY_CHANGED}
+     * broadcast was sent last.
+     * Note: This value may be used for internal operations and/or to determine whether to trigger
+     * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+     */
+    private int mLastBroadcastChargeCounter;
+    /**
+     * {@link HealthInfo#batteryCycleCount} value when {@link Intent#ACTION_BATTERY_CHANGED}
+     * broadcast was sent last.
+     * Note: This value may be used for internal operations and/or to determine whether to trigger
+     * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+     */
+    private int mLastBroadcastBatteryCycleCount;
+    /**
+     * {@link HealthInfo#chargingState} value when {@link Intent#ACTION_BATTERY_CHANGED}
+     * broadcast was sent last.
+     * Note: This value may be used for internal operations and/or to determine whether to trigger
+     * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+     */
+    private int mLastBroadcastChargingState;
+    /**
+     * {@link HealthInfo#batteryCapacityLevel} value when {@link Intent#ACTION_BATTERY_CHANGED}
+     * broadcast was sent last.
+     * Note: This value may be used for internal operations and/or to determine whether to trigger
+     * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+     */
+    private int mLastBroadcastBatteryCapacityLevel;
+    /**
+     * {@link #mPlugType} value when {@link Intent#ACTION_BATTERY_CHANGED}
+     * broadcast was sent last.
+     * Note: These values may be used for internal operations and/or to determine whether to trigger
+     * the broadcast or not.
+     */
+    private int mLastBroadcastPlugType = -1; // Extra state so we can detect first run
+    /**
+     * {@link #mInvalidCharger} value when {@link Intent#ACTION_BATTERY_CHANGED}
+     * broadcast was sent last.
+     * Note: These values may be used for internal operations and/or to determine whether to trigger
+     * the broadcast or not.
+     */
+    private int mLastBroadcastInvalidCharger;
     /**
      * The last seen charging policy. This requires the
      * {@link android.Manifest.permission#BATTERY_STATS} permission and should therefore not be
@@ -172,7 +271,6 @@
     private int mSequence = 1;
 
     private int mInvalidCharger;
-    private int mLastInvalidCharger;
 
     private int mLowBatteryWarningLevel;
     private int mLastLowBatteryWarningLevel;
@@ -184,7 +282,6 @@
     private static String sSystemUiPackage;
 
     private int mPlugType;
-    private int mLastPlugType = -1; // Extra state so we can detect first run
 
     private boolean mBatteryLevelLow;
 
@@ -197,6 +294,16 @@
     private boolean mUpdatesStopped;
     private boolean mBatteryInputSuspended;
 
+    /**
+     * Time when the voltage was updated last by HAL and we sent the
+     * {@link Intent#ACTION_BATTERY_CHANGED} broadcast.
+     * Note: This value is used to rate limit the {@link Intent#ACTION_BATTERY_CHANGED} broadcast
+     * so it is possible that voltage was updated but we did not send the broadcast so in that
+     * case we do not update the time.
+     */
+    @VisibleForTesting
+    public long mLastBroadcastVoltageUpdateTime;
+
     private Led mLed;
 
     private boolean mSentLowBatteryBroadcast = false;
@@ -211,7 +318,8 @@
     private final CopyOnWriteArraySet<BatteryManagerInternal.ChargingPolicyChangeListener>
             mChargingPolicyChangeListeners = new CopyOnWriteArraySet<>();
 
-    private static final Bundle BATTERY_CHANGED_OPTIONS = BroadcastOptions.makeBasic()
+    @VisibleForTesting
+    public static final Bundle BATTERY_CHANGED_OPTIONS = BroadcastOptions.makeBasic()
             .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
             .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
             .toBundle();
@@ -234,6 +342,25 @@
     private static final int MSG_BROADCAST_POWER_CONNECTION_CHANGED = 2;
     private static final int MSG_BROADCAST_BATTERY_LOW_OKAY = 3;
 
+    /**
+     * This value is used to rate limit the {@link Intent#ACTION_BATTERY_CHANGED} broadcast. We
+     * only send the broadcast and update the temperature value when the temp change is greater or
+     * equals to 1 degree celsius.
+     */
+    private static final int ABSOLUTE_DECI_CELSIUS_DIFF_FOR_TEMP_UPDATE = 10;
+    /**
+     * This value is used to rate limit the {@link Intent#ACTION_BATTERY_CHANGED} broadcast. We
+     * only send the broadcast if the last voltage was updated at least 20s seconds back and has a
+     * fluctuation of at least 1%.
+     */
+    private static final int TIME_DIFF_FOR_VOLTAGE_UPDATE_MS = 20000;
+    /**
+     * The value is used to rate limit the {@link Intent#ACTION_BATTERY_CHANGED} broadcast. We
+     * only send the broadcast if the last voltage was updated at least 20s seconds back and has a
+     * fluctuation of at least 1%.
+     */
+    private static final float BASE_POINT_DIFF_FOR_VOLTAGE_UPDATE = 0.01f;
+
     private final Handler.Callback mLocalCallback = msg -> {
         switch (msg.what) {
             case MSG_BROADCAST_BATTERY_CHANGED: {
@@ -283,10 +410,19 @@
     };
 
     public BatteryService(Context context) {
+        this(context, Objects.requireNonNull(Looper.myLooper(),
+                "BatteryService uses handler!! Can't create handler inside thread that has not "
+                        + "called Looper.prepare()"));
+    }
+
+    @VisibleForTesting
+    public BatteryService(Context context, @NonNull Looper looper) {
         super(context);
 
+        Objects.requireNonNull(looper);
+
         mContext = context;
-        mHandler = new Handler(mLocalCallback, true /*async*/);
+        mHandler = new Handler(looper, mLocalCallback, true /*async*/);
         mLed = new Led(context, getLocalService(LightsManager.class));
         mBatteryStats = BatteryStatsService.getService();
         mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
@@ -436,7 +572,7 @@
 
     private boolean shouldSendBatteryLowLocked() {
         final boolean plugged = mPlugType != BATTERY_PLUGGED_NONE;
-        final boolean oldPlugged = mLastPlugType != BATTERY_PLUGGED_NONE;
+        final boolean oldPlugged = mLastBroadcastPlugType != BATTERY_PLUGGED_NONE;
 
         /* The ACTION_BATTERY_LOW broadcast is sent in these situations:
          * - is just un-plugged (previously was plugged) and battery level is
@@ -447,7 +583,7 @@
         return !plugged
                 && mHealthInfo.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
                 && mHealthInfo.batteryLevel <= mLowBatteryWarningLevel
-                && (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel
+                && (oldPlugged || mLastBroadcastBatteryLevel > mLowBatteryWarningLevel
                     || mHealthInfo.batteryLevel > mLastLowBatteryWarningLevel);
     }
 
@@ -515,7 +651,13 @@
         }
     }
 
-    private void update(android.hardware.health.HealthInfo info) {
+    /**
+     * Updates the healthInfo and triggers the broadcast.
+     *
+     * @param info the new health info
+     */
+    @VisibleForTesting
+    public void update(android.hardware.health.HealthInfo info) {
         traceBegin("HealthInfoUpdate");
 
         Trace.traceCounter(
@@ -556,8 +698,8 @@
         long dischargeDuration = 0;
 
         mBatteryLevelCritical =
-            mHealthInfo.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
-            && mHealthInfo.batteryLevel <= mCriticalBatteryLevel;
+                mHealthInfo.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
+                        && mHealthInfo.batteryLevel <= mCriticalBatteryLevel;
         mPlugType = plugType(mHealthInfo);
 
         if (DEBUG) {
@@ -591,24 +733,28 @@
             mHandler.post(this::notifyChargingPolicyChanged);
         }
 
-        if (force
-                || (mHealthInfo.batteryStatus != mLastBatteryStatus
-                        || mHealthInfo.batteryHealth != mLastBatteryHealth
-                        || mHealthInfo.batteryPresent != mLastBatteryPresent
-                        || mHealthInfo.batteryLevel != mLastBatteryLevel
-                        || mPlugType != mLastPlugType
-                        || mHealthInfo.batteryVoltageMillivolts != mLastBatteryVoltage
-                        || mHealthInfo.batteryTemperatureTenthsCelsius != mLastBatteryTemperature
-                        || mHealthInfo.maxChargingCurrentMicroamps != mLastMaxChargingCurrent
-                        || mHealthInfo.maxChargingVoltageMicrovolts != mLastMaxChargingVoltage
-                        || mHealthInfo.batteryChargeCounterUah != mLastChargeCounter
-                        || mInvalidCharger != mLastInvalidCharger
-                        || mHealthInfo.batteryCycleCount != mLastBatteryCycleCount
-                        || mHealthInfo.chargingState != mLastChargingState
-                        || mHealthInfo.batteryCapacityLevel != mLastBatteryCapacityLevel)) {
+        final boolean includeChargeCounter =
+                !com.android.server.flags.Flags.rateLimitBatteryChangedBroadcast()
+                        && mHealthInfo.batteryChargeCounterUah != mLastBroadcastChargeCounter;
 
-            if (mPlugType != mLastPlugType) {
-                if (mLastPlugType == BATTERY_PLUGGED_NONE) {
+        if (force
+                || (mHealthInfo.batteryStatus != mLastBroadcastBatteryStatus
+                || mHealthInfo.batteryHealth != mLastBroadcastBatteryHealth
+                || mHealthInfo.batteryPresent != mLastBroadcastBatteryPresent
+                || mHealthInfo.batteryLevel != mLastBroadcastBatteryLevel
+                || mPlugType != mLastBroadcastPlugType
+                || mHealthInfo.batteryVoltageMillivolts != mLastBroadcastBatteryVoltage
+                || mHealthInfo.batteryTemperatureTenthsCelsius != mLastBroadcastBatteryTemperature
+                || mHealthInfo.maxChargingCurrentMicroamps != mLastBroadcastMaxChargingCurrent
+                || mHealthInfo.maxChargingVoltageMicrovolts != mLastBroadcastMaxChargingVoltage
+                || includeChargeCounter
+                || mInvalidCharger != mLastBroadcastInvalidCharger
+                || mHealthInfo.batteryCycleCount != mLastBroadcastBatteryCycleCount
+                || mHealthInfo.chargingState != mLastBroadcastChargingState
+                || mHealthInfo.batteryCapacityLevel != mLastBroadcastBatteryCapacityLevel)) {
+
+            if (mPlugType != mLastBroadcastPlugType) {
+                if (mLastBroadcastPlugType == BATTERY_PLUGGED_NONE) {
                     // discharging -> charging
                     mChargeStartLevel = mHealthInfo.batteryLevel;
                     mChargeStartTime = SystemClock.elapsedRealtime();
@@ -622,7 +768,8 @@
 
                     // There's no value in this data unless we've discharged at least once and the
                     // battery level has changed; so don't log until it does.
-                    if (mDischargeStartTime != 0 && mDischargeStartLevel != mHealthInfo.batteryLevel) {
+                    if (mDischargeStartTime != 0
+                            && mDischargeStartLevel != mHealthInfo.batteryLevel) {
                         dischargeDuration = SystemClock.elapsedRealtime() - mDischargeStartTime;
                         logOutlier = true;
                         EventLog.writeEvent(EventLogTags.BATTERY_DISCHARGE, dischargeDuration,
@@ -639,7 +786,7 @@
                     if (mChargeStartTime != 0 && chargeDuration != 0) {
                         final LogMaker builder = new LogMaker(MetricsEvent.ACTION_CHARGE);
                         builder.setType(MetricsEvent.TYPE_DISMISS);
-                        builder.addTaggedData(MetricsEvent.FIELD_PLUG_TYPE, mLastPlugType);
+                        builder.addTaggedData(MetricsEvent.FIELD_PLUG_TYPE, mLastBroadcastPlugType);
                         builder.addTaggedData(MetricsEvent.FIELD_CHARGING_DURATION_MILLIS,
                                 chargeDuration);
                         builder.addTaggedData(MetricsEvent.FIELD_BATTERY_LEVEL_START,
@@ -651,19 +798,20 @@
                     mChargeStartTime = 0;
                 }
             }
-            if (mHealthInfo.batteryStatus != mLastBatteryStatus ||
-                    mHealthInfo.batteryHealth != mLastBatteryHealth ||
-                    mHealthInfo.batteryPresent != mLastBatteryPresent ||
-                    mPlugType != mLastPlugType) {
+            if (mHealthInfo.batteryStatus != mLastBroadcastBatteryStatus
+                    || mHealthInfo.batteryHealth != mLastBroadcastBatteryHealth
+                    || mHealthInfo.batteryPresent != mLastBroadcastBatteryPresent
+                    || mPlugType != mLastBroadcastPlugType) {
                 EventLog.writeEvent(EventLogTags.BATTERY_STATUS,
-                        mHealthInfo.batteryStatus, mHealthInfo.batteryHealth, mHealthInfo.batteryPresent ? 1 : 0,
+                        mHealthInfo.batteryStatus, mHealthInfo.batteryHealth,
+                        mHealthInfo.batteryPresent ? 1 : 0,
                         mPlugType, mHealthInfo.batteryTechnology);
                 SystemProperties.set(
                         "debug.tracing.battery_status",
                         Integer.toString(mHealthInfo.batteryStatus));
                 SystemProperties.set("debug.tracing.plug_type", Integer.toString(mPlugType));
             }
-            if (mHealthInfo.batteryLevel != mLastBatteryLevel) {
+            if (mHealthInfo.batteryLevel != mLastBroadcastBatteryLevel) {
                 // Don't do this just from voltage or temperature changes, that is
                 // too noisy.
                 EventLog.writeEvent(
@@ -672,8 +820,8 @@
                         mHealthInfo.batteryVoltageMillivolts,
                         mHealthInfo.batteryTemperatureTenthsCelsius);
             }
-            if (mBatteryLevelCritical && !mLastBatteryLevelCritical &&
-                    mPlugType == BATTERY_PLUGGED_NONE) {
+            if (mBatteryLevelCritical && !mLastBroadcastBatteryLevelCritical
+                    && mPlugType == BATTERY_PLUGGED_NONE) {
                 // We want to make sure we log discharge cycle outliers
                 // if the battery is about to die.
                 dischargeDuration = SystemClock.elapsedRealtime() - mDischargeStartTime;
@@ -684,7 +832,7 @@
                 // Should we now switch in to low battery mode?
                 if (mPlugType == BATTERY_PLUGGED_NONE
                         && mHealthInfo.batteryStatus !=
-                           BatteryManager.BATTERY_STATUS_UNKNOWN
+                        BatteryManager.BATTERY_STATUS_UNKNOWN
                         && mHealthInfo.batteryLevel <= mLowBatteryWarningLevel) {
                     mBatteryLevelLow = true;
                 }
@@ -692,7 +840,7 @@
                 // Should we now switch out of low battery mode?
                 if (mPlugType != BATTERY_PLUGGED_NONE) {
                     mBatteryLevelLow = false;
-                } else if (mHealthInfo.batteryLevel >= mLowBatteryCloseWarningLevel)  {
+                } else if (mHealthInfo.batteryLevel >= mLowBatteryCloseWarningLevel) {
                     mBatteryLevelLow = false;
                 } else if (force && mHealthInfo.batteryLevel >= mLowBatteryWarningLevel) {
                     // If being forced, the previous state doesn't matter, we will just
@@ -706,7 +854,7 @@
             // Separate broadcast is sent for power connected / not connected
             // since the standard intent will not wake any applications and some
             // applications may want to have smart behavior based on this.
-            if (mPlugType != 0 && mLastPlugType == 0) {
+            if (mPlugType != 0 && mLastBroadcastPlugType == 0) {
                 final Intent statusIntent = new Intent(Intent.ACTION_POWER_CONNECTED);
                 statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
                 statusIntent.putExtra(BatteryManager.EXTRA_SEQUENCE, mSequence);
@@ -726,8 +874,7 @@
                         }
                     });
                 }
-            }
-            else if (mPlugType == 0 && mLastPlugType != 0) {
+            } else if (mPlugType == 0 && mLastBroadcastPlugType != 0) {
                 final Intent statusIntent = new Intent(Intent.ACTION_POWER_DISCONNECTED);
                 statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
                 statusIntent.putExtra(BatteryManager.EXTRA_SEQUENCE, mSequence);
@@ -797,8 +944,14 @@
             // We are doing this after sending the above broadcasts, so anything processing
             // them will get the new sequence number at that point.  (See for example how testing
             // of JobScheduler's BatteryController works.)
-            sendBatteryChangedIntentLocked(force);
-            if (mLastBatteryLevel != mHealthInfo.batteryLevel || mLastPlugType != mPlugType) {
+
+            boolean rateLimitBatteryChangedBroadcast = rateLimitBatteryChangedBroadcast(force);
+
+            if (!rateLimitBatteryChangedBroadcast) {
+                sendBatteryChangedIntentLocked(force);
+            }
+            if (mLastBroadcastBatteryLevel != mHealthInfo.batteryLevel
+                    || mLastBroadcastPlugType != mPlugType) {
                 sendBatteryLevelChangedIntentLocked();
             }
 
@@ -811,21 +964,24 @@
                 logOutlierLocked(dischargeDuration);
             }
 
-            mLastBatteryStatus = mHealthInfo.batteryStatus;
-            mLastBatteryHealth = mHealthInfo.batteryHealth;
-            mLastBatteryPresent = mHealthInfo.batteryPresent;
-            mLastBatteryLevel = mHealthInfo.batteryLevel;
-            mLastPlugType = mPlugType;
-            mLastBatteryVoltage = mHealthInfo.batteryVoltageMillivolts;
-            mLastBatteryTemperature = mHealthInfo.batteryTemperatureTenthsCelsius;
-            mLastMaxChargingCurrent = mHealthInfo.maxChargingCurrentMicroamps;
-            mLastMaxChargingVoltage = mHealthInfo.maxChargingVoltageMicrovolts;
-            mLastChargeCounter = mHealthInfo.batteryChargeCounterUah;
-            mLastBatteryLevelCritical = mBatteryLevelCritical;
-            mLastInvalidCharger = mInvalidCharger;
-            mLastBatteryCycleCount = mHealthInfo.batteryCycleCount;
-            mLastChargingState = mHealthInfo.chargingState;
-            mLastBatteryCapacityLevel = mHealthInfo.batteryCapacityLevel;
+            // Only update the values when we send the broadcast
+            if (!rateLimitBatteryChangedBroadcast) {
+                mLastBroadcastBatteryStatus = mHealthInfo.batteryStatus;
+                mLastBroadcastBatteryHealth = mHealthInfo.batteryHealth;
+                mLastBroadcastBatteryPresent = mHealthInfo.batteryPresent;
+                mLastBroadcastBatteryLevel = mHealthInfo.batteryLevel;
+                mLastBroadcastPlugType = mPlugType;
+                mLastBroadcastBatteryVoltage = mHealthInfo.batteryVoltageMillivolts;
+                mLastBroadcastBatteryTemperature = mHealthInfo.batteryTemperatureTenthsCelsius;
+                mLastBroadcastMaxChargingCurrent = mHealthInfo.maxChargingCurrentMicroamps;
+                mLastBroadcastMaxChargingVoltage = mHealthInfo.maxChargingVoltageMicrovolts;
+                mLastBroadcastChargeCounter = mHealthInfo.batteryChargeCounterUah;
+                mLastBroadcastBatteryLevelCritical = mBatteryLevelCritical;
+                mLastBroadcastInvalidCharger = mInvalidCharger;
+                mLastBroadcastBatteryCycleCount = mHealthInfo.batteryCycleCount;
+                mLastBroadcastChargingState = mHealthInfo.chargingState;
+                mLastBroadcastBatteryCapacityLevel = mHealthInfo.batteryCapacityLevel;
+            }
         }
     }
 
@@ -1089,6 +1245,74 @@
         }
     }
 
+    /**
+     * Rate limit's the broadcast based on the changes in temp, voltage and chargeCounter.
+     */
+    private boolean rateLimitBatteryChangedBroadcast(boolean forceUpdate) {
+        if (!com.android.server.flags.Flags.rateLimitBatteryChangedBroadcast()) {
+            return false;
+        }
+        if (mLastBroadcastBatteryVoltage == 0 || mLastBroadcastBatteryTemperature == 0) {
+            mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime();
+            return false;
+        }
+
+        final boolean voltageUpdated =
+                mLastBroadcastBatteryVoltage != mHealthInfo.batteryVoltageMillivolts;
+        final boolean temperatureUpdated =
+                mLastBroadcastBatteryTemperature != mHealthInfo.batteryTemperatureTenthsCelsius;
+        final boolean otherStatesUpdated = forceUpdate
+                || mHealthInfo.batteryStatus != mLastBroadcastBatteryStatus
+                || mHealthInfo.batteryHealth != mLastBroadcastBatteryHealth
+                || mHealthInfo.batteryPresent != mLastBroadcastBatteryPresent
+                || mHealthInfo.batteryLevel != mLastBroadcastBatteryLevel
+                || mPlugType != mLastBroadcastPlugType
+                || mHealthInfo.maxChargingCurrentMicroamps != mLastBroadcastMaxChargingCurrent
+                || mHealthInfo.maxChargingVoltageMicrovolts != mLastBroadcastMaxChargingVoltage
+                || mInvalidCharger != mLastBroadcastInvalidCharger
+                || mHealthInfo.batteryCycleCount != mLastBroadcastBatteryCycleCount
+                || mHealthInfo.chargingState != mLastBroadcastChargingState
+                || mHealthInfo.batteryCapacityLevel != mLastBroadcastBatteryCapacityLevel;
+
+        // We only rate limit based on changes in the temp, voltage.
+        if (otherStatesUpdated) {
+
+            if (voltageUpdated) {
+                mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime();
+            }
+            return false;
+        }
+
+        final float basePointDiff =
+                (float) (mLastBroadcastBatteryVoltage - mHealthInfo.batteryVoltageMillivolts)
+                        / mLastBroadcastBatteryVoltage;
+
+        // We only send the broadcast if voltage change is greater than 1% and last voltage
+        // update was sent at least 20 seconds back.
+        if (voltageUpdated
+                && abs(basePointDiff) >= BASE_POINT_DIFF_FOR_VOLTAGE_UPDATE
+                && SystemClock.elapsedRealtime() - mLastBroadcastVoltageUpdateTime
+                        >= TIME_DIFF_FOR_VOLTAGE_UPDATE_MS) {
+            mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime();
+
+            return false;
+        }
+
+        // Only send the broadcast if the temperature update is greater than 1 degree celsius.
+        if (temperatureUpdated
+                && abs(
+                mLastBroadcastBatteryTemperature - mHealthInfo.batteryTemperatureTenthsCelsius)
+                        >= ABSOLUTE_DECI_CELSIUS_DIFF_FOR_TEMP_UPDATE) {
+
+            if (voltageUpdated) {
+                mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime();
+            }
+            return false;
+        }
+
+        return true;
+    }
+
     class Shell extends ShellCommand {
         @Override
         public int onCommand(String cmd) {
@@ -1399,6 +1623,10 @@
                 pw.println("  level: " + mHealthInfo.batteryLevel);
                 pw.println("  scale: " + BATTERY_SCALE);
                 pw.println("  voltage: " + mHealthInfo.batteryVoltageMillivolts);
+                pw.println(" Time when the latest updated value of the voltage was sent via "
+                        + "battery changed broadcast: " + mLastBroadcastVoltageUpdateTime);
+                pw.println(" The last voltage value sent via the battery changed broadcast: "
+                        + mLastBroadcastBatteryVoltage);
                 pw.println("  temperature: " + mHealthInfo.batteryTemperatureTenthsCelsius);
                 pw.println("  technology: " + mHealthInfo.batteryTechnology);
                 pw.println("  Charging state: " + mHealthInfo.chargingState);
@@ -1457,6 +1685,11 @@
         Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
     }
 
+    @VisibleForTesting
+    public Handler getHandlerForTest() {
+        return mHandler;
+    }
+
     @SuppressLint("AndroidFrameworkRequiresPermission")
     private static void sendBroadcastToAllUsers(Context context, Intent intent,
             Bundle options) {
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 08632fe..c067662 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -514,27 +514,11 @@
         mLogger = new OomAdjusterDebugLogger(this, mService.mConstants);
 
         mProcessGroupHandler = new Handler(adjusterThread.getLooper(), msg -> {
-            final int pid = msg.arg1;
-            final int group = msg.arg2;
-            if (pid == ActivityManagerService.MY_PID) {
-                // Skip setting the process group for system_server, keep it as default.
-                return true;
-            }
-            final boolean traceEnabled = Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-            if (traceEnabled) {
-                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "setProcessGroup "
-                        + msg.obj + " to " + group);
-            }
-            try {
-                android.os.Process.setProcessGroup(pid, group);
-            } catch (Exception e) {
-                if (DEBUG_ALL) {
-                    Slog.w(TAG, "Failed setting process group of " + pid + " to " + group, e);
-                }
-            } finally {
-                if (traceEnabled) {
-                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-                }
+            final int group = msg.what;
+            final ProcessRecord app = (ProcessRecord) msg.obj;
+            setProcessGroup(app.getPid(), group, app.processName);
+            if (Flags.phantomProcessesFix()) {
+                mService.mPhantomProcessList.setProcessGroupForPhantomProcessOfApp(app, group);
             }
             return true;
         });
@@ -545,8 +529,31 @@
     }
 
     void setProcessGroup(int pid, int group, String processName) {
+        if (pid == ActivityManagerService.MY_PID) {
+            // Skip setting the process group for system_server, keep it as default.
+            return;
+        }
+        final boolean traceEnabled = Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+        if (traceEnabled) {
+            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "setProcessGroup "
+                    + processName + " to " + group);
+        }
+        try {
+            android.os.Process.setProcessGroup(pid, group);
+        } catch (Exception e) {
+            if (DEBUG_ALL) {
+                Slog.w(TAG, "Failed setting process group of " + pid + " to " + group, e);
+            }
+        } finally {
+            if (traceEnabled) {
+                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+            }
+        }
+    }
+
+    void setAppAndChildProcessGroup(ProcessRecord app, int group) {
         mProcessGroupHandler.sendMessage(mProcessGroupHandler.obtainMessage(
-                0 /* unused */, pid, group, processName));
+                group, app));
     }
 
     void initSettings() {
@@ -3503,8 +3510,7 @@
                     processGroup = THREAD_GROUP_DEFAULT;
                     break;
             }
-            setProcessGroup(app.getPid(), processGroup, app.processName);
-            mService.mPhantomProcessList.setProcessGroupForPhantomProcessOfApp(app, processGroup);
+            setAppAndChildProcessGroup(app, processGroup);
             try {
                 final int renderThreadTid = app.getRenderThreadTid();
                 if (curSchedGroup == SCHED_GROUP_TOP_APP) {
diff --git a/services/core/java/com/android/server/am/PhantomProcessList.java b/services/core/java/com/android/server/am/PhantomProcessList.java
index bfdced7..123780f 100644
--- a/services/core/java/com/android/server/am/PhantomProcessList.java
+++ b/services/core/java/com/android/server/am/PhantomProcessList.java
@@ -548,6 +548,7 @@
      */
     void setProcessGroupForPhantomProcessOfApp(final ProcessRecord app, final int group) {
         synchronized (mLock) {
+            lookForPhantomProcessesLocked(app);
             final SparseArray<PhantomProcessRecord> array = getPhantomProcessOfAppLocked(app);
             if (array == null) {
                 return;
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 7b4d6c7..5d5b35b 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -250,4 +250,14 @@
     is_fixed_read_only: true
     description: "Add +X to the prev scores according to their positions in the process LRU list"
     bug: "359912586"
-}
\ No newline at end of file
+}
+
+flag {
+    name: "phantom_processes_fix"
+    namespace: "backstage_power"
+    description: "Make sure setProcessGroupForPhantomProcessOfApp deals with phantom processes properly"
+    bug: "375058190"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index f5a75c7d..0e77040 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -25,7 +25,7 @@
 import static android.Manifest.permission.RESTRICT_DISPLAY_MODES;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
-import static android.hardware.display.DisplayManager.EventFlag;
+import static android.hardware.display.DisplayManagerGlobal.InternalEventFlag;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
@@ -1390,16 +1390,16 @@
     }
 
     private void registerCallbackInternal(IDisplayManagerCallback callback, int callingPid,
-            int callingUid, @EventFlag long eventFlagsMask) {
+            int callingUid, @InternalEventFlag long internalEventFlagsMask) {
         synchronized (mSyncRoot) {
             CallbackRecord record = mCallbacks.get(callingPid);
 
             if (record != null) {
-                record.updateEventFlagsMask(eventFlagsMask);
+                record.updateEventFlagsMask(internalEventFlagsMask);
                 return;
             }
 
-            record = new CallbackRecord(callingPid, callingUid, callback, eventFlagsMask);
+            record = new CallbackRecord(callingPid, callingUid, callback, internalEventFlagsMask);
             try {
                 IBinder binder = callback.asBinder();
                 binder.linkToDeath(record, 0);
@@ -4009,7 +4009,7 @@
         public final int mPid;
         public final int mUid;
         private final IDisplayManagerCallback mCallback;
-        private @DisplayManager.EventFlag AtomicLong mEventFlagsMask;
+        private @InternalEventFlag AtomicLong mInternalEventFlagsMask;
         private final String mPackageName;
 
         public boolean mWifiDisplayScanRequested;
@@ -4030,11 +4030,11 @@
         private boolean mFrozen;
 
         CallbackRecord(int pid, int uid, @NonNull IDisplayManagerCallback callback,
-                @EventFlag long eventFlagsMask) {
+                @InternalEventFlag long internalEventFlagsMask) {
             mPid = pid;
             mUid = uid;
             mCallback = callback;
-            mEventFlagsMask = new AtomicLong(eventFlagsMask);
+            mInternalEventFlagsMask = new AtomicLong(internalEventFlagsMask);
             mCached = false;
             mFrozen = false;
 
@@ -4056,8 +4056,8 @@
             mPackageName = packageNames == null ? null : packageNames[0];
         }
 
-        public void updateEventFlagsMask(@EventFlag long eventFlag) {
-            mEventFlagsMask.set(eventFlag);
+        public void updateEventFlagsMask(@InternalEventFlag long internalEventFlag) {
+            mInternalEventFlagsMask.set(internalEventFlag);
         }
 
         /**
@@ -4121,13 +4121,13 @@
             if (!shouldSendEvent(event)) {
                 if (extraLogging(mPackageName)) {
                     Slog.i(TAG,
-                            "Not sending displayEvent: " + event + " due to flag:"
-                                    + mEventFlagsMask);
+                            "Not sending displayEvent: " + event + " due to mask:"
+                                    + mInternalEventFlagsMask);
                 }
                 if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
                     Trace.instant(Trace.TRACE_TAG_POWER,
-                            "notifyDisplayEventAsync#notSendingEvent=" + event + ",mEventsFlag="
-                                    + mEventFlagsMask);
+                            "notifyDisplayEventAsync#notSendingEvent=" + event
+                                    + ",mInternalEventFlagsMask=" + mInternalEventFlagsMask);
                 }
                 // The client is not interested in this event, so do nothing.
                 return true;
@@ -4173,22 +4173,29 @@
          * Return true if the client is interested in this event.
          */
         private boolean shouldSendEvent(@DisplayEvent int event) {
-            final long flag = mEventFlagsMask.get();
+            final long mask = mInternalEventFlagsMask.get();
             switch (event) {
                 case DisplayManagerGlobal.EVENT_DISPLAY_ADDED:
-                    return (flag & DisplayManager.EVENT_FLAG_DISPLAY_ADDED) != 0;
+                    return (mask & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED) != 0;
                 case DisplayManagerGlobal.EVENT_DISPLAY_CHANGED:
-                    return (flag & DisplayManager.EVENT_FLAG_DISPLAY_CHANGED) != 0;
+                    return (mask & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED) != 0;
                 case DisplayManagerGlobal.EVENT_DISPLAY_BRIGHTNESS_CHANGED:
-                    return (flag & DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS) != 0;
+                    return (mask
+                            & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED)
+                            != 0;
                 case DisplayManagerGlobal.EVENT_DISPLAY_REMOVED:
-                    return (flag & DisplayManager.EVENT_FLAG_DISPLAY_REMOVED) != 0;
+                    return (mask & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED) != 0;
                 case DisplayManagerGlobal.EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED:
-                    return (flag & DisplayManager.EVENT_FLAG_HDR_SDR_RATIO_CHANGED) != 0;
+                    return (mask
+                            & DisplayManagerGlobal
+                            .INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED)
+                            != 0;
                 case DisplayManagerGlobal.EVENT_DISPLAY_CONNECTED:
                     // fallthrough
                 case DisplayManagerGlobal.EVENT_DISPLAY_DISCONNECTED:
-                    return (flag & DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED) != 0;
+                    return (mask
+                            & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED)
+                            != 0;
                 default:
                     // This should never happen.
                     Slog.e(TAG, "Unknown display event " + event);
@@ -4374,15 +4381,16 @@
 
         @Override // Binder call
         public void registerCallback(IDisplayManagerCallback callback) {
-            registerCallbackWithEventMask(callback, DisplayManager.EVENT_FLAG_DISPLAY_ADDED
-                    | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-                    | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED);
+            registerCallbackWithEventMask(callback,
+                    DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
+                    | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED
+                    | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED);
         }
 
         @Override // Binder call
         @SuppressLint("AndroidFrameworkRequiresPermission") // Permission only required sometimes
         public void registerCallbackWithEventMask(IDisplayManagerCallback callback,
-                @EventFlag long eventFlagsMask) {
+                @InternalEventFlag long internalEventFlagsMask) {
             if (callback == null) {
                 throw new IllegalArgumentException("listener must not be null");
             }
@@ -4391,7 +4399,9 @@
             final int callingUid = Binder.getCallingUid();
 
             if (mFlags.isConnectedDisplayManagementEnabled()) {
-                if ((eventFlagsMask & DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED) != 0) {
+                if ((internalEventFlagsMask
+                        & DisplayManagerGlobal
+                        .INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED) != 0) {
                     mContext.enforceCallingOrSelfPermission(MANAGE_DISPLAYS,
                             "Permission required to get signals about connection events.");
                 }
@@ -4399,7 +4409,7 @@
 
             final long token = Binder.clearCallingIdentity();
             try {
-                registerCallbackInternal(callback, callingPid, callingUid, eventFlagsMask);
+                registerCallbackInternal(callback, callingPid, callingUid, internalEventFlagsMask);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 88562ab..8423e19 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -2077,8 +2077,8 @@
             mDeviceConfigDisplaySettings.startListening();
 
             mInjector.registerDisplayListener(this, mHandler,
-                    DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-                            | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS);
+                    DisplayManager.EVENT_FLAG_DISPLAY_CHANGED,
+                    DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS);
         }
 
         private void setLoggingEnabled(boolean loggingEnabled) {
@@ -2878,8 +2878,8 @@
             }
             mDisplayManagerInternal = mInjector.getDisplayManagerInternal();
             mInjector.registerDisplayListener(this, mHandler,
-                    DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
-                    | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED);
+                    DisplayManager.EVENT_FLAG_DISPLAY_REMOVED,
+                    DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS);
         }
 
         /**
@@ -3108,6 +3108,9 @@
         void registerDisplayListener(@NonNull DisplayManager.DisplayListener listener,
                 Handler handler, long flags);
 
+        void registerDisplayListener(@NonNull DisplayManager.DisplayListener listener,
+                Handler handler, long flags, long privateFlags);
+
         Display getDisplay(int displayId);
 
         Display[] getDisplays();
@@ -3175,6 +3178,12 @@
         }
 
         @Override
+        public void registerDisplayListener(DisplayManager.DisplayListener listener,
+                Handler handler, long flags, long privateFlags) {
+            getDisplayManager().registerDisplayListener(listener, handler, flags, privateFlags);
+        }
+
+        @Override
         public Display getDisplay(int displayId) {
             return getDisplayManager().getDisplay(displayId);
         }
diff --git a/services/core/java/com/android/server/flags/services.aconfig b/services/core/java/com/android/server/flags/services.aconfig
index 69ba785..eea5c98 100644
--- a/services/core/java/com/android/server/flags/services.aconfig
+++ b/services/core/java/com/android/server/flags/services.aconfig
@@ -67,3 +67,14 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    namespace: "backstage_power"
+    name: "rate_limit_battery_changed_broadcast"
+    description: "Optimize the delivery of the battery changed broadcast by rate limiting the frequency of the updates"
+    bug: "362337621"
+    is_fixed_read_only: true
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 5fc3e33..05bc69a 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1015,7 +1015,8 @@
                     permission, attributionSource, message, forDataDelivery, startDataDelivery,
                     fromDatasource, attributedOp);
             // Finish any started op if some step in the attribution chain failed.
-            if (startDataDelivery && result != PermissionChecker.PERMISSION_GRANTED) {
+            if (startDataDelivery && result != PermissionChecker.PERMISSION_GRANTED
+                    && result != PermissionChecker.PERMISSION_SOFT_DENIED) {
                 if (attributedOp == AppOpsManager.OP_NONE) {
                     finishDataDelivery(AppOpsManager.permissionToOpCode(permission),
                             attributionSource.asState(), fromDatasource);
@@ -1244,6 +1245,7 @@
             final boolean hasChain = attributionChainId != ATTRIBUTION_CHAIN_ID_NONE;
             AttributionSource current = attributionSource;
             AttributionSource next = null;
+            AttributionSource prev = null;
             // We consider the chain trusted if the start node has UPDATE_APP_OPS_STATS, and
             // every attributionSource in the chain is registered with the system.
             final boolean isChainStartTrusted = !hasChain || checkPermission(context,
@@ -1310,6 +1312,22 @@
                         selfAccess, singleReceiverFromDatasource, attributedOp,
                         proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
 
+                if (startDataDelivery && opMode != AppOpsManager.MODE_ALLOWED) {
+                    // Current failed the perm check, so if we are part-way through an attr chain,
+                    // we need to clean up the already started proxy op higher up the chain.  Note,
+                    // proxy ops are verified two by two, which means we have to clear the 2nd next
+                    // from the previous iteration (since it is actually curr.next which failed
+                    // to pass the perm check).
+                    if (prev != null) {
+                        final var cutAttrSourceState = prev.asState();
+                        if (cutAttrSourceState.next.length > 0) {
+                            cutAttrSourceState.next[0].next = new AttributionSourceState[0];
+                        }
+                        finishDataDelivery(context, attributedOp,
+                                cutAttrSourceState, fromDatasource);
+                    }
+                }
+
                 switch (opMode) {
                     case AppOpsManager.MODE_ERRORED: {
                         if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) {
@@ -1335,6 +1353,8 @@
                     return PermissionChecker.PERMISSION_GRANTED;
                 }
 
+                // an attribution we have already possibly started an op for
+                prev = current;
                 current = next;
             }
         }
diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/services/core/java/com/android/server/vcn/VcnContext.java
index 6ce8685..9213d96 100644
--- a/services/core/java/com/android/server/vcn/VcnContext.java
+++ b/services/core/java/com/android/server/vcn/VcnContext.java
@@ -74,10 +74,6 @@
         return mFeatureFlags;
     }
 
-    public boolean isFlagSafeModeTimeoutConfigEnabled() {
-        return mFeatureFlags.safeModeTimeoutConfig();
-    }
-
     /**
      * Verifies that the caller is running on the VcnContext Thread.
      *
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 2d3bc84..2325f35 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -1263,7 +1263,7 @@
         final PersistableBundleWrapper carrierConfig = snapshot.getCarrierConfigForSubGrp(subGrp);
         int resultSeconds = defaultSeconds;
 
-        if (vcnContext.isFlagSafeModeTimeoutConfigEnabled() && carrierConfig != null) {
+        if (carrierConfig != null) {
             resultSeconds =
                     carrierConfig.getInt(
                             VcnManager.VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY, defaultSeconds);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java
index 06f1b27..a8708f9 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java
@@ -223,7 +223,8 @@
                 mIntRangeUserPerceptionEnabled);
         mSynchronizer.startSynchronizing();
         verify(mDisplayManagerMock).registerDisplayListener(mDisplayListenerCaptor.capture(),
-                isA(Handler.class), eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+                isA(Handler.class), eq(0L),
+                eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
         mDisplayListener = mDisplayListenerCaptor.getValue();
 
         verify(mContentResolverSpy).registerContentObserver(eq(BRIGHTNESS_URI), eq(false),
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index b917af4..c741cae 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -203,11 +203,13 @@
 
     private static final String VIRTUAL_DISPLAY_NAME = "Test Virtual Display";
     private static final String PACKAGE_NAME = "com.android.frameworks.displayservicetests";
-    private static final long STANDARD_DISPLAY_EVENTS = DisplayManager.EVENT_FLAG_DISPLAY_ADDED
-            | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-            | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
+    private static final long STANDARD_DISPLAY_EVENTS =
+            DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
+            | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED
+            | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED;
     private static final long STANDARD_AND_CONNECTION_DISPLAY_EVENTS =
-            STANDARD_DISPLAY_EVENTS | DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED;
+            STANDARD_DISPLAY_EVENTS
+                    | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED;
 
     private static final String EVENT_DISPLAY_ADDED = "EVENT_DISPLAY_ADDED";
     private static final String EVENT_DISPLAY_REMOVED = "EVENT_DISPLAY_REMOVED";
@@ -2379,7 +2381,7 @@
         // register display listener callback
         FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
         long allEventsExceptDisplayAdded = STANDARD_DISPLAY_EVENTS
-                & ~DisplayManager.EVENT_FLAG_DISPLAY_ADDED;
+                & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED;
         displayManagerBinderService.registerCallbackWithEventMask(callback,
                 allEventsExceptDisplayAdded);
 
@@ -2450,7 +2452,7 @@
 
         FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
         long allEventsExceptDisplayRemoved = STANDARD_DISPLAY_EVENTS
-                & ~DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
+                & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED;
         displayManagerBinderService.registerCallbackWithEventMask(callback,
                 allEventsExceptDisplayRemoved);
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index 58f0ab4..f3fc6d7 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -1225,8 +1225,8 @@
                 ArgumentCaptor.forClass(DisplayListener.class);
         verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
                 any(Handler.class),
-                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-                        | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED),
+                eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
         DisplayListener displayListener = displayListenerCaptor.getValue();
 
         setBrightness(10, 10, displayListener);
@@ -1256,8 +1256,8 @@
                 ArgumentCaptor.forClass(DisplayListener.class);
         verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
                 any(Handler.class),
-                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-                        | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED),
+                eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
         DisplayListener displayListener = displayListenerCaptor.getValue();
 
         setBrightness(10, 10, displayListener);
@@ -1291,8 +1291,8 @@
                 ArgumentCaptor.forClass(DisplayListener.class);
         verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
                 any(Handler.class),
-                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-                        | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED),
+                eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
         DisplayListener displayListener = displayListenerCaptor.getValue();
 
         setBrightness(10, 10, displayListener);
@@ -1325,8 +1325,8 @@
                   ArgumentCaptor.forClass(DisplayListener.class);
         verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
                 any(Handler.class),
-                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-                    | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED),
+                eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
         DisplayListener displayListener = displayListenerCaptor.getValue();
 
         ArgumentCaptor<SensorEventListener> sensorListenerCaptor =
@@ -1404,8 +1404,8 @@
                 ArgumentCaptor.forClass(DisplayListener.class);
         verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
                 any(Handler.class),
-                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-                        | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED),
+                eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
         DisplayListener displayListener = displayListenerCaptor.getValue();
 
         ArgumentCaptor<SensorEventListener> sensorListenerCaptor =
@@ -1464,8 +1464,8 @@
                   ArgumentCaptor.forClass(DisplayListener.class);
         verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
                 any(Handler.class),
-                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-                    | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED),
+                eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
         DisplayListener displayListener = displayListenerCaptor.getValue();
 
         ArgumentCaptor<SensorEventListener> listenerCaptor =
@@ -1630,8 +1630,8 @@
                 ArgumentCaptor.forClass(DisplayListener.class);
         verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
                 any(Handler.class),
-                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-                        | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED),
+                eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
         DisplayListener displayListener = displayListenerCaptor.getValue();
 
         // Get the sensor listener so that we can give it new light sensor events
@@ -1730,8 +1730,8 @@
                   ArgumentCaptor.forClass(DisplayListener.class);
         verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
                 any(Handler.class),
-                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-                    | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED),
+                eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
         DisplayListener displayListener = displayListenerCaptor.getValue();
 
         // Get the sensor listener so that we can give it new light sensor events
@@ -2877,8 +2877,8 @@
         ArgumentCaptor<DisplayListener> captor =
                   ArgumentCaptor.forClass(DisplayListener.class);
         verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class),
-                  eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
-                  | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_REMOVED),
+                eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
         DisplayListener listener = captor.getValue();
 
         // Specify Limitation
@@ -3000,8 +3000,8 @@
         ArgumentCaptor<DisplayListener> captor =
                   ArgumentCaptor.forClass(DisplayListener.class);
         verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class),
-                  eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
-                  | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_REMOVED),
+                eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
         DisplayListener listener = captor.getValue();
 
         final int initialRefreshRate = 60;
@@ -3075,8 +3075,8 @@
         ArgumentCaptor<DisplayListener> captor =
                   ArgumentCaptor.forClass(DisplayListener.class);
         verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class),
-                  eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
-                  | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_REMOVED),
+                eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
         DisplayListener listener = captor.getValue();
 
         // Specify Limitation for different display
@@ -3115,8 +3115,8 @@
         ArgumentCaptor<DisplayListener> captor =
                   ArgumentCaptor.forClass(DisplayListener.class);
         verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class),
-                  eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
-                  | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_REMOVED),
+                eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
         DisplayListener listener = captor.getValue();
 
         // Specify Limitation
@@ -3200,8 +3200,8 @@
 
         ArgumentCaptor<DisplayListener> captor = ArgumentCaptor.forClass(DisplayListener.class);
         verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class),
-                eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
-                        | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_REMOVED),
+                eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
         DisplayListener listener = captor.getValue();
 
         // Specify Sunlight limitations
@@ -3239,8 +3239,8 @@
         ArgumentCaptor<DisplayListener> captor =
                   ArgumentCaptor.forClass(DisplayListener.class);
         verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class),
-                  eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
-                  | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_REMOVED),
+                eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
         DisplayListener listener = captor.getValue();
 
         // Specify Limitation for different display
@@ -3897,7 +3897,12 @@
         public void registerDisplayListener(DisplayListener listener, Handler handler) {}
 
         @Override
-        public void registerDisplayListener(DisplayListener listener, Handler handler, long flag) {}
+        public void registerDisplayListener(DisplayListener listener, Handler handler,
+                long flags) {}
+
+        @Override
+        public void registerDisplayListener(DisplayListener listener, Handler handler, long flag,
+                long privateFlag) {}
 
         @Override
         public Display getDisplay(int displayId) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/BatteryServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/BatteryServiceTest.java
new file mode 100644
index 0000000..5e2f80b
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/BatteryServiceTest.java
@@ -0,0 +1,338 @@
+/*
+ * 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.server;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.hardware.health.HealthInfo;
+import android.os.HandlerThread;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.R;
+import com.android.internal.app.IBatteryStats;
+import com.android.modules.utils.testing.ExtendedMockitoRule;
+import com.android.server.am.BatteryStatsService;
+import com.android.server.flags.Flags;
+import com.android.server.lights.LightsManager;
+import com.android.server.lights.LogicalLight;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class BatteryServiceTest {
+
+    private static final int CURRENT_BATTERY_VOLTAGE = 3000;
+    private static final int VOLTAGE_LESS_THEN_ONE_PERCENT = 3029;
+    private static final int VOLTAGE_MORE_THEN_ONE_PERCENT = 3030;
+    private static final int CURRENT_BATTERY_TEMP = 300;
+    private static final int TEMP_LESS_THEN_ONE_DEGREE_CELSIUS = 305;
+    private static final int TEMP_MORE_THEN_ONE_DEGREE_CELSIUS = 310;
+    private static final int CURRENT_BATTERY_HEALTH = 2;
+    private static final int UPDATED_BATTERY_HEALTH = 3;
+    private static final int CURRENT_CHARGE_COUNTER = 4680000;
+    private static final int UPDATED_CHARGE_COUNTER = 4218000;
+    private static final int HANDLER_IDLE_TIME_MS = 5000;
+    @Rule
+    public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+            .mockStatic(SystemProperties.class)
+            .mockStatic(ActivityManager.class)
+            .mockStatic(BatteryStatsService.class)
+            .build();
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+    @Mock
+    private Context mContextMock;
+    @Mock
+    private LightsManager mLightsManagerMock;
+    @Mock
+    private ActivityManagerInternal mActivityManagerInternalMock;
+    @Mock
+    private IBatteryStats mIBatteryStatsMock;
+
+    private BatteryService mBatteryService;
+    private String mSystemUiPackage;
+
+    /**
+     * Creates a mock and registers it to {@link LocalServices}.
+     */
+    private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
+        LocalServices.removeServiceForTest(clazz);
+        LocalServices.addService(clazz, mock);
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mSystemUiPackage = InstrumentationRegistry.getInstrumentation().getTargetContext()
+                .getResources().getString(R.string.config_systemUi);
+
+        when(mLightsManagerMock.getLight(anyInt())).thenReturn(mock(LogicalLight.class));
+        when(mActivityManagerInternalMock.isSystemReady()).thenReturn(true);
+        when(mContextMock.getResources()).thenReturn(
+                InstrumentationRegistry.getInstrumentation().getTargetContext().getResources());
+        ExtendedMockito.when(BatteryStatsService.getService()).thenReturn(mIBatteryStatsMock);
+
+        doNothing().when(mIBatteryStatsMock).setBatteryState(anyInt(), anyInt(), anyInt(), anyInt(),
+                anyInt(), anyInt(), anyInt(), anyInt(), anyLong());
+        doNothing().when(() -> SystemProperties.set(anyString(), anyString()));
+        doNothing().when(() -> ActivityManager.broadcastStickyIntent(any(),
+                eq(new String[]{mSystemUiPackage}), eq(AppOpsManager.OP_NONE),
+                eq(BatteryService.BATTERY_CHANGED_OPTIONS), eq(UserHandle.USER_ALL)));
+
+        addLocalServiceMock(LightsManager.class, mLightsManagerMock);
+        addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock);
+
+        createBatteryService();
+    }
+
+    @Test
+    public void createBatteryService_withNullLooper_throwsNullPointerException() {
+        assertThrows(NullPointerException.class, () -> new BatteryService(mContextMock));
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+    public void onlyVoltageUpdated_lessThenOnePercent_broadcastNotSent() {
+        mBatteryService.update(createHealthInfo(VOLTAGE_LESS_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
+                CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH));
+
+        waitForHandlerToExecute();
+
+        verifyNumberOfTimesBroadcastSent(0);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+    public void onlyVoltageUpdated_beforeTwentySeconds_broadcastNotSent() {
+        mBatteryService.update(
+                createHealthInfo(VOLTAGE_MORE_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
+                        CURRENT_CHARGE_COUNTER,
+                        CURRENT_BATTERY_HEALTH));
+
+        waitForHandlerToExecute();
+
+        verifyNumberOfTimesBroadcastSent(0);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+    public void onlyVoltageUpdated_broadcastSent() {
+        mBatteryService.mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime() - 20000;
+        mBatteryService.update(createHealthInfo(VOLTAGE_MORE_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
+                CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH));
+
+        waitForHandlerToExecute();
+
+        verifyNumberOfTimesBroadcastSent(1);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+    public void onlyTempUpdated_lessThenOneDegreeCelsius_broadcastNotSent() {
+        mBatteryService.update(
+                createHealthInfo(CURRENT_BATTERY_VOLTAGE, TEMP_LESS_THEN_ONE_DEGREE_CELSIUS,
+                        CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH));
+
+        waitForHandlerToExecute();
+
+        verifyNumberOfTimesBroadcastSent(0);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+    public void tempUpdated_broadcastSent() {
+        long lastVoltageUpdateTime = mBatteryService.mLastBroadcastVoltageUpdateTime;
+        mBatteryService.update(
+                createHealthInfo(VOLTAGE_LESS_THEN_ONE_PERCENT, TEMP_MORE_THEN_ONE_DEGREE_CELSIUS,
+                        UPDATED_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH));
+
+        waitForHandlerToExecute();
+
+        assertTrue(lastVoltageUpdateTime < mBatteryService.mLastBroadcastVoltageUpdateTime);
+        verifyNumberOfTimesBroadcastSent(1);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+    public void batteryHealthUpdated_voltageAndTempConst_broadcastSent() {
+        mBatteryService.update(
+                createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
+                        CURRENT_CHARGE_COUNTER,
+                        UPDATED_BATTERY_HEALTH));
+
+        waitForHandlerToExecute();
+
+        verifyNumberOfTimesBroadcastSent(1);
+
+        // updating counter just after the health update does not triggers broadcast.
+        mBatteryService.update(
+                createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
+                        UPDATED_CHARGE_COUNTER,
+                        UPDATED_BATTERY_HEALTH));
+
+        waitForHandlerToExecute();
+
+        verifyNumberOfTimesBroadcastSent(1);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+    public void voltageUpdated_lessThanOnePercent_flagDisabled_broadcastSent() {
+        mBatteryService.update(createHealthInfo(VOLTAGE_LESS_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
+                CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH));
+
+        waitForHandlerToExecute();
+
+        verifyNumberOfTimesBroadcastSent(1);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+    public void onlyChargeCounterUpdated_broadcastNotSent() {
+        mBatteryService.update(
+                createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
+                        UPDATED_CHARGE_COUNTER,
+                        CURRENT_BATTERY_HEALTH));
+
+        waitForHandlerToExecute();
+
+        verifyNumberOfTimesBroadcastSent(0);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+    public void chargeCounterUpdated_tempUpdatedLessThanOneDegree_broadcastNotSent() {
+        mBatteryService.update(
+                createHealthInfo(CURRENT_BATTERY_VOLTAGE, TEMP_LESS_THEN_ONE_DEGREE_CELSIUS,
+                        UPDATED_CHARGE_COUNTER,
+                        CURRENT_BATTERY_HEALTH));
+
+        waitForHandlerToExecute();
+
+        verifyNumberOfTimesBroadcastSent(0);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+    public void onlyChargeCounterUpdated_broadcastSent() {
+        mBatteryService.update(
+                createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
+                        UPDATED_CHARGE_COUNTER,
+                        CURRENT_BATTERY_HEALTH));
+
+        waitForHandlerToExecute();
+
+        verifyNumberOfTimesBroadcastSent(1);
+    }
+
+    private HealthInfo createHealthInfo(
+            int batteryVoltage,
+            int batteryTemperature,
+            int batteryChargeCounter,
+            int batteryHealth) {
+        HealthInfo h = new HealthInfo();
+        h.batteryVoltageMillivolts = batteryVoltage;
+        h.batteryTemperatureTenthsCelsius = batteryTemperature;
+        h.batteryChargeCounterUah = batteryChargeCounter;
+        h.batteryStatus = 5;
+        h.batteryHealth = batteryHealth;
+        h.batteryPresent = true;
+        h.batteryLevel = 100;
+        h.maxChargingCurrentMicroamps = 298125;
+        h.batteryCurrentAverageMicroamps = -2812;
+        h.batteryCurrentMicroamps = 298125;
+        h.maxChargingVoltageMicrovolts = 3000;
+        h.batteryCycleCount = 50;
+        h.chargingState = 4;
+        h.batteryCapacityLevel = 100;
+        return h;
+    }
+
+    // Creates a new battery service objects and sets the initial values.
+    private void createBatteryService() throws InterruptedException {
+        final HandlerThread handlerThread = new HandlerThread("BatteryServiceTest");
+        handlerThread.start();
+
+        mBatteryService = new BatteryService(mContextMock, handlerThread.getLooper());
+
+        // trigger the update to set the initial values.
+        mBatteryService.update(
+                createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
+                        CURRENT_CHARGE_COUNTER,
+                        CURRENT_BATTERY_HEALTH));
+
+        waitForHandlerToExecute();
+    }
+
+    private void waitForHandlerToExecute() {
+        final CountDownLatch latch = new CountDownLatch(1);
+        mBatteryService.getHandlerForTest().post(latch::countDown);
+        boolean isExecutionComplete = false;
+
+        try {
+            isExecutionComplete = latch.await(HANDLER_IDLE_TIME_MS, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            fail("Handler interrupted before executing the message " + e);
+        }
+
+        assertTrue("Timed out while waiting for Handler to execute.", isExecutionComplete);
+    }
+
+    private void verifyNumberOfTimesBroadcastSent(int numberOfTimes) {
+        // Increase the numberOfTimes by 1 as one broadcast was sent initially during the test
+        // setUp.
+        verify(() -> ActivityManager.broadcastStickyIntent(any(),
+                        eq(new String[]{mSystemUiPackage}), eq(AppOpsManager.OP_NONE),
+                        eq(BatteryService.BATTERY_CHANGED_OPTIONS), eq(UserHandle.USER_ALL)),
+                times(++numberOfTimes));
+    }
+}
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
index 49665f7..613b926 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
@@ -360,12 +360,10 @@
 
     private void verifyGetSafeModeTimeoutMs(
             boolean isInTestMode,
-            boolean isConfigTimeoutSupported,
             PersistableBundleWrapper carrierConfig,
             long expectedTimeoutMs)
             throws Exception {
         doReturn(isInTestMode).when(mVcnContext).isInTestMode();
-        doReturn(isConfigTimeoutSupported).when(mVcnContext).isFlagSafeModeTimeoutConfigEnabled();
 
         final TelephonySubscriptionSnapshot snapshot = mock(TelephonySubscriptionSnapshot.class);
         doReturn(carrierConfig).when(snapshot).getCarrierConfigForSubGrp(TEST_SUB_GRP);
@@ -377,16 +375,7 @@
     }
 
     @Test
-    public void testGetSafeModeTimeoutMs_configTimeoutUnsupported() throws Exception {
-        verifyGetSafeModeTimeoutMs(
-                false /* isInTestMode */,
-                false /* isConfigTimeoutSupported */,
-                null /* carrierConfig */,
-                TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS));
-    }
-
-    @Test
-    public void testGetSafeModeTimeoutMs_configTimeoutSupported() throws Exception {
+    public void testGetSafeModeTimeoutMs() throws Exception {
         final int carrierConfigTimeoutSeconds = 20;
         final PersistableBundleWrapper carrierConfig = mock(PersistableBundleWrapper.class);
         doReturn(carrierConfigTimeoutSeconds)
@@ -395,17 +384,14 @@
 
         verifyGetSafeModeTimeoutMs(
                 false /* isInTestMode */,
-                true /* isConfigTimeoutSupported */,
                 carrierConfig,
                 TimeUnit.SECONDS.toMillis(carrierConfigTimeoutSeconds));
     }
 
     @Test
-    public void testGetSafeModeTimeoutMs_configTimeoutSupported_carrierConfigNull()
-            throws Exception {
+    public void testGetSafeModeTimeoutMs_carrierConfigNull() throws Exception {
         verifyGetSafeModeTimeoutMs(
                 false /* isInTestMode */,
-                true /* isConfigTimeoutSupported */,
                 null /* carrierConfig */,
                 TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS));
     }
@@ -420,7 +406,6 @@
 
         verifyGetSafeModeTimeoutMs(
                 true /* isInTestMode */,
-                true /* isConfigTimeoutSupported */,
                 carrierConfig,
                 TimeUnit.SECONDS.toMillis(carrierConfigTimeoutSeconds));
     }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
index 4c7b25a..8374fd9 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
@@ -222,7 +222,6 @@
         doReturn(mTestLooper.getLooper()).when(mVcnContext).getLooper();
         doReturn(mVcnNetworkProvider).when(mVcnContext).getVcnNetworkProvider();
         doReturn(mFeatureFlags).when(mVcnContext).getFeatureFlags();
-        doReturn(true).when(mVcnContext).isFlagSafeModeTimeoutConfigEnabled();
 
         doReturn(mUnderlyingNetworkController)
                 .when(mDeps)