diff options
40 files changed, 1917 insertions, 77 deletions
diff --git a/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeTouchHelper.java b/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeTouchHelper.java index 39a2ab3010ac..43118a0800fb 100644 --- a/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeTouchHelper.java +++ b/core/java/com/android/internal/widget/remotecompose/accessibility/PlatformRemoteComposeTouchHelper.java @@ -30,7 +30,7 @@ import com.android.internal.widget.ExploreByTouchHelper; import com.android.internal.widget.remotecompose.core.CoreDocument; import com.android.internal.widget.remotecompose.core.operations.layout.Component; import com.android.internal.widget.remotecompose.core.semantics.AccessibilitySemantics; -import com.android.internal.widget.remotecompose.core.semantics.CoreSemantics.Mode; +import com.android.internal.widget.remotecompose.core.semantics.AccessibleComponent.Mode; import java.util.HashSet; import java.util.List; 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 60767ed9eb00..7c48aa60feeb 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java +++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java @@ -123,6 +123,11 @@ public class CoreDocument { return mWidth; } + /** + * Set the viewport width of the document + * + * @param width document width + */ public void setWidth(int width) { this.mWidth = width; mRemoteComposeState.setWindowWidth(width); @@ -132,6 +137,11 @@ public class CoreDocument { return mHeight; } + /** + * Set the viewport height of the document + * + * @param height document height + */ public void setHeight(int height) { this.mHeight = height; mRemoteComposeState.setWindowHeight(height); @@ -395,6 +405,11 @@ public class CoreDocument { // ============== Haptic support ================== public interface HapticEngine { + /** + * Implements a haptic effect + * + * @param type the type of effect + */ void haptic(int type); } @@ -404,6 +419,11 @@ public class CoreDocument { mHapticEngine = engine; } + /** + * Execute an haptic command + * + * @param type the type of haptic pre-defined effect + */ public void haptic(int type) { if (mHapticEngine != null) { mHapticEngine.haptic(type); @@ -412,12 +432,23 @@ public class CoreDocument { // ============== Haptic support ================== - public void appliedTouchOperation(Component operation) { - mAppliedTouchOperations.add(operation); + /** + * To signal that the given component will apply the touch operation + * + * @param component the component applying the touch + */ + public void appliedTouchOperation(Component component) { + mAppliedTouchOperations.add(component); } /** Callback interface for host actions */ public interface ActionCallback { + /** + * Callback for actions + * + * @param name the action name + * @param value the payload of the action + */ void onAction(@NonNull String name, Object value); } @@ -450,7 +481,14 @@ public class CoreDocument { mActionListeners.clear(); } + /** Id Actions */ public interface IdActionCallback { + /** + * Callback on Id Actions + * + * @param id the actio id triggered + * @param metadata optional metadata + */ void onAction(int id, @Nullable String metadata); } @@ -530,10 +568,20 @@ public class CoreDocument { return mTop; } + /** + * Returns the width of the click area + * + * @return the width of the click area + */ public float width() { return Math.max(0, mRight - mLeft); } + /** + * Returns the height of the click area + * + * @return the height of the click area + */ public float height() { return Math.max(0, mBottom - mTop); } @@ -816,6 +864,7 @@ public class CoreDocument { for (ClickAreaRepresentation clickArea : mClickAreas) { if (clickArea.mId == id) { warnClickListeners(clickArea); + return; } } for (IdActionCallback listener : mIdActionListeners) { @@ -1127,6 +1176,11 @@ public class CoreDocument { return count; } + /** + * Returns a list of useful statistics for the runtime document + * + * @return + */ @NonNull public String[] getStats() { ArrayList<String> ret = new ArrayList<>(); @@ -1145,8 +1199,8 @@ public class CoreDocument { values[0] += 1; values[1] += sizeOfComponent(mOperation, buffer); - if (mOperation instanceof Component) { - Component com = (Component) mOperation; + if (mOperation instanceof Container) { + Container com = (Container) mOperation; count += addChildren(com, map, buffer); } else if (mOperation instanceof LoopOperation) { LoopOperation com = (LoopOperation) mOperation; @@ -1172,35 +1226,9 @@ public class CoreDocument { } 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(); - int[] values; - if (map.containsKey(c.getSimpleName())) { - values = map.get(c.getSimpleName()); - } else { - values = new int[2]; - map.put(c.getSimpleName(), values); - } - values[0] += 1; - values[1] += sizeOfComponent(mOperation, tmp); - if (mOperation instanceof Component) { - count += addChildren((Component) mOperation, map, tmp); - } - if (mOperation instanceof LoopOperation) { - count += addChildren((LoopOperation) mOperation, map, tmp); - } - } - return count; - } - - private int addChildren( - @NonNull LoopOperation base, - @NonNull HashMap<String, int[]> map, - @NonNull WireBuffer tmp) { - int count = base.mList.size(); - for (Operation mOperation : base.mList) { + @NonNull Container base, @NonNull HashMap<String, int[]> map, @NonNull WireBuffer tmp) { + int count = base.getList().size(); + for (Operation mOperation : base.getList()) { Class<? extends Operation> c = mOperation.getClass(); int[] values; if (map.containsKey(c.getSimpleName())) { @@ -1211,39 +1239,42 @@ public class CoreDocument { } values[0] += 1; values[1] += sizeOfComponent(mOperation, tmp); - if (mOperation instanceof Component) { - count += addChildren((Component) mOperation, map, tmp); - } - if (mOperation instanceof LoopOperation) { - count += addChildren((LoopOperation) mOperation, map, tmp); + if (mOperation instanceof Container) { + count += addChildren((Container) mOperation, map, tmp); } } return count; } + /** + * Returns a string representation of the operations, traversing the list of operations & + * containers + * + * @return + */ @NonNull public String toNestedString() { StringBuilder ret = new StringBuilder(); for (Operation mOperation : mOperations) { ret.append(mOperation.toString()); ret.append("\n"); - if (mOperation instanceof Component) { - toNestedString((Component) mOperation, ret, " "); + if (mOperation instanceof Container) { + toNestedString((Container) mOperation, ret, " "); } } return ret.toString(); } private void toNestedString( - @NonNull Component base, @NonNull StringBuilder ret, String indent) { - for (Operation mOperation : base.mList) { + @NonNull Container base, @NonNull StringBuilder ret, String indent) { + for (Operation mOperation : base.getList()) { for (String line : mOperation.toString().split("\n")) { ret.append(indent); ret.append(line); ret.append("\n"); } - if (mOperation instanceof Component) { - toNestedString((Component) mOperation, ret, indent + " "); + if (mOperation instanceof Container) { + toNestedString((Container) mOperation, ret, indent + " "); } } } @@ -1255,6 +1286,12 @@ public class CoreDocument { /** defines if a shader can be run */ public interface ShaderControl { + /** + * validate if a shader can run in the document + * + * @param shader the source of the shader + * @return true if the shader is allowed to run + */ boolean isShaderValid(String shader); } 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 d9f12cb71d53..9a37a22390a2 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/Operations.java +++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java @@ -55,6 +55,8 @@ import com.android.internal.widget.remotecompose.core.operations.MatrixSkew; import com.android.internal.widget.remotecompose.core.operations.MatrixTranslate; import com.android.internal.widget.remotecompose.core.operations.NamedVariable; import com.android.internal.widget.remotecompose.core.operations.PaintData; +import com.android.internal.widget.remotecompose.core.operations.ParticlesCreate; +import com.android.internal.widget.remotecompose.core.operations.ParticlesLoop; import com.android.internal.widget.remotecompose.core.operations.PathAppend; import com.android.internal.widget.remotecompose.core.operations.PathCreate; import com.android.internal.widget.remotecompose.core.operations.PathData; @@ -75,6 +77,8 @@ import com.android.internal.widget.remotecompose.core.operations.layout.CanvasCo import com.android.internal.widget.remotecompose.core.operations.layout.ClickModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.ComponentStart; import com.android.internal.widget.remotecompose.core.operations.layout.ContainerEnd; +import com.android.internal.widget.remotecompose.core.operations.layout.ImpulseOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.ImpulseProcess; import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponentContent; import com.android.internal.widget.remotecompose.core.operations.layout.LoopOperation; import com.android.internal.widget.remotecompose.core.operations.layout.RootLayoutComponent; @@ -188,6 +192,11 @@ public class Operations { public static final int PATH_TWEEN = 158; public static final int PATH_CREATE = 159; public static final int PATH_ADD = 160; + public static final int PARTICLE_CREATE = 161; + public static final int PARTICLE_PROCESS = 162; + public static final int PARTICLE_LOOP = 163; + public static final int IMPULSE_START = 164; + public static final int IMPULSE_PROCESS = 165; ///////////////////////////////////////// ====================== @@ -366,6 +375,10 @@ public class Operations { map.put(PATH_TWEEN, PathTween::read); map.put(PATH_CREATE, PathCreate::read); map.put(PATH_ADD, PathAppend::read); + map.put(IMPULSE_START, ImpulseOperation::read); + map.put(IMPULSE_PROCESS, ImpulseProcess::read); + map.put(PARTICLE_CREATE, ParticlesCreate::read); + map.put(PARTICLE_LOOP, ParticlesLoop::read); map.put(ACCESSIBILITY_SEMANTICS, CoreSemantics::read); // map.put(ACCESSIBILITY_CUSTOM_ACTION, CoreSemantics::read); diff --git a/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java b/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java index 4a40a31ef6ab..49a0457c3595 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java +++ b/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java @@ -33,10 +33,16 @@ public abstract class PaintContext { return mContext; } + /** + * Returns true if the needsRepaint flag is set + * + * @return true if the document asks to be repainted + */ public boolean doesNeedsRepaint() { return mNeedsRepaint; } + /** Clear the needsRepaint flag */ public void clearNeedsRepaint() { mNeedsRepaint = false; } @@ -65,6 +71,20 @@ public abstract class PaintContext { matrixSave(); } + /** + * Draw a bitmap + * + * @param imageId + * @param srcLeft + * @param srcTop + * @param srcRight + * @param srcBottom + * @param dstLeft + * @param dstTop + * @param dstRight + * @param dstBottom + * @param cdId + */ public abstract void drawBitmap( int imageId, int srcLeft, @@ -77,26 +97,105 @@ public abstract class PaintContext { int dstBottom, int cdId); + /** + * scale the following commands + * + * @param scaleX horizontal scale factor + * @param scaleY vertical scale factor + */ public abstract void scale(float scaleX, float scaleY); + /** + * Rotate the following commands + * + * @param translateX horizontal translation + * @param translateY vertical translation + */ public abstract void translate(float translateX, float translateY); + /** + * Draw an arc + * + * @param left + * @param top + * @param right + * @param bottom + * @param startAngle + * @param sweepAngle + */ public abstract void drawArc( float left, float top, float right, float bottom, float startAngle, float sweepAngle); + /** + * Draw a sector + * + * @param left + * @param top + * @param right + * @param bottom + * @param startAngle + * @param sweepAngle + */ public abstract void drawSector( float left, float top, float right, float bottom, float startAngle, float sweepAngle); + /** + * Draw a bitmap + * + * @param id + * @param left + * @param top + * @param right + * @param bottom + */ public abstract void drawBitmap(int id, float left, float top, float right, float bottom); + /** + * Draw a circle + * + * @param centerX + * @param centerY + * @param radius + */ public abstract void drawCircle(float centerX, float centerY, float radius); + /** + * Draw a line + * + * @param x1 + * @param y1 + * @param x2 + * @param y2 + */ public abstract void drawLine(float x1, float y1, float x2, float y2); + /** + * Draw an oval + * + * @param left + * @param top + * @param right + * @param bottom + */ public abstract void drawOval(float left, float top, float right, float bottom); + /** + * Draw a path + * + * @param id the path id + * @param start starting point of the path where we start drawing it + * @param end ending point of the path where we stop drawing it + */ public abstract void drawPath(int id, float start, float end); + /** + * Draw a rectangle + * + * @param left left coordinate of the rectangle + * @param top top coordinate of the rectangle + * @param right right coordinate of the rectangle + * @param bottom bottom coordinate of the rectangle + */ public abstract void drawRect(float left, float top, float right, float bottom); /** this caches the paint to a paint stack */ @@ -105,9 +204,27 @@ public abstract class PaintContext { /** This restores the paint form the paint stack */ public abstract void restorePaint(); + /** + * draw a round rect + * + * @param left left coordinate of the rectangle + * @param top top coordinate of the rectangle + * @param right right coordinate of the rectangle + * @param bottom bottom coordinate of the rectangle + * @param radiusX horizontal radius of the rounded corner + * @param radiusY vertical radius of the rounded corner + */ public abstract void drawRoundRect( float left, float top, float right, float bottom, float radiusX, float radiusY); + /** + * Draw the text glyphs on the provided path + * + * @param textId id of the text + * @param pathId id of the path + * @param hOffset horizontal offset + * @param vOffset vertical offset + */ public abstract void drawTextOnPath(int textId, int pathId, float hOffset, float vOffset); /** @@ -158,6 +275,14 @@ public abstract class PaintContext { public abstract void drawTweenPath( int path1Id, int path2Id, float tween, float start, float stop); + /** + * Interpolate between two path and return the resulting path + * + * @param out the interpolated path + * @param path1 start path + * @param path2 end path + * @param tween interpolation value from 0 (start path) to 1 (end path) + */ public abstract void tweenPath(int out, int path1, int path2, float tween); /** @@ -275,12 +400,33 @@ public abstract class PaintContext { System.out.println("[LOG] " + content); } + /** Indicates the document needs to be repainted */ public void needsRepaint() { mNeedsRepaint = true; } + /** + * Starts a graphics layer + * + * @param w + * @param h + */ public abstract void startGraphicsLayer(int w, int h); + /** + * Starts a graphics layer + * + * @param scaleX + * @param scaleY + * @param rotationX + * @param rotationY + * @param rotationZ + * @param shadowElevation + * @param transformOriginX + * @param transformOriginY + * @param alpha + * @param renderEffectId + */ public abstract void setGraphicsLayer( float scaleX, float scaleY, @@ -293,6 +439,7 @@ public abstract class PaintContext { float alpha, int renderEffectId); + /** Ends a graphics layer */ public abstract void endGraphicsLayer(); public boolean 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 cfdd52212b03..f355676be63e 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java @@ -39,6 +39,11 @@ public abstract class PaintOperation extends Operation { return indent + toString(); } + /** + * Paint the operation in the context + * + * @param context painting context + */ public abstract void paint(@NonNull PaintContext context); /** 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 dcb8efebeecc..e4a063d7d6ff 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/Platform.java +++ b/core/java/com/android/internal/widget/remotecompose/core/Platform.java @@ -20,13 +20,38 @@ import android.annotation.Nullable; /** Services that are needed to be provided by the platform during encoding. */ public interface Platform { + + /** + * Converts a platform-specific image object into a platform-independent byte buffer + * + * @param image + * @return + */ @Nullable byte[] imageToByteArray(@NonNull Object image); + /** + * Returns the width of a platform-specific image object + * + * @param image platform-specific image object + * @return the width of the image in pixels + */ int getImageWidth(@NonNull Object image); + /** + * Returns the height of a platform-specific image object + * + * @param image platform-specific image object + * @return the height of the image in pixels + */ int getImageHeight(@NonNull Object image); + /** + * Converts a platform-specific path object into a platform-independent float buffer + * + * @param path + * @return + */ @Nullable float[] pathToFloatArray(@NonNull Object path); @@ -38,6 +63,12 @@ public interface Platform { TODO, } + /** + * Log a message + * + * @param category + * @param message + */ void log(LogCategory category, String message); Platform None = 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 fc1e36d45a81..39cc997fadc2 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java +++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java @@ -58,6 +58,8 @@ import com.android.internal.widget.remotecompose.core.operations.MatrixSkew; import com.android.internal.widget.remotecompose.core.operations.MatrixTranslate; import com.android.internal.widget.remotecompose.core.operations.NamedVariable; import com.android.internal.widget.remotecompose.core.operations.PaintData; +import com.android.internal.widget.remotecompose.core.operations.ParticlesCreate; +import com.android.internal.widget.remotecompose.core.operations.ParticlesLoop; import com.android.internal.widget.remotecompose.core.operations.PathAppend; import com.android.internal.widget.remotecompose.core.operations.PathCreate; import com.android.internal.widget.remotecompose.core.operations.PathData; @@ -77,6 +79,8 @@ 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.ComponentStart; import com.android.internal.widget.remotecompose.core.operations.layout.ContainerEnd; +import com.android.internal.widget.remotecompose.core.operations.layout.ImpulseOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.ImpulseProcess; import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponentContent; import com.android.internal.widget.remotecompose.core.operations.layout.LoopOperation; import com.android.internal.widget.remotecompose.core.operations.layout.RootLayoutComponent; @@ -411,11 +415,7 @@ public class RemoteComposeBuffer { BitmapData.apply( mBuffer, imageId, imageWidth, imageHeight, data); // todo: potential npe } - int contentDescriptionId = 0; - if (contentDescription != null) { - contentDescriptionId = addText(contentDescription); - } - DrawBitmap.apply(mBuffer, imageId, left, top, right, bottom, contentDescriptionId); + addDrawBitmap(imageId, left, top, right, bottom, contentDescription); } /** @@ -441,6 +441,24 @@ public class RemoteComposeBuffer { } /** + * @param imageId The Id bitmap to be drawn + * @param left left coordinate of rectangle that the bitmap will be to fit into + * @param top top coordinate of rectangle that the bitmap will be to fit into + * @param contentDescription content description of the image + */ + public void addDrawBitmap( + int imageId, float left, float top, @Nullable String contentDescription) { + int imageWidth = mPlatform.getImageWidth(imageId); + int imageHeight = mPlatform.getImageHeight(imageId); + int contentDescriptionId = 0; + if (contentDescription != null) { + contentDescriptionId = addText(contentDescription); + } + DrawBitmap.apply( + mBuffer, imageId, left, top, imageWidth, imageHeight, contentDescriptionId); + } + + /** * @param image The bitmap to be drawn * @param srcLeft left coordinate in the source bitmap will be to extracted * @param srcTop top coordinate in the source bitmap will be to extracted @@ -537,7 +555,7 @@ public class RemoteComposeBuffer { } /** - * This defines the name of the color given the id. + * This defines the name of the bitmap given the id. * * @param id of the Bitmap * @param name Name of the color @@ -2060,4 +2078,61 @@ public class RemoteComposeBuffer { public int nextId() { return mRemoteComposeState.nextId(); } + + private boolean mInImpulseProcess = false; + + /** + * add an impulse. (must be followed by impulse end) + * + * @param duration duration of the impulse + * @param start the start time + */ + public void addImpulse(float duration, float start) { + ImpulseOperation.apply(mBuffer, duration, start); + mInImpulseProcess = false; + } + + /** add an impulse process */ + public void addImpulseProcess() { + ImpulseProcess.apply(mBuffer); + mInImpulseProcess = true; + } + + /** Add an impulse end */ + public void addImpulseEnd() { + ContainerEnd.apply(mBuffer); + if (mInImpulseProcess) { + ContainerEnd.apply(mBuffer); + } + mInImpulseProcess = false; + } + + /** + * Start a particle engine container & initial setup + * + * @param id the particle engine id + * @param varIds list of variable ids + * @param initialExpressions the expressions used to initialize the variables + * @param particleCount the number of particles to draw + */ + public void addParticles( + int id, int[] varIds, float[][] initialExpressions, int particleCount) { + ParticlesCreate.apply(mBuffer, id, varIds, initialExpressions, particleCount); + } + + /** + * Setup the particle engine loop + * + * @param id the particle engine id + * @param restart value on restart + * @param expressions the expressions used to update the variables during the particles run + */ + public void addParticlesLoop(int id, float[] restart, float[][] expressions) { + ParticlesLoop.apply(mBuffer, id, restart, expressions); + } + + /** Closes the particle engine container */ + public void addParticleLoopEnd() { + ContainerEnd.apply(mBuffer); + } } 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 63469aabaed5..7d71584174bc 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java +++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java @@ -45,7 +45,7 @@ public abstract class RemoteContext { new CoreDocument(); // todo: is this a valid way to initialize? bbade@ public @NonNull RemoteComposeState mRemoteComposeState = new RemoteComposeState(); // todo, is this a valid use of RemoteComposeState -- bbade@ - long mStart = System.nanoTime(); // todo This should be set at a hi level + @Nullable protected PaintContext mPaintContext = null; protected float mDensity = 2.75f; @@ -71,6 +71,11 @@ public abstract class RemoteContext { return mDensity; } + /** + * Set the density of the document + * + * @param density + */ public void setDensity(float density) { if (density > 0) { mDensity = density; @@ -142,7 +147,6 @@ public abstract class RemoteContext { * @return a monotonic time in seconds (arbitrary zero point) */ public float getAnimationTime() { - mAnimationTime = (System.nanoTime() - mStart) * 1E-9f; // Eliminate return mAnimationTime; } @@ -243,6 +247,11 @@ public abstract class RemoteContext { public abstract @Nullable Object getObject(int mId); + /** + * Add a touch listener to the context + * + * @param touchExpression + */ public void addTouchListener(TouchListener touchExpression) {} /** @@ -337,6 +346,16 @@ public abstract class RemoteContext { // Operations /////////////////////////////////////////////////////////////////////////////////////////////// + /** + * Set the main information about a document + * + * @param majorVersion major version of the document protocol used + * @param minorVersion minor version of the document protocol used + * @param patchVersion patch version of the document protocol used + * @param width original width of the document when created + * @param height original height of the document when created + * @param capabilities bitmask of capabilities used in the document (TBD) + */ public void header( int majorVersion, int minorVersion, @@ -552,6 +571,15 @@ public abstract class RemoteContext { /** Defines when the last build was made */ public static final int ID_API_LEVEL = 28; + /** Defines when the TOUCH EVENT HAPPENED */ + public static final int ID_TOUCH_EVENT_TIME = 29; + + /** Animation time in seconds */ + public static final int ID_ANIMATION_TIME = 30; + + /** The delta between current and last Frame */ + public static final int ID_ANIMATION_DELTA_TIME = 31; + public static final float FLOAT_DENSITY = Utils.asNan(ID_DENSITY); /** CONTINUOUS_SEC is seconds from midnight looping every hour 0-3600 */ @@ -595,6 +623,15 @@ public abstract class RemoteContext { /** TOUCH_VEL_Y is the x velocity of the touch */ public static final float FLOAT_TOUCH_VEL_Y = Utils.asNan(ID_TOUCH_VEL_Y); + /** TOUCH_EVENT_TIME the time of the touch */ + public static final float FLOAT_TOUCH_EVENT_TIME = Utils.asNan(ID_TOUCH_EVENT_TIME); + + /** Animation time in seconds */ + public static final float FLOAT_ANIMATION_TIME = Utils.asNan(ID_ANIMATION_TIME); + + /** Animation time in seconds */ + public static final float FLOAT_ANIMATION_DELTA_TIME = Utils.asNan(ID_ANIMATION_DELTA_TIME); + /** X acceleration sensor value in M/s^2 */ public static final float FLOAT_ACCELERATION_X = Utils.asNan(ID_ACCELERATION_X); diff --git a/core/java/com/android/internal/widget/remotecompose/core/SerializableToString.java b/core/java/com/android/internal/widget/remotecompose/core/SerializableToString.java index 79ac789a454e..37896211bcef 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/SerializableToString.java +++ b/core/java/com/android/internal/widget/remotecompose/core/SerializableToString.java @@ -20,5 +20,11 @@ import android.annotation.NonNull; import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer; public interface SerializableToString { + /** + * Returns a stable string representation of an operation + * + * @param indent the indentation for that operation + * @param serializer the serializer object + */ void serializeToString(int indent, @NonNull StringSerializer serializer); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/TouchListener.java b/core/java/com/android/internal/widget/remotecompose/core/TouchListener.java index 611ba97c10cb..0a1be82bc53a 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/TouchListener.java +++ b/core/java/com/android/internal/widget/remotecompose/core/TouchListener.java @@ -15,6 +15,8 @@ */ package com.android.internal.widget.remotecompose.core; +import com.android.internal.widget.remotecompose.core.operations.layout.Component; + /** Interface used by objects to register for touch events */ public interface TouchListener { /** @@ -45,4 +47,11 @@ public interface TouchListener { * @param y the y coord of the drag */ void touchDrag(RemoteContext context, float x, float y); + + /** + * Called after the touch event handler is inflated + * + * @param component component it is under + */ + void setComponent(Component component); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentationBuilder.java b/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentationBuilder.java index 0174ce531d3f..0972e05e39b6 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentationBuilder.java +++ b/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentationBuilder.java @@ -18,11 +18,33 @@ package com.android.internal.widget.remotecompose.core.documentation; import android.annotation.NonNull; public interface DocumentationBuilder { + + /** + * Add arbitrary text to the documentation + * + * @param value + */ void add(@NonNull String value); + /** + * Add the operation to the documentation + * + * @param category category of the operation + * @param id the OPCODE of the operation + * @param name the name of the operation + * @return a DocumentedOperation + */ @NonNull DocumentedOperation operation(@NonNull String category, int id, @NonNull String name); + /** + * Add the operation to the documentation as a Work in Progress (WIP) operation + * + * @param category category of the operation + * @param id the OPCODE of the operation + * @param name the name of the operation + * @return a DocumentedOperation + */ @NonNull DocumentedOperation wipOperation(@NonNull String category, int id, @NonNull String name); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentedCompanionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentedCompanionOperation.java index 2806a5e1ad1d..487ea2ebfacb 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentedCompanionOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentedCompanionOperation.java @@ -18,5 +18,10 @@ package com.android.internal.widget.remotecompose.core.documentation; import android.annotation.NonNull; public interface DocumentedCompanionOperation { + /** + * A callback to populate the documentation of an operation + * + * @param doc the document being built + */ void documentation(@NonNull DocumentationBuilder doc); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentedOperation.java b/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentedOperation.java index bfab62365736..ffe887156e40 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 @@ -49,6 +49,12 @@ public class DocumentedOperation { int mExamplesWidth = 100; int mExamplesHeight = 100; + /** + * Returns the string representation of a field type + * + * @param type + * @return + */ @NonNull public static String getType(int type) { switch (type) { @@ -117,6 +123,11 @@ public class DocumentedOperation { return mVarSize; } + /** + * Returns the size of the operation fields + * + * @return size in bytes + */ public int getSizeFields() { int size = 0; mVarSize = ""; @@ -152,12 +163,29 @@ public class DocumentedOperation { return mExamplesHeight; } + /** + * Document a field of the operation + * + * @param type + * @param name + * @param description + * @return + */ @NonNull public DocumentedOperation field(int type, @NonNull String name, @NonNull String description) { mFields.add(new OperationField(type, name, description)); return this; } + /** + * Document a field of the operation + * + * @param type + * @param name + * @param varSize + * @param description + * @return + */ @NonNull public DocumentedOperation field( int type, @NonNull String name, @NonNull String varSize, @NonNull String description) { @@ -165,6 +193,13 @@ public class DocumentedOperation { return this; } + /** + * Add possible values for the operation field + * + * @param name + * @param value + * @return + */ @NonNull public DocumentedOperation possibleValues(@NonNull String name, int value) { if (!mFields.isEmpty()) { @@ -173,24 +208,50 @@ public class DocumentedOperation { return this; } + /** + * Add a description + * + * @param description + * @return + */ @NonNull public DocumentedOperation description(@NonNull String description) { mDescription = description; return this; } + /** + * Add arbitrary text as examples + * + * @param examples + * @return + */ @NonNull public DocumentedOperation examples(@NonNull String examples) { mTextExamples = examples; return this; } + /** + * Add an example image + * + * @param name the title of the image + * @param imagePath the path of the image + * @return + */ @NonNull public DocumentedOperation exampleImage(@NonNull String name, @NonNull String imagePath) { mExamples.add(new StringPair(name, imagePath)); return this; } + /** + * Add examples with a given size + * + * @param width + * @param height + * @return + */ @NonNull public DocumentedOperation examplesDimension(int width, int height) { mExamplesWidth = width; 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 9febcfe57047..e120f3150a96 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 @@ -61,10 +61,21 @@ public class OperationField { return mPossibleValues; } + /** + * Add possible values for a field + * + * @param name + * @param value + */ public void possibleValue(@NonNull String name, @NonNull String value) { mPossibleValues.add(new StringPair(name, value)); } + /** + * Return true if the field has enumerated values + * + * @return true if enumerated values, false otherwise + */ public boolean hasEnumeratedValues() { return !mPossibleValues.isEmpty(); } @@ -74,6 +85,11 @@ public class OperationField { return mVarSize; } + /** + * Returns the size in byte of the field depending on its type, or -1 if unknown + * + * @return the size in bytes + */ public int getSize() { switch (mType) { case DocumentedOperation.BYTE: 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 4c9602572721..98c2745380d7 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 @@ -135,6 +135,15 @@ public class Header extends Operation implements RemoteComposeOperation { return OP_CODE; } + /** + * Apply the header to the wire buffer + * + * @param buffer + * @param width + * @param height + * @param density + * @param capabilities + */ public static void apply( @NonNull WireBuffer buffer, int width, int height, float density, long capabilities) { buffer.start(OP_CODE); @@ -193,6 +202,11 @@ public class Header extends Operation implements RemoteComposeOperation { .field(LONG, "CAPABILITIES", "Major version"); } + /** + * Set the version on a document + * + * @param document + */ public void setVersion(CoreDocument document) { document.setVersion(mMajorVersion, mMinorVersion, mPatchVersion); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesCreate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesCreate.java new file mode 100644 index 000000000000..9e891c48c065 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesCreate.java @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.widget.remotecompose.core.operations; + +import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT_ARRAY; +import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; +import static com.android.internal.widget.remotecompose.core.operations.utilities.AnimatedFloatExpression.VAR1; + +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; +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.documentation.DocumentedOperation; +import com.android.internal.widget.remotecompose.core.operations.utilities.AnimatedFloatExpression; +import com.android.internal.widget.remotecompose.core.operations.utilities.NanMap; + +import java.util.Arrays; +import java.util.List; + +/** + * This creates a particle system. which consist of id, particleCount, array of id's and equations + * for constructing the particles + */ +public class ParticlesCreate extends Operation implements VariableSupport { + private static final int OP_CODE = Operations.PARTICLE_CREATE; + private static final String CLASS_NAME = "ParticlesCreate"; + private final int mId; + private final float[][] mEquations; + private final float[][] mOutEquations; + private final float[][] mParticles; + private final int[] mIndexeVars; // the elements in mEquations that INDEXES + private final int mParticleCount; + private final int[] mVarId; + private static final int MAX_FLOAT_ARRAY = 2000; + private static final int MAX_EQU_LENGTH = 32; + @NonNull AnimatedFloatExpression mExp = new AnimatedFloatExpression(); + + public ParticlesCreate(int id, int[] varId, float[][] values, int particleCount) { + mId = id; + mVarId = varId; + mEquations = values; + mParticleCount = particleCount; + mOutEquations = new float[values.length][]; + for (int i = 0; i < values.length; i++) { + mOutEquations[i] = new float[values[i].length]; + System.arraycopy(values[i], 0, mOutEquations[i], 0, values[i].length); + } + mParticles = new float[particleCount][varId.length]; + + int[] index = new int[20]; + int indexes = 0; + int var1Int = Float.floatToRawIntBits(VAR1); + for (int j = 0; j < mEquations.length; j++) { + for (int k = 0; k < mEquations[j].length; k++) { + if (Float.isNaN(mEquations[j][k]) + && Float.floatToRawIntBits(mEquations[j][k]) == var1Int) { + index[indexes++] = j * mEquations.length + k; + } + } + } + mIndexeVars = Arrays.copyOf(index, indexes); + } + + @Override + public void updateVariables(@NonNull RemoteContext context) { + for (int i = 0; i < mEquations.length; i++) { + + for (int j = 0; j < mEquations[i].length; j++) { + float v = mEquations[i][j]; + mOutEquations[i][j] = + (Float.isNaN(v) + && !AnimatedFloatExpression.isMathOperator(v) + && !NanMap.isDataVariable(v)) + ? context.getFloat(Utils.idFromNan(v)) + : v; + } + } + } + + @Override + public void registerListening(@NonNull RemoteContext context) { + context.putObject(mId, this); // T + for (int i = 0; i < mEquations.length; i++) { + float[] mEquation = mEquations[i]; + for (float v : mEquation) { + if (Float.isNaN(v) + && !AnimatedFloatExpression.isMathOperator(v) + && !NanMap.isDataVariable(v)) { + context.listensTo(Utils.idFromNan(v), this); + } + } + } + } + + @Override + public void write(@NonNull WireBuffer buffer) { + apply(buffer, mId, mVarId, mEquations, mParticleCount); + } + + @NonNull + @Override + public String toString() { + String str = "ParticlesCreate[" + Utils.idString(mId) + "] "; + for (int j = 0; j < mVarId.length; j++) { + str += "[" + mVarId[j] + "] "; + float[] equation = mEquations[j]; + String[] labels = new String[equation.length]; + for (int i = 0; i < equation.length; i++) { + if (Float.isNaN(equation[i])) { + labels[i] = "[" + Utils.idStringFromNan(equation[i]) + "]"; + } + } + str += AnimatedFloatExpression.toString(equation, labels) + "\n"; + } + + return str; + } + + /** + * Write the operation on the buffer + * + * @param buffer + * @param id + * @param varId + * @param equations + * @param particleCount + */ + public static void apply( + @NonNull WireBuffer buffer, + int id, + @NonNull int[] varId, + @NonNull float[][] equations, + int particleCount) { + buffer.start(OP_CODE); + buffer.writeInt(id); + buffer.writeInt(particleCount); + buffer.writeInt(varId.length); + for (int i = 0; i < varId.length; i++) { + buffer.writeInt(varId[i]); + buffer.writeInt(equations[i].length); + for (int j = 0; j < equations[i].length; j++) { + buffer.writeFloat(equations[i][j]); + } + } + } + + /** + * Read this operation and add it to the list of operations + * + * @param buffer the buffer to read + * @param operations the list of operations that will be added to + */ + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { + int id = buffer.readInt(); + int particleCount = buffer.readInt(); + int varLen = buffer.readInt(); + if (varLen > MAX_FLOAT_ARRAY) { + throw new RuntimeException(varLen + " map entries more than max = " + MAX_FLOAT_ARRAY); + } + int[] varId = new int[varLen]; + float[][] equations = new float[varLen][]; + for (int i = 0; i < varId.length; i++) { + varId[i] = buffer.readInt(); + int equLen = buffer.readInt(); + if (equLen > MAX_EQU_LENGTH) { + throw new RuntimeException( + equLen + " map entries more than max = " + MAX_FLOAT_ARRAY); + } + equations[i] = new float[equLen]; + for (int j = 0; j < equations[i].length; j++) { + equations[i][j] = buffer.readFloat(); + } + } + ParticlesCreate data = new ParticlesCreate(id, varId, equations, particleCount); + operations.add(data); + } + + /** + * Populate the documentation with a description of this operation + * + * @param doc to append the description to. + */ + public static void documentation(@NonNull DocumentationBuilder doc) { + doc.operation("Data Operations", OP_CODE, CLASS_NAME) + .description("Creates a particle system") + .field(DocumentedOperation.INT, "id", "The reference of the particle system") + .field(INT, "particleCount", "number of particles to create") + .field(INT, "varLen", "number of variables asociate with the particles") + .field(FLOAT_ARRAY, "id", "varLen", "id followed by equations") + .field(INT, "equLen", "length of the equation") + .field(FLOAT_ARRAY, "equation", "varLen * equLen", "float array equations"); + } + + @NonNull + @Override + public String deepToString(@NonNull String indent) { + return indent + toString(); + } + + void initializeParticle(int pNo) { + for (int j = 0; j < mParticles[pNo].length; j++) { + for (int k = 0; k < mIndexeVars.length; k++) { + int pos = mIndexeVars[k]; + int jIndex = pos / mOutEquations.length; + int kIndex = pos % mOutEquations.length; + mOutEquations[jIndex][kIndex] = pNo; + } + mParticles[pNo][j] = mExp.eval(mOutEquations[j], mOutEquations[j].length); + } + } + + @Override + public void apply(@NonNull RemoteContext context) { + for (int i = 0; i < mParticles.length; i++) { + initializeParticle(i); + } + } + + public float[][] getParticles() { + return mParticles; + } + + public int[] getVariableIds() { + return mVarId; + } + + public float[][] getEquations() { + return mOutEquations; + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesLoop.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesLoop.java new file mode 100644 index 000000000000..791079070622 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ParticlesLoop.java @@ -0,0 +1,295 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.widget.remotecompose.core.operations; + +import static com.android.internal.widget.remotecompose.core.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.PaintContext; +import com.android.internal.widget.remotecompose.core.PaintOperation; +import com.android.internal.widget.remotecompose.core.RemoteContext; +import com.android.internal.widget.remotecompose.core.VariableSupport; +import com.android.internal.widget.remotecompose.core.WireBuffer; +import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; +import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.Container; +import com.android.internal.widget.remotecompose.core.operations.utilities.AnimatedFloatExpression; +import com.android.internal.widget.remotecompose.core.operations.utilities.NanMap; + +import java.util.ArrayList; +import java.util.List; + +/** + * This provides the mechinism to evolve the particles It consist of a restart equation and a list + * of equations particle restarts if restart equation > 0 + */ +public class ParticlesLoop extends PaintOperation implements VariableSupport, Container { + private static final int OP_CODE = Operations.PARTICLE_LOOP; + private static final String CLASS_NAME = "ParticlesLoop"; + private final int mId; + private final float[] mRestart; + private final float[] mOutRestart; + private final float[][] mEquations; + private final float[][] mOutEquations; + private int[] mVarId; + private float[][] mParticles; + private static final int MAX_FLOAT_ARRAY = 2000; + private static final int MAX_EQU_LENGTH = 32; + ParticlesCreate mParticlesSource; + + @NonNull + @Override + public ArrayList<Operation> getList() { + return mList; + } + + @NonNull private ArrayList<Operation> mList = new ArrayList<>(); + + @NonNull AnimatedFloatExpression mExp = new AnimatedFloatExpression(); + + /** + * Create a new ParticlesLoop operation + * + * @param id of the create + * @param restart the restart equation kills and restart when positive + * @param values the loop equations + */ + public ParticlesLoop(int id, float[] restart, float[][] values) { + mId = id; + mRestart = restart; + if (restart != null) { + mOutRestart = new float[restart.length]; + System.arraycopy(restart, 0, mOutRestart, 0, restart.length); + } else { + mOutRestart = null; + } + + mEquations = values; + mOutEquations = new float[values.length][]; + for (int i = 0; i < values.length; i++) { + mOutEquations[i] = new float[values[i].length]; + System.arraycopy(values[i], 0, mOutEquations[i], 0, values[i].length); + } + } + + @Override + public void updateVariables(@NonNull RemoteContext context) { + if (mOutRestart != null) { + for (int i = 0; i < mRestart.length; i++) { + float v = mRestart[i]; + mOutRestart[i] = + (Float.isNaN(v) + && !AnimatedFloatExpression.isMathOperator(v) + && !NanMap.isDataVariable(v)) + ? context.getFloat(Utils.idFromNan(v)) + : v; + } + } + for (int i = 0; i < mEquations.length; i++) { + float[] mEquation = mEquations[i]; + for (int j = 0; j < mEquation.length; j++) { + float v = mEquation[j]; + mOutEquations[i][j] = + (Float.isNaN(v) + && !AnimatedFloatExpression.isMathOperator(v) + && !NanMap.isDataVariable(v)) + ? context.getFloat(Utils.idFromNan(v)) + : v; + } + } + } + + @Override + public void registerListening(@NonNull RemoteContext context) { + mParticlesSource = (ParticlesCreate) context.getObject(mId); + mParticles = mParticlesSource.getParticles(); + mVarId = mParticlesSource.getVariableIds(); + if (mRestart != null) { + for (int i = 0; i < mRestart.length; i++) { + float v = mRestart[i]; + if (Float.isNaN(v) + && !AnimatedFloatExpression.isMathOperator(v) + && !NanMap.isDataVariable(v)) { + context.listensTo(Utils.idFromNan(v), this); + } + } + } + for (int i = 0; i < mEquations.length; i++) { + float[] mEquation = mEquations[i]; + for (float v : mEquation) { + if (Float.isNaN(v) + && !AnimatedFloatExpression.isMathOperator(v) + && !NanMap.isDataVariable(v)) { + context.listensTo(Utils.idFromNan(v), this); + } + } + } + } + + @Override + public void write(@NonNull WireBuffer buffer) { + apply(buffer, mId, mRestart, mEquations); + } + + @NonNull + @Override + public String toString() { + String str = "ParticlesLoop[" + Utils.idString(mId) + "] "; + return str; + } + + /** + * Write the operation on the buffer + * + * @param buffer + * @param id + * @param restart + * @param equations + */ + public static void apply( + @NonNull WireBuffer buffer, + int id, + @Nullable float[] restart, + @NonNull float[][] equations) { + buffer.start(OP_CODE); + buffer.writeInt(id); + if (restart != null) { + buffer.writeInt(restart.length); + for (int i = 0; i < restart.length; i++) { + buffer.writeFloat(restart[i]); + } + } else { + buffer.writeInt(0); + } + buffer.writeInt(equations.length); + for (int i = 0; i < equations.length; i++) { + buffer.writeInt(equations[i].length); + for (int j = 0; j < equations[i].length; j++) { + buffer.writeFloat(equations[i][j]); + } + } + } + + /** + * Read this operation and add it to the list of operations + * + * @param buffer the buffer to read + * @param operations the list of operations that will be added to + */ + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { + int id = buffer.readInt(); + int restartLen = buffer.readInt(); + float[] restart = null; + if (restartLen > 0) { + restart = new float[restartLen]; + for (int i = 0; i < restartLen; i++) { + restart[i] = buffer.readFloat(); + } + } + + int varLen = buffer.readInt(); + if (varLen > MAX_FLOAT_ARRAY) { + throw new RuntimeException(varLen + " map entries more than max = " + MAX_FLOAT_ARRAY); + } + + float[][] equations = new float[varLen][]; + for (int i = 0; i < varLen; i++) { + + int equLen = buffer.readInt(); + if (equLen > MAX_EQU_LENGTH) { + throw new RuntimeException( + equLen + " map entries more than max = " + MAX_FLOAT_ARRAY); + } + equations[i] = new float[equLen]; + for (int j = 0; j < equations[i].length; j++) { + equations[i][j] = buffer.readFloat(); + } + } + ParticlesLoop data = new ParticlesLoop(id, restart, equations); + operations.add(data); + } + + /** + * Populate the documentation with a description of this operation + * + * @param doc to append the description to. + */ + public static void documentation(@NonNull DocumentationBuilder doc) { + doc.operation("Data Operations", OP_CODE, CLASS_NAME) + .description("This evolves the particles & recycles them") + .field(DocumentedOperation.INT, "id", "id of particle system") + .field( + INT, + "recycleLen", + "the number of floats in restart equeation if 0 no restart") + .field(FLOAT_ARRAY, "values", "recycleLen", "array of floats") + .field(INT, "varLen", "the number of equations to follow") + .field(INT, "equLen", "the number of equations to follow") + .field(FLOAT_ARRAY, "values", "equLen", "floats for the equation"); + } + + @NonNull + @Override + public String deepToString(@NonNull String indent) { + return indent + toString(); + } + + @Override + public void paint(@NonNull PaintContext context) { + RemoteContext remoteContext = context.getContext(); + for (int i = 0; i < mParticles.length; i++) { + // Save the values to context TODO hand code the update + for (int j = 0; j < mParticles[i].length; j++) { + remoteContext.loadFloat(mVarId[j], mParticles[i][j]); + updateVariables(remoteContext); + } + // Evaluate the update function + for (int j = 0; j < mParticles[i].length; j++) { + mParticles[i][j] = mExp.eval(mOutEquations[j], mOutEquations[j].length); + remoteContext.loadFloat(mVarId[j], mParticles[i][j]); + } + // test for reset + if (mOutRestart != null) { + for (int k = 0; k < mRestart.length; k++) { + float v = mRestart[k]; + mOutRestart[k] = + (Float.isNaN(v) + && !AnimatedFloatExpression.isMathOperator(v) + && !NanMap.isDataVariable(v)) + ? remoteContext.getFloat(Utils.idFromNan(v)) + : v; + } + if (mExp.eval(mOutRestart, mOutRestart.length) > 0) { + mParticlesSource.initializeParticle(i); + } + } + + for (Operation op : mList) { + if (op instanceof VariableSupport) { + ((VariableSupport) op).updateVariables(context.getContext()); + } + + remoteContext.incrementOpCount(); + op.apply(context.getContext()); + } + } + } +} 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 55dd88233265..129201c2b2d2 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 @@ -219,6 +219,15 @@ public class RootContentBehavior extends Operation implements RemoteComposeOpera return OP_CODE; } + /** + * Write the operation on the buffer + * + * @param buffer + * @param scroll + * @param alignment + * @param sizing + * @param mode + */ public static void apply( @NonNull WireBuffer buffer, int scroll, int alignment, int sizing, int mode) { buffer.start(OP_CODE); 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 ad86e0f2b1f3..c1ddf63264fa 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 @@ -95,6 +95,12 @@ public class RootContentDescription extends Operation return OP_CODE; } + /** + * Write the operation on the buffer + * + * @param buffer + * @param contentDescription + */ public static void apply(@NonNull WireBuffer buffer, int contentDescription) { buffer.start(Operations.ROOT_CONTENT_DESCRIPTION); buffer.writeInt(contentDescription); 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 e9aae1ebad45..1698ecb9d3fc 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 @@ -92,6 +92,12 @@ public class Theme extends Operation implements RemoteComposeOperation { return OP_CODE; } + /** + * Write the operation on the buffer + * + * @param buffer + * @param theme + */ public static void apply(@NonNull WireBuffer buffer, int theme) { buffer.start(OP_CODE); buffer.writeInt(theme); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java index 3b293bd1b8e0..997628140c46 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java @@ -368,6 +368,7 @@ public class TouchExpression extends Operation implements VariableSupport, Touch * * @param component the component, or null if outside */ + @Override public void setComponent(@Nullable Component component) { mComponent = component; if (mComponent != null) { 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 511858aa2b29..abb7ad8910f9 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 @@ -66,6 +66,12 @@ public class CanvasContent extends Component { return "CANVAS_CONTENT"; } + /** + * Write the operation on the buffer + * + * @param buffer + * @param componentId + */ public static void apply(@NonNull WireBuffer buffer, int componentId) { buffer.start(Operations.LAYOUT_CANVAS_CONTENT); buffer.writeInt(componentId); 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 5f084e938588..110bd3079697 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 @@ -77,6 +77,12 @@ public class ClickModifierOperation extends PaintOperation return CoreSemantics.Mode.MERGE; } + /** + * Animate ripple + * + * @param x starting position x of the ripple + * @param y starting position y of the ripple + */ public void animateRipple(float x, float y) { mAnimateRippleStart = System.currentTimeMillis(); mAnimateRippleX = x; @@ -212,6 +218,11 @@ public class ClickModifierOperation extends PaintOperation return "ClickModifier"; } + /** + * Write the operation on the buffer + * + * @param buffer + */ public static void apply(@NonNull WireBuffer buffer) { buffer.start(OP_CODE); } 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 eee2aabe1d8b..8e733ce1d808 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 @@ -24,6 +24,7 @@ import com.android.internal.widget.remotecompose.core.PaintContext; import com.android.internal.widget.remotecompose.core.PaintOperation; import com.android.internal.widget.remotecompose.core.RemoteContext; import com.android.internal.widget.remotecompose.core.SerializableToString; +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.operations.ComponentValue; @@ -236,6 +237,7 @@ public class Component extends PaintOperation finalizeCreation(); } + /** Callback on component creation TODO: replace with inflate() */ public void finalizeCreation() { for (Operation op : mList) { if (op instanceof Component) { @@ -273,6 +275,11 @@ public class Component extends PaintOperation context.mLastComponent = prev; } + /** + * Add a component value to the component + * + * @param v + */ public void addComponentValue(@NonNull ComponentValue v) { mComponentValues.add(v); } @@ -303,10 +310,10 @@ public class Component extends PaintOperation */ public void inflate() { for (Operation op : mList) { - if (op instanceof TouchExpression) { + if (op instanceof TouchListener) { // Make sure to set the component of a touch expression that belongs to us! - TouchExpression touchExpression = (TouchExpression) op; - touchExpression.setComponent(this); + TouchListener touchListener = (TouchListener) op; + touchListener.setComponent(this); } } } @@ -317,6 +324,11 @@ public class Component extends PaintOperation INVISIBLE } + /** + * Returns true if the component is visible + * + * @return + */ public boolean isVisible() { if (mVisibility != Visibility.VISIBLE || mParent == null) { return mVisibility == Visibility.VISIBLE; @@ -327,6 +339,11 @@ public class Component extends PaintOperation return true; } + /** + * Set the visibility of the component + * + * @param visibility can be VISIBLE, INVISIBLE or GONE + */ public void setVisibility(@NonNull Visibility visibility) { if (visibility != mVisibility || visibility != mScheduledVisibility) { mScheduledVisibility = visibility; @@ -443,6 +460,13 @@ public class Component extends PaintOperation @NonNull public float[] locationInWindow = new float[2]; + /** + * Hit detection -- returns true if the point (x, y) is inside the component + * + * @param x + * @param y + * @return + */ public boolean contains(float x, float y) { locationInWindow[0] = 0f; locationInWindow[1] = 0f; @@ -454,14 +478,32 @@ public class Component extends PaintOperation return x >= lx1 && x < lx2 && y >= ly1 && y < ly2; } + /** + * Returns the horizontal scroll value of the content of this component + * + * @return 0 if no scroll + */ public float getScrollX() { return 0; } + /** + * Returns the vertical scroll value of the content of this component + * + * @return 0 if no scroll + */ public float getScrollY() { return 0; } + /** + * Click handler + * + * @param context + * @param document + * @param x + * @param y + */ public void onClick( @NonNull RemoteContext context, @NonNull CoreDocument document, float x, float y) { if (!contains(x, y)) { @@ -479,6 +521,14 @@ public class Component extends PaintOperation } } + /** + * Touch down handler + * + * @param context + * @param document + * @param x + * @param y + */ public void onTouchDown(RemoteContext context, CoreDocument document, float x, float y) { if (!contains(x, y)) { return; @@ -501,6 +551,17 @@ public class Component extends PaintOperation } } + /** + * Touch Up handler + * + * @param context + * @param document + * @param x + * @param y + * @param dx + * @param dy + * @param force + */ public void onTouchUp( RemoteContext context, CoreDocument document, @@ -529,6 +590,15 @@ public class Component extends PaintOperation } } + /** + * Touch Cancel handler + * + * @param context + * @param document + * @param x + * @param y + * @param force + */ public void onTouchCancel( RemoteContext context, CoreDocument document, float x, float y, boolean force) { if (!force && !contains(x, y)) { @@ -551,6 +621,15 @@ public class Component extends PaintOperation } } + /** + * Touch Drag handler + * + * @param context + * @param document + * @param x + * @param y + * @param force + */ public void onTouchDrag( RemoteContext context, CoreDocument document, float x, float y, boolean force) { if (!force && !contains(x, y)) { @@ -573,6 +652,12 @@ public class Component extends PaintOperation } } + /** + * Returns the location of the component relative to the root component + * + * @param value a 2 dimension float array that will receive the horizontal and vertical position + * of the component. + */ public void getLocationInWindow(@NonNull float[] value) { value[0] += mX; value[1] += mY; @@ -681,6 +766,7 @@ public class Component extends PaintOperation } } + /** Mark the tree as needing a repaint */ public void needsRepaint() { try { getRoot().mNeedsRepaint = true; @@ -689,6 +775,11 @@ public class Component extends PaintOperation } } + /** + * Debugging function returning the list of child operations + * + * @return a formatted string with the list of operations + */ @NonNull public String content() { StringBuilder builder = new StringBuilder(); @@ -700,6 +791,11 @@ public class Component extends PaintOperation return builder.toString(); } + /** + * Returns a string containing the text operations if any + * + * @return + */ @NonNull public String textContent() { StringBuilder builder = new StringBuilder(); @@ -713,6 +809,12 @@ public class Component extends PaintOperation return builder.toString(); } + /** + * Utility debug function + * + * @param component + * @param context + */ public void debugBox(@NonNull Component component, @NonNull PaintContext context) { float width = component.mWidth; float height = component.mHeight; @@ -731,11 +833,22 @@ public class Component extends PaintOperation context.restorePaint(); } + /** + * Set the position of this component relative to its parent + * + * @param x horizontal position + * @param y vertical position + */ public void setLayoutPosition(float x, float y) { this.mX = x; this.mY = y; } + /** + * The vertical position of this component relative to its parent + * + * @return + */ public float getTranslateX() { if (mParent != null) { return mX - mParent.mX; @@ -743,6 +856,11 @@ public class Component extends PaintOperation return 0f; } + /** + * The horizontal position of this component relative to its parent + * + * @return + */ public float getTranslateY() { if (mParent != null) { return mY - mParent.mY; @@ -750,6 +868,11 @@ public class Component extends PaintOperation return 0f; } + /** + * Paint the component itself. + * + * @param context + */ public void paintingComponent(@NonNull PaintContext context) { if (mPreTranslate != null) { mPreTranslate.paint(context); @@ -778,6 +901,12 @@ public class Component extends PaintOperation context.getContext().mLastComponent = prev; } + /** + * If animation is turned on and we need to be animated, we'll apply it. + * + * @param context + * @return + */ public boolean applyAnimationAsNeeded(@NonNull PaintContext context) { if (context.isAnimationEnabled() && mAnimateMeasure != null) { mAnimateMeasure.paint(context); @@ -822,6 +951,11 @@ public class Component extends PaintOperation paintingComponent(context); } + /** + * Extract child components + * + * @param components an ArrayList that will be populated by child components (if any) + */ public void getComponents(@NonNull ArrayList<Component> components) { for (Operation op : mList) { if (op instanceof Component) { @@ -830,6 +964,11 @@ public class Component extends PaintOperation } } + /** + * Extract child TextData elements + * + * @param data an ArrayList that will be populated with the TextData elements (if any) + */ public void getData(@NonNull ArrayList<TextData> data) { for (Operation op : mList) { if (op instanceof TextData) { @@ -838,6 +977,11 @@ public class Component extends PaintOperation } } + /** + * Returns the number of children components + * + * @return + */ public int getComponentCount() { int count = 0; for (Operation op : mList) { @@ -848,6 +992,12 @@ public class Component extends PaintOperation return count; } + /** + * Return the id used for painting the component -- either its component id or its animation id + * (if set) + * + * @return + */ public int getPaintId() { if (mAnimationId != -1) { return mAnimationId; @@ -855,10 +1005,21 @@ public class Component extends PaintOperation return mComponentId; } + /** + * Return true if the needsRepaint flag is set on this component + * + * @return + */ public boolean doesNeedsRepaint() { return mNeedsRepaint; } + /** + * Utility function to return a component from its id + * + * @param cid + * @return + */ @Nullable public Component getComponent(int cid) { if (mComponentId == cid || mAnimationId == cid) { 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 f009d8801159..5ef71d006107 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 @@ -126,6 +126,12 @@ public class ComponentStart extends Operation implements Container { public static final int LAYOUT_ROW = 15; public static final int LAYOUT_COLUMN = 16; + /** + * Returns the string representation of the component type + * + * @param type + * @return a string representing the component type + */ @NonNull public static String typeDescription(int type) { switch (type) { @@ -179,6 +185,15 @@ public class ComponentStart extends Operation implements Container { return Operations.COMPONENT_START; } + /** + * Write the operation on the buffer + * + * @param buffer + * @param type + * @param componentId + * @param width + * @param height + */ public static void apply( @NonNull WireBuffer buffer, int type, int componentId, float width, float height) { buffer.start(Operations.COMPONENT_START); @@ -188,6 +203,11 @@ public class ComponentStart extends Operation implements Container { buffer.writeFloat(height); } + /** + * Return the size of the operation in byte + * + * @return the size in byte + */ public static int size() { return 1 + 4 + 4 + 4; } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Container.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Container.java index c678f6c22cef..41e2bf801009 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Container.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Container.java @@ -21,7 +21,13 @@ import com.android.internal.widget.remotecompose.core.Operation; import java.util.ArrayList; +/** An Operation container */ public interface Container { + /** + * Returns a list of child operations + * + * @return a list of child operations + */ @NonNull ArrayList<Operation> getList(); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ContainerEnd.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ContainerEnd.java index 4290c4bc3c2b..004f19407c90 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ContainerEnd.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ContainerEnd.java @@ -68,6 +68,11 @@ public class ContainerEnd extends Operation { return Operations.CONTAINER_END; } + /** + * Write the operation on the buffer + * + * @param buffer + */ public static void apply(@NonNull WireBuffer buffer) { buffer.start(id()); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseOperation.java new file mode 100644 index 000000000000..e277d49325be --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseOperation.java @@ -0,0 +1,231 @@ +/* + * 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 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.PaintOperation; +import com.android.internal.widget.remotecompose.core.RemoteContext; +import com.android.internal.widget.remotecompose.core.VariableSupport; +import com.android.internal.widget.remotecompose.core.WireBuffer; +import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; +import com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation; +import com.android.internal.widget.remotecompose.core.operations.Utils; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a Impulse Event To trigger an impulse event. set the startAt time to the + * context.getAnimationTime() Impluse Operation. This operation execute a list of actions once and + * the impluseProcess is executed for a fixed duration + */ +public class ImpulseOperation extends PaintOperation implements VariableSupport, Container { + private static final int OP_CODE = Operations.IMPULSE_START; + private static final String CLASS_NAME = "ImpulseOperation"; + private float mDuration; + private float mStartAt; + private float mOutDuration; + private float mOutStartAt; + private boolean mInitialPass = true; + @NonNull public ArrayList<Operation> mList = new ArrayList<>(); + + int mIndexVariableId; + + private ImpulseProcess mProcess; + + /** + * Constructor for a Impulse Operation + * + * @param duration the duration of the impluse + * @param startAt the start time of the impluse + */ + public ImpulseOperation(float duration, float startAt) { + mDuration = duration; + mStartAt = startAt; + mOutStartAt = startAt; + mOutDuration = duration; + } + + @Override + public void registerListening(RemoteContext context) { + if (mProcess == null) { + System.out.println("....."); + Operation last = mList.get(mList.size() - 1); + if (last instanceof ImpulseProcess) { + mProcess = (ImpulseProcess) last; + mList.remove(last); + } + } + if (Float.isNaN(mStartAt)) { + context.listensTo(Utils.idFromNan(mStartAt), this); + } + if (Float.isNaN(mDuration)) { + context.listensTo(Utils.idFromNan(mDuration), this); + } + for (Operation operation : mList) { + if (operation instanceof VariableSupport) { + VariableSupport variableSupport = (VariableSupport) operation; + variableSupport.registerListening(context); + } + } + if (mProcess != null) { + mProcess.registerListening(context); + } + } + + @Override + public void updateVariables(RemoteContext context) { + + mOutDuration = + Float.isNaN(mDuration) ? context.getFloat(Utils.idFromNan(mDuration)) : mDuration; + + mOutStartAt = + Float.isNaN(mStartAt) ? context.getFloat(Utils.idFromNan(mStartAt)) : mStartAt; + if (mProcess != null) { + mProcess.updateVariables(context); + } + } + + @NonNull + public ArrayList<Operation> getList() { + return mList; + } + + @Override + public void write(@NonNull WireBuffer buffer) { + apply(buffer, mDuration, mStartAt); + } + + @NonNull + @Override + public String toString() { + StringBuilder builder = new StringBuilder("LoopOperation\n"); + for (Operation operation : mList) { + builder.append(" startAt: "); + builder.append(mStartAt); + builder.append(" duration: "); + builder.append(mDuration); + builder.append("\n"); + } + return builder.toString(); + } + + @NonNull + @Override + public String deepToString(@NonNull String indent) { + return (indent != null ? indent : "") + toString(); + } + + @Override + public void paint(@NonNull PaintContext context) { + RemoteContext remoteContext = context.getContext(); + + if (remoteContext.getAnimationTime() < mOutStartAt + mOutDuration) { + if (mInitialPass) { + for (Operation op : mList) { + if (op instanceof VariableSupport && op.isDirty()) { + ((VariableSupport) op).updateVariables(context.getContext()); + } + remoteContext.incrementOpCount(); + op.apply(context.getContext()); + } + mInitialPass = false; + } else { + remoteContext.incrementOpCount(); + if (mProcess != null) { + mProcess.paint(context); + } + } + } else { + mInitialPass = true; + } + } + + /** + * The name of the class + * + * @return the name + */ + @NonNull + public static String name() { + return CLASS_NAME; + } + + /** + * Write the operation on the buffer + * + * @param buffer + * @param duration + * @param startAt + */ + public static void apply(@NonNull WireBuffer buffer, float duration, float startAt) { + buffer.start(OP_CODE); + buffer.writeFloat(duration); + buffer.writeFloat(startAt); + } + + /** + * Read this operation and add it to the list of operations + * + * @param buffer the buffer to read + * @param operations the list of operations that will be added to + */ + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { + float duration = buffer.readFloat(); + float startAt = buffer.readFloat(); + + operations.add(new ImpulseOperation(duration, startAt)); + } + + /** + * Populate the documentation with a description of this operation + * + * @param doc to append the description to. + */ + public static void documentation(@NonNull DocumentationBuilder doc) { + doc.operation("Operations", OP_CODE, name()) + .description( + "Impulse Operation. This operation execute a list of action for a fixed" + + " duration") + .field(DocumentedOperation.FLOAT, "duration", "How long to last") + .field(DocumentedOperation.FLOAT, "startAt", "value step"); + } + + /** + * Calculate and estimate of the number of iterations + * + * @return number of loops or 10 if based on variables + */ + public int estimateIterations() { + if (Float.isNaN(mDuration)) { + return 10; // this is a generic estmate if the values are variables; + } + return (int) (mDuration * 60); + } + + /** + * set the impulse process. This gets executed for the duration of the impulse + * + * @param impulseProcess process to be executed every time + */ + public void setProcess(ImpulseProcess impulseProcess) { + mProcess = impulseProcess; + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseProcess.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseProcess.java new file mode 100644 index 000000000000..f896f3d8ee9c --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ImpulseProcess.java @@ -0,0 +1,154 @@ +/* + * 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 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.PaintOperation; +import com.android.internal.widget.remotecompose.core.RemoteContext; +import com.android.internal.widget.remotecompose.core.VariableSupport; +import com.android.internal.widget.remotecompose.core.WireBuffer; +import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; + +import java.util.ArrayList; +import java.util.List; + +/** Represents the repeating part of an Impulse. */ +public class ImpulseProcess extends PaintOperation implements VariableSupport, Container { + private static final int OP_CODE = Operations.IMPULSE_PROCESS; + private static final String CLASS_NAME = "ImpulseProcess"; + + @NonNull public ArrayList<Operation> mList = new ArrayList<>(); + + /** The constructor */ + public ImpulseProcess() {} + + @Override + public void registerListening(RemoteContext context) { + for (Operation operation : mList) { + if (operation instanceof VariableSupport) { + VariableSupport variableSupport = (VariableSupport) operation; + variableSupport.registerListening(context); + } + } + } + + @Override + public void updateVariables(RemoteContext context) { + for (Operation operation : mList) { + if (operation instanceof VariableSupport) { + VariableSupport variableSupport = (VariableSupport) operation; + variableSupport.updateVariables(context); + } + } + } + + /** + * The returns a list to be filled + * + * @return list to be filled + */ + @NonNull + public ArrayList<Operation> getList() { + return mList; + } + + @Override + public void write(@NonNull WireBuffer buffer) { + apply(buffer); + } + + @NonNull + @Override + public String toString() { + StringBuilder builder = new StringBuilder(CLASS_NAME + "\n"); + for (Operation operation : mList) { + builder.append(" "); + builder.append(operation); + builder.append("\n"); + } + return builder.toString(); + } + + @NonNull + @Override + public String deepToString(@NonNull String indent) { + return (indent != null ? indent : "") + toString(); + } + + @Override + public void paint(@NonNull PaintContext context) { + RemoteContext remoteContext = context.getContext(); + for (Operation op : mList) { + if (op instanceof VariableSupport && op.isDirty()) { + ((VariableSupport) op).updateVariables(context.getContext()); + } + remoteContext.incrementOpCount(); + op.apply(context.getContext()); + } + } + + /** + * The name of the class + * + * @return the name + */ + @NonNull + public static String name() { + return "Loop"; + } + + /** + * Apply this operation to the buffer + * + * @param buffer + */ + public static void apply(@NonNull WireBuffer buffer) { + buffer.start(OP_CODE); + } + + /** + * Read this operation and add it to the list of operations + * + * @param buffer the buffer to read + * @param operations the list of operations that will be added to + */ + public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) { + operations.add(new ImpulseProcess()); + } + + /** + * Populate the documentation with a description of this operation + * + * @param doc to append the description to. + */ + public static void documentation(@NonNull DocumentationBuilder doc) { + doc.operation("Operations", OP_CODE, name()) + .description("Impulse Process that runs a list of operations"); + } + + /** + * Calculate and estimate of the number of iterations + * + * @return number of loops or 10 if based on variables + */ + public int estimateIterations() { + return 1; + } +} 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 91038852573e..e71cb9a51830 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 @@ -22,6 +22,7 @@ import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.OperationInterface; import com.android.internal.widget.remotecompose.core.PaintContext; import com.android.internal.widget.remotecompose.core.RemoteContext; +import com.android.internal.widget.remotecompose.core.TouchListener; import com.android.internal.widget.remotecompose.core.VariableSupport; import com.android.internal.widget.remotecompose.core.operations.BitmapData; import com.android.internal.widget.remotecompose.core.operations.FloatExpression; @@ -176,8 +177,8 @@ public class LayoutComponent extends Component { || (op instanceof PaintData) || (op instanceof FloatExpression)) { supportedOperations.add(op); - if (op instanceof TouchExpression) { - ((TouchExpression) op).setComponent(this); + if (op instanceof TouchListener) { + ((TouchListener) op).setComponent(this); } } else { // nothing 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 27172aa7672c..4babe5f036a5 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 @@ -66,6 +66,12 @@ public class LayoutComponentContent extends Component { return "CONTENT"; } + /** + * Write the operation on the buffer + * + * @param buffer + * @param componentId + */ public static void apply(@NonNull WireBuffer buffer, int componentId) { buffer.start(Operations.LAYOUT_CONTENT); buffer.writeInt(componentId); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ListActionsOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ListActionsOperation.java index 6dce6f115572..bfa417e87637 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ListActionsOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ListActionsOperation.java @@ -88,6 +88,18 @@ public abstract class ListActionsOperation extends PaintOperation } } + /** + * Execute the list of actions + * + * @param context the RemoteContext + * @param document the current document + * @param component the component we belong to + * @param x the x touch down coordinate + * @param y the y touch down coordinate + * @param force if true, will apply the actions even if the component is not visible / not + * containing the touch down coordinates + * @return true if we applied the actions, false otherwise + */ public boolean applyActions( RemoteContext context, CoreDocument document, 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 f5954eea04af..0f4cf562eae6 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 @@ -33,6 +33,7 @@ import java.util.List; /** Represents a loop of operations */ public class LoopOperation extends PaintOperation implements Container, VariableSupport { + private static final int OP_CODE = Operations.LOOP_START; @NonNull public ArrayList<Operation> mList = new ArrayList<>(); @@ -77,6 +78,7 @@ public class LoopOperation extends PaintOperation implements Container, Variable mIndexVariableId = indexId; } + @Override @NonNull public ArrayList<Operation> getList() { return mList; @@ -139,6 +141,15 @@ public class LoopOperation extends PaintOperation implements Container, Variable return "Loop"; } + /** + * Write the operation on the buffer + * + * @param buffer + * @param indexId + * @param from + * @param step + * @param until + */ public static void apply( @NonNull WireBuffer buffer, int indexId, float from, float step, float until) { buffer.start(OP_CODE); 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 baff5ee488a7..f94cda21aca7 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 @@ -174,6 +174,11 @@ public class RootLayoutComponent extends Component { context.restore(); } + /** + * Display the component hierarchy + * + * @return a formatted string containing the component hierarchy + */ @NonNull public String displayHierarchy() { StringSerializer serializer = new StringSerializer(); @@ -181,6 +186,13 @@ public class RootLayoutComponent extends Component { return serializer.toString(); } + /** + * Display the component hierarchy + * + * @param component the current component + * @param indent the current indentation level + * @param serializer the serializer we write to + */ public void displayHierarchy( @NonNull Component component, int indent, @NonNull StringSerializer serializer) { component.serializeToString(indent, serializer); @@ -214,6 +226,12 @@ public class RootLayoutComponent extends Component { return Operations.LAYOUT_ROOT; } + /** + * Write the operation on the buffer + * + * @param buffer + * @param componentId + */ public static void apply(@NonNull WireBuffer buffer, int componentId) { buffer.start(Operations.LAYOUT_ROOT); buffer.writeInt(componentId); @@ -249,6 +267,11 @@ public class RootLayoutComponent extends Component { apply(buffer, mComponentId); } + /** + * Returns true if we have components with a touch listener + * + * @return true if listeners, false otherwise + */ public boolean hasTouchListeners() { return mHasTouchListeners; } 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 842c9c161aee..96e29cdd01ab 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 @@ -40,6 +40,11 @@ public class DebugLog { } } + /** + * Add a node to the current node + * + * @param node + */ public void add(@NonNull Node node) { list.add(node); } @@ -54,23 +59,35 @@ public class DebugLog { @NonNull public static Node node = new Node(null, "Root"); @NonNull public static Node currentNode = node; + /** clear the current logging */ public static void clear() { node = new Node(null, "Root"); currentNode = node; } + /** + * start a node + * + * @param valueSupplier + */ public static void s(@NonNull StringValueSupplier valueSupplier) { if (DEBUG_LAYOUT_ON) { currentNode = new Node(currentNode, valueSupplier.getString()); } } + /** + * arbitrary log statement + * + * @param valueSupplier + */ public static void log(@NonNull StringValueSupplier valueSupplier) { if (DEBUG_LAYOUT_ON) { new LogNode(currentNode, valueSupplier.getString()); } } + /** end a node */ public static void e() { if (DEBUG_LAYOUT_ON) { if (currentNode.parent != null) { @@ -81,6 +98,11 @@ public class DebugLog { } } + /** + * end a node + * + * @param valueSupplier + */ public static void e(@NonNull StringValueSupplier valueSupplier) { if (DEBUG_LAYOUT_ON) { currentNode.endString = valueSupplier.getString(); @@ -92,6 +114,13 @@ public class DebugLog { } } + /** + * print a given node + * + * @param indent + * @param node + * @param builder + */ public static void printNode(int indent, @NonNull Node node, @NonNull StringBuilder builder) { if (DEBUG_LAYOUT_ON) { StringBuilder indentationBuilder = new StringBuilder(); @@ -121,6 +150,7 @@ public class DebugLog { } } + /** Output the captured log to System.out */ public static void display() { if (DEBUG_LAYOUT_ON) { StringBuilder builder = new StringBuilder(); diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/touch/VelocityEasing.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/touch/VelocityEasing.java index c7e2442221cd..56a041026c1f 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/touch/VelocityEasing.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/touch/VelocityEasing.java @@ -108,14 +108,14 @@ public class VelocityEasing { return mStage[i].getPos(t); } } - var ret = (float) getEasing((t - mStage[lastStages].mStartTime)); + float ret = (float) getEasing((t - mStage[lastStages].mStartTime)); ret += mStage[lastStages].mStartPos; return ret; } @Override public String toString() { - var s = " "; + String s = " "; for (int i = 0; i < mNumberOfStages; i++) { Stage stage = mStage[i]; s += " $i $stage"; diff --git a/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibilityModifier.java b/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibilityModifier.java index cd8b7b865cd2..098c4c3190e5 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibilityModifier.java +++ b/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibilityModifier.java @@ -17,7 +17,20 @@ package com.android.internal.widget.remotecompose.core.semantics; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ModifierOperation; -/** A Modifier that provides semantic info. */ +/** + * A Modifier that provides semantic info. + * + * <p>This is needed since `AccessibilityModifier` is generally an open set and designed to be + * extended. + */ public interface AccessibilityModifier extends ModifierOperation, AccessibleComponent { + /** + * This method retrieves the operation code. + * + * <p>This function is used to get the current operation code associated with the object or + * context this method belongs to. + * + * @return The operation code as an integer. + */ int getOpCode(); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibleComponent.java b/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibleComponent.java index e07fc4d9a8f8..cc6c2a6ac7a9 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibleComponent.java +++ b/core/java/com/android/internal/widget/remotecompose/core/semantics/AccessibleComponent.java @@ -17,29 +17,100 @@ package com.android.internal.widget.remotecompose.core.semantics; import android.annotation.Nullable; +/** + * Interface representing an accessible component in the UI. This interface defines properties and + * methods related to accessibility semantics for a component. It extends the {@link + * AccessibilitySemantics} interface to inherit semantic properties. + * + * <p>This is similar to {@link CoreSemantics} but handles built in operations that also expose + * those core semantics. + */ public interface AccessibleComponent extends AccessibilitySemantics { + /** + * Returns the ID of the content description for this item. + * + * <p>The content description is used to provide textual information about the item to + * accessibility services, such as screen readers. This allows users with visual impairments to + * understand the purpose and content of the item. + * + * <p>This is similar to AccessibilityNodeInfo.getContentDescription(). + * + * @return The ID of a RemoteString for the content description, or null if no content + * description is provided. + */ default @Nullable Integer getContentDescriptionId() { return null; } + /** + * Gets the text ID associated with this object. + * + * <p>This method is intended to be overridden by subclasses that need to associate a specific + * text ID with themselves. The default implementation returns null, indicating that no text ID + * is associated with the object. + * + * <p>This is similar to AccessibilityNodeInfo.getText(). + * + * @return The text ID, or null if no text ID is associated with this object. + */ default @Nullable Integer getTextId() { return null; } + /** + * Retrieves the role associated with this object. The enum type deliberately matches the + * Compose Role. In the platform it will be applied as ROLE_DESCRIPTION_KEY. + * + * <p>The default implementation returns {@code null}, indicating that no role is assigned. + * + * @return The role associated with this object, or {@code null} if no role is assigned. + */ default @Nullable Role getRole() { return null; } + /** + * Checks if the element is clickable. + * + * <p>By default, elements are not considered clickable. Subclasses should override this method + * to indicate clickability based on their specific properties and behavior. + * + * <p>This is similar to AccessibilityNodeInfo.isClickable(). + * + * @return {@code true} if the element is clickable, {@code false} otherwise. + */ default boolean isClickable() { return false; } + /** + * Gets the merge mode of the operation. + * + * <p>The mode indicates the type of operation being performed. By default it returns {@link + * CoreSemantics.Mode#SET}, indicating a "set" operation. + * + * <p>{@link CoreSemantics.Mode#CLEAR_AND_SET} matches a Compose modifier of + * `Modifier.clearAndSetSemantics {}` + * + * <p>{@link CoreSemantics.Mode#MERGE} matches a Compose modifier of + * `Modifier.semantics(mergeDescendants = true) {}` + * + * @return The mode of the operation, which defaults to {@link CoreSemantics.Mode#SET}. + */ default CoreSemantics.Mode getMode() { return CoreSemantics.Mode.SET; } - // Our master list - // https://developer.android.com/reference/kotlin/androidx/compose/ui/semantics/Role + /** + * Represents the role of an accessible component. + * + * <p>The enum type deliberately matches the Compose Role. In the platform it will be applied as + * ROLE_DESCRIPTION_KEY. + * + * @link <a + * href="https://developer.android.com/reference/androidx/compose/ui/semantics/Role">Compose + * Semantics Role</a> + */ enum Role { BUTTON("Button"), CHECKBOX("Checkbox"), @@ -70,4 +141,21 @@ public interface AccessibleComponent extends AccessibilitySemantics { return Role.UNKNOWN; } } + + /** + * Defines the merge mode of an element in the semantic tree. + * + * <p>{@link CoreSemantics.Mode#CLEAR_AND_SET} matches a Compose modifier of + * `Modifier.clearAndSetSemantics {}` + * + * <p>{@link CoreSemantics.Mode#MERGE} matches a Compose modifier of + * `Modifier.semantics(mergeDescendants = true) {}` + * + * <p>{@link CoreSemantics.Mode#SET} adds or overrides semantics on an element. + */ + enum Mode { + SET, + CLEAR_AND_SET, + MERGE + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/semantics/CoreSemantics.java b/core/java/com/android/internal/widget/remotecompose/core/semantics/CoreSemantics.java index 4047dd27d163..5b35ee5fc7ed 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/semantics/CoreSemantics.java +++ b/core/java/com/android/internal/widget/remotecompose/core/semantics/CoreSemantics.java @@ -118,16 +118,22 @@ public class CoreSemantics extends Operation implements AccessibilityModifier { return indent + this; } - @NonNull - public String serializedName() { - return "SEMANTICS"; - } - @Override public void serializeToString(int indent, @NonNull StringSerializer serializer) { - serializer.append(indent, serializedName() + " = " + this); + serializer.append(indent, "SEMANTICS" + " = " + this); } + /** + * Reads a CoreSemantics object from a WireBuffer and adds it to a list of operations. + * + * <p>This method reads the data required to construct a CoreSemantics object from the provided + * WireBuffer. After reading and constructing the CoreSemantics object, it is added to the + * provided list of operations. + * + * @param buffer The WireBuffer to read data from. + * @param operations The list of operations to which the read CoreSemantics object will be + * added. + */ public static void read(WireBuffer buffer, List<Operation> operations) { CoreSemantics semantics = new CoreSemantics(); @@ -148,10 +154,4 @@ public class CoreSemantics extends Operation implements AccessibilityModifier { public @Nullable Integer getTextId() { return mTextId != 0 ? mTextId : null; } - - public enum Mode { - SET, - CLEAR_AND_SET, - MERGE - } } diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java index da65a9cf5cc9..e1a01a6b1efa 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 @@ -47,6 +47,7 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta Point mActionDownPoint = new Point(0, 0); AndroidRemoteContext mARContext = new AndroidRemoteContext(); float mDensity = 1f; + long mStart = System.nanoTime(); long mLastFrameDelay = 1; float mMaxFrameRate = 60f; // frames per seconds @@ -109,6 +110,7 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta setContentDescription(mDocument.getDocument().getContentDescription()); updateClickAreas(); requestLayout(); + mARContext.loadFloat(RemoteContext.ID_TOUCH_EVENT_TIME, -Float.MAX_VALUE); invalidate(); } @@ -337,6 +339,8 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta mActionDownPoint.y = (int) event.getY(); CoreDocument doc = mDocument.getDocument(); if (doc.hasTouchListener()) { + mARContext.loadFloat( + RemoteContext.ID_TOUCH_EVENT_TIME, mARContext.getAnimationTime()); mInActionDown = true; if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); @@ -368,6 +372,8 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta performClick(); doc = mDocument.getDocument(); if (doc.hasTouchListener()) { + mARContext.loadFloat( + RemoteContext.ID_TOUCH_EVENT_TIME, mARContext.getAnimationTime()); mVelocityTracker.computeCurrentVelocity(1000); float dx = mVelocityTracker.getXVelocity(pointerId); float dy = mVelocityTracker.getYVelocity(pointerId); @@ -380,6 +386,8 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta case MotionEvent.ACTION_MOVE: if (mInActionDown) { if (mVelocityTracker != null) { + mARContext.loadFloat( + RemoteContext.ID_TOUCH_EVENT_TIME, mARContext.getAnimationTime()); mVelocityTracker.addMovement(event); doc = mDocument.getDocument(); boolean repaint = doc.touchDrag(mARContext, event.getX(), event.getY()); @@ -453,7 +461,8 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta private int mCount; private long mTime = System.nanoTime(); private long mDuration; - private boolean mEvalTime = false; + private boolean mEvalTime = false; // turn on to measure eval time + private float mLastAnimationTime = 0.1f; // set to random non 0 number /** * This returns the amount of time in ms the player used to evalueate a pass it is averaged over @@ -480,7 +489,18 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta if (mDocument == null) { return; } - long start = mEvalTime ? System.nanoTime() : 0; + long start = mEvalTime ? System.nanoTime() : 0; // measure execut of commands + + float animationTime = (System.nanoTime() - mStart) * 1E-9f; + mARContext.setAnimationTime(animationTime); + mARContext.loadFloat(RemoteContext.ID_ANIMATION_TIME, animationTime); + mARContext.loadFloat( + RemoteContext.ID_ANIMATION_DELTA_TIME, animationTime - mLastAnimationTime); + mLastAnimationTime = animationTime; + mARContext.setAnimationEnabled(true); + mARContext.currentTime = System.currentTimeMillis(); + mARContext.setDebug(mDebug); + float density = getContext().getResources().getDisplayMetrics().density; mARContext.useCanvas(canvas); mARContext.mWidth = getWidth(); mARContext.mHeight = getHeight(); |