diff options
author | 2025-04-09 18:36:32 -0700 | |
---|---|---|
committer | 2025-04-09 18:36:32 -0700 | |
commit | ea39268231d379acaf5ed243ea64f5ba37c638c3 (patch) | |
tree | e8f5a908364277300d0146557fbfde73519adfee | |
parent | 9fe1eb72ce6342f8fb88201fa42db3a7ef8539ee (diff) | |
parent | 03c576b78ba829e98efcc3aa32903e42fcabf251 (diff) |
Merge cherrypicks of ['googleplex-android-review.googlesource.com/32772320', 'googleplex-android-review.googlesource.com/32888719'] into 25Q2-release.
Change-Id: Iab3b5b1fd7f0832d9d450ab69d17df488736a113
29 files changed, 1394 insertions, 443 deletions
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 d62538b6d1ed..ab18f4209377 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java +++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.remotecompose.core.operations.BitmapData; import com.android.internal.widget.remotecompose.core.operations.ComponentValue; +import com.android.internal.widget.remotecompose.core.operations.DataListFloat; import com.android.internal.widget.remotecompose.core.operations.DrawContent; import com.android.internal.widget.remotecompose.core.operations.FloatConstant; import com.android.internal.widget.remotecompose.core.operations.FloatExpression; @@ -31,7 +32,6 @@ import com.android.internal.widget.remotecompose.core.operations.RootContentBeha import com.android.internal.widget.remotecompose.core.operations.ShaderData; import com.android.internal.widget.remotecompose.core.operations.TextData; import com.android.internal.widget.remotecompose.core.operations.Theme; -import com.android.internal.widget.remotecompose.core.operations.Utils; import com.android.internal.widget.remotecompose.core.operations.layout.CanvasOperations; import com.android.internal.widget.remotecompose.core.operations.layout.Component; import com.android.internal.widget.remotecompose.core.operations.layout.Container; @@ -64,16 +64,16 @@ public class CoreDocument implements Serializable { private static final boolean DEBUG = false; // Semantic version - public static final int MAJOR_VERSION = 0; - public static final int MINOR_VERSION = 4; + public static final int MAJOR_VERSION = 1; + public static final int MINOR_VERSION = 0; public static final int PATCH_VERSION = 0; // Internal version level - public static final int DOCUMENT_API_LEVEL = 4; + public static final int DOCUMENT_API_LEVEL = 6; // We also keep a more fine-grained BUILD number, exposed as // ID_API_LEVEL = DOCUMENT_API_LEVEL + BUILD - static final float BUILD = 0.8f; + static final float BUILD = 0.0f; private static final boolean UPDATE_VARIABLES_BEFORE_LAYOUT = false; @@ -113,6 +113,7 @@ public class CoreDocument implements Serializable { private IntMap<Object> mDocProperties; boolean mFirstPaint = true; + private boolean mIsUpdateDoc = false; /** Returns a version number that is monotonically increasing. */ public static int getDocumentApiLevel() { @@ -460,7 +461,8 @@ public class CoreDocument implements Serializable { HashMap<Integer, FloatConstant> fltData = new HashMap<Integer, FloatConstant>(); HashMap<Integer, IntegerConstant> intData = new HashMap<Integer, IntegerConstant>(); HashMap<Integer, LongConstant> longData = new HashMap<Integer, LongConstant>(); - recursiveTreverse( + HashMap<Integer, DataListFloat> floatListData = new HashMap<Integer, DataListFloat>(); + recursiveTraverse( mOperations, (op) -> { if (op instanceof TextData) { @@ -478,10 +480,13 @@ public class CoreDocument implements Serializable { } else if (op instanceof LongConstant) { LongConstant d = (LongConstant) op; longData.put(d.mId, d); + } else if (op instanceof DataListFloat) { + DataListFloat d = (DataListFloat) op; + floatListData.put(d.mId, d); } }); - recursiveTreverse( + recursiveTraverse( delta.mOperations, (op) -> { if (op instanceof TextData) { @@ -489,7 +494,6 @@ public class CoreDocument implements Serializable { TextData txtInDoc = txtData.get(t.mTextId); if (txtInDoc != null) { txtInDoc.update(t); - Utils.log("update" + t.mText); txtInDoc.markDirty(); } } else if (op instanceof BitmapData) { @@ -520,6 +524,13 @@ public class CoreDocument implements Serializable { longInDoc.update(lc); longInDoc.markDirty(); } + } else if (op instanceof DataListFloat) { + DataListFloat lc = (DataListFloat) op; + DataListFloat longInDoc = floatListData.get(lc.mId); + if (longInDoc != null) { + longInDoc.update(lc); + longInDoc.markDirty(); + } } }); } @@ -528,10 +539,10 @@ public class CoreDocument implements Serializable { void visit(Operation op); } - private void recursiveTreverse(ArrayList<Operation> mOperations, Visitor visitor) { + private void recursiveTraverse(ArrayList<Operation> mOperations, Visitor visitor) { for (Operation op : mOperations) { if (op instanceof Container) { - recursiveTreverse(((Component) op).mList, visitor); + recursiveTraverse(((Container) op).getList(), visitor); } visitor.visit(op); } @@ -640,6 +651,32 @@ public class CoreDocument implements Serializable { this.minor = minor; this.patchLevel = patchLevel; } + + /** + * Returns true if the document has been encoded for at least the given version MAJOR.MINOR + * + * @param major major version number + * @param minor minor version number + * @param patch patch version number + * @return true if the document was written at least with the given version + */ + public boolean supportsVersion(int major, int minor, int patch) { + if (major > this.major) { + return false; + } + if (major < this.major) { + return true; + } + // major is the same + if (minor > this.minor) { + return false; + } + if (minor < this.minor) { + return true; + } + // minor is the same + return patch <= this.patchLevel; + } } public static class ClickAreaRepresentation { @@ -935,12 +972,20 @@ public class CoreDocument implements Serializable { /** * Returns true if the document can be displayed given this version of the player * - * @param majorVersion the max major version supported by the player - * @param minorVersion the max minor version supported by the player + * @param playerMajorVersion the max major version supported by the player + * @param playerMinorVersion the max minor version supported by the player * @param capabilities a bitmask of capabilities the player supports (unused for now) */ - public boolean canBeDisplayed(int majorVersion, int minorVersion, long capabilities) { - return mVersion.major <= majorVersion && mVersion.minor <= minorVersion; + public boolean canBeDisplayed( + int playerMajorVersion, int playerMinorVersion, long capabilities) { + if (mVersion.major < playerMajorVersion) { + return true; + } + if (mVersion.major > playerMajorVersion) { + return false; + } + // same major version + return mVersion.minor <= playerMinorVersion; } /** @@ -1182,29 +1227,23 @@ public class CoreDocument implements Serializable { * @return array of named variables or null */ public String[] getNamedVariables(int type) { - int count = 0; - for (Operation op : mOperations) { + ArrayList<String> ret = new ArrayList<>(); + getNamedVars(type, mOperations, ret); + return ret.toArray(new String[0]); + } + + private void getNamedVars(int type, ArrayList<Operation> ops, ArrayList<String> list) { + for (Operation op : ops) { if (op instanceof NamedVariable) { NamedVariable n = (NamedVariable) op; if (n.mVarType == type) { - count++; + list.add(n.mVarName); } } - } - if (count == 0) { - return null; - } - String[] ret = new String[count]; - int i = 0; - for (Operation op : mOperations) { - if (op instanceof NamedVariable) { - NamedVariable n = (NamedVariable) op; - if (n.mVarType == type) { - ret[i++] = n.mVarName; - } + if (op instanceof Container) { + getNamedVars(type, ((Container) op).getList(), list); } } - return ret; } ////////////////////////////////////////////////////////////////////////// @@ -1543,4 +1582,20 @@ public class CoreDocument implements Serializable { } } } + + /** + * Set if this is an update doc + * + * @param isUpdateDoc + */ + public void setUpdateDoc(boolean isUpdateDoc) { + mIsUpdateDoc = isUpdateDoc; + } + + /** + * @return if this is an update doc + */ + public boolean isUpdateDoc() { + return mIsUpdateDoc; + } } 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 20252366e264..14d0a7560804 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/Operations.java +++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java @@ -22,6 +22,7 @@ import com.android.internal.widget.remotecompose.core.operations.BitmapFontData; import com.android.internal.widget.remotecompose.core.operations.ClickArea; import com.android.internal.widget.remotecompose.core.operations.ClipPath; import com.android.internal.widget.remotecompose.core.operations.ClipRect; +import com.android.internal.widget.remotecompose.core.operations.ColorAttribute; import com.android.internal.widget.remotecompose.core.operations.ColorConstant; import com.android.internal.widget.remotecompose.core.operations.ColorExpression; import com.android.internal.widget.remotecompose.core.operations.ComponentValue; @@ -126,6 +127,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.modifier import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.PaddingModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.RippleModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.RoundedClipRectModifierOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.RunActionOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ScrollModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ValueFloatChangeActionOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ValueFloatExpressionChangeActionOperation; @@ -233,6 +235,7 @@ public class Operations { public static final int HAPTIC_FEEDBACK = 177; public static final int CONDITIONAL_OPERATIONS = 178; public static final int DEBUG_MESSAGE = 179; + public static final int ATTRIBUTE_COLOR = 180; ///////////////////////////////////////// ====================== @@ -287,6 +290,7 @@ public class Operations { public static final int HOST_ACTION = 209; public static final int HOST_METADATA_ACTION = 216; public static final int HOST_NAMED_ACTION = 210; + public static final int RUN_ACTION = 236; public static final int VALUE_INTEGER_CHANGE_ACTION = 212; public static final int VALUE_STRING_CHANGE_ACTION = 213; @@ -393,6 +397,7 @@ public class Operations { map.put(CONTAINER_END, ContainerEnd::read); + map.put(RUN_ACTION, RunActionOperation::read); map.put(HOST_ACTION, HostActionOperation::read); map.put(HOST_METADATA_ACTION, HostActionMetadataOperation::read); map.put(HOST_NAMED_ACTION, HostNamedActionOperation::read); @@ -446,6 +451,7 @@ public class Operations { map.put(HAPTIC_FEEDBACK, HapticFeedback::read); map.put(CONDITIONAL_OPERATIONS, ConditionalOperations::read); map.put(DEBUG_MESSAGE, DebugMessage::read); + map.put(ATTRIBUTE_COLOR, ColorAttribute::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 e61ca4c1ee13..dd8790e45761 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java +++ b/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java @@ -20,6 +20,8 @@ import android.annotation.Nullable; import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle; +import java.util.HashMap; + /** Specify an abstract paint context used by RemoteCompose commands to draw */ public abstract class PaintContext { public static final int TEXT_MEASURE_MONOSPACE_WIDTH = 0x01; @@ -481,28 +483,9 @@ public abstract class PaintContext { /** * 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, - float rotationX, - float rotationY, - float rotationZ, - float shadowElevation, - float transformOriginX, - float transformOriginY, - float alpha, - int renderEffectId); + * @param attributes + */ + public abstract void setGraphicsLayer(@NonNull HashMap<Integer, Object> attributes); /** Ends a graphics layer */ public abstract void endGraphicsLayer(); @@ -518,4 +501,16 @@ public abstract class PaintContext { * @return the string if found */ public abstract @Nullable String getText(int textID); + + /** + * Returns true if the document has been encoded for at least the given version MAJOR.MINOR + * + * @param major major version number + * @param minor minor version number + * @param patch patch version number + * @return true if the document was written at least with the given version + */ + public boolean supportsVersion(int major, int minor, int patch) { + return mContext.supportsVersion(major, minor, patch); + } } 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 eb7399afd2b7..898e2a8846e3 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java +++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java @@ -25,6 +25,7 @@ import com.android.internal.widget.remotecompose.core.operations.BitmapFontData; import com.android.internal.widget.remotecompose.core.operations.ClickArea; import com.android.internal.widget.remotecompose.core.operations.ClipPath; import com.android.internal.widget.remotecompose.core.operations.ClipRect; +import com.android.internal.widget.remotecompose.core.operations.ColorAttribute; import com.android.internal.widget.remotecompose.core.operations.ColorConstant; import com.android.internal.widget.remotecompose.core.operations.ColorExpression; import com.android.internal.widget.remotecompose.core.operations.ComponentValue; @@ -116,6 +117,7 @@ import com.android.internal.widget.remotecompose.core.operations.layout.modifier import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.PaddingModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.RippleModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.RoundedClipRectModifierOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.RunActionOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ScrollModifierOperation; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ZIndexModifierOperation; import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle; @@ -132,6 +134,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; /** Provides an abstract buffer to encode/decode RemoteCompose operations */ public class RemoteComposeBuffer { @@ -1333,7 +1336,7 @@ public class RemoteComposeBuffer { * @return the nan id of float */ public float reserveFloatVariable() { - int id = mRemoteComposeState.cacheFloat(0f); + int id = mRemoteComposeState.nextId(); return Utils.asNan(id); } @@ -2008,55 +2011,10 @@ public class RemoteComposeBuffer { /** * Add a graphics layer * - * @param scaleX scale x - * @param scaleY scale y - * @param rotationX rotation in X - * @param rotationY rotation in Y - * @param rotationZ rotation in Z - * @param shadowElevation shadow elevation - * @param transformOriginX transform origin x - * @param transformOriginY transform origin y - * @param alpha alpha value - * @param cameraDistance camera distance - * @param blendMode blend mode - * @param spotShadowColorId spot shadow color - * @param ambientShadowColorId ambient shadow color - * @param colorFilterId id of color filter - * @param renderEffectId id of render effect - */ - public void addModifierGraphicsLayer( - float scaleX, - float scaleY, - float rotationX, - float rotationY, - float rotationZ, - float shadowElevation, - float transformOriginX, - float transformOriginY, - float alpha, - float cameraDistance, - int blendMode, - int spotShadowColorId, - int ambientShadowColorId, - int colorFilterId, - int renderEffectId) { - GraphicsLayerModifierOperation.apply( - mBuffer, - scaleX, - scaleY, - rotationX, - rotationY, - rotationZ, - shadowElevation, - transformOriginX, - transformOriginY, - alpha, - cameraDistance, - blendMode, - spotShadowColorId, - ambientShadowColorId, - colorFilterId, - renderEffectId); + * @param attributes + */ + public void addModifierGraphicsLayer(HashMap<Integer, Object> attributes) { + GraphicsLayerModifierOperation.apply(mBuffer, attributes); } /** @@ -2248,6 +2206,11 @@ public class RemoteComposeBuffer { CanvasOperations.apply(mBuffer); } + /** Add container hosting actions */ + public void addRunActionsStart() { + RunActionOperation.apply(mBuffer); + } + /** * Add a component width value * @@ -2516,4 +2479,17 @@ public class RemoteComposeBuffer { public void addDebugMessage(int textId, float value, int flags) { DebugMessage.apply(mBuffer, textId, value, flags); } + + /** + * Return a color attribute value on the given color + * + * @param baseColor + * @param type type of attribute + * @return + */ + public float getColorAttribute(int baseColor, short type) { + int id = mRemoteComposeState.nextId(); + ColorAttribute.apply(mBuffer, id, baseColor, type); + return Utils.asNan(id); + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java index b297a023d03b..3c3cad4862c1 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java +++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java @@ -68,6 +68,18 @@ public abstract class RemoteContext { private boolean mUseChoreographer = true; + /** + * Returns true if the document has been encoded for at least the given version MAJOR.MINOR + * + * @param major major version number + * @param minor minor version number + * @param patch patch version number + * @return true if the document was written at least with the given version + */ + public boolean supportsVersion(int major, int minor, int patch) { + return mDocument.mVersion.supportsVersion(major, minor, patch); + } + public float getDensity() { return mDensity; } @@ -653,6 +665,8 @@ public abstract class RemoteContext { public static final int ID_EPOCH_SECOND = 32; + public static final int ID_FONT_SIZE = 33; + public static final float FLOAT_DENSITY = Utils.asNan(ID_DENSITY); /** CONTINUOUS_SEC is seconds from midnight looping every hour 0-3600 */ @@ -738,6 +752,9 @@ public abstract class RemoteContext { /** When was this player built */ public static final float FLOAT_API_LEVEL = Utils.asNan(ID_API_LEVEL); + /** The default font size */ + public static final float FLOAT_FONT_SIZE = Utils.asNan(ID_FONT_SIZE); + /** The time in seconds since the epoch. */ public static final long INT_EPOCH_SECOND = ((long) ID_EPOCH_SECOND) + 0x100000000L; diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorAttribute.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorAttribute.java new file mode 100644 index 000000000000..8ba5a97f175b --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorAttribute.java @@ -0,0 +1,214 @@ +/* + * 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.INT; +import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.SHORT; + +import android.annotation.NonNull; + +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.PaintContext; +import com.android.internal.widget.remotecompose.core.PaintOperation; +import com.android.internal.widget.remotecompose.core.RemoteContext; +import com.android.internal.widget.remotecompose.core.WireBuffer; +import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; +import com.android.internal.widget.remotecompose.core.serialize.MapSerializer; + +import java.util.List; + +/** Operation to perform Color related calculation */ +public class ColorAttribute extends PaintOperation { + private static final int OP_CODE = Operations.ATTRIBUTE_COLOR; + private static final String CLASS_NAME = "ColorAttribute"; + public int mId; + public int mColorId; + public short mType; + + /** The hue value of the color */ + public static final short COLOR_HUE = 0; + + /** The saturation value of the color */ + public static final short COLOR_SATURATION = 1; + + /** The brightness value of the color */ + public static final short COLOR_BRIGHTNESS = 2; + + /** The red value of the color */ + public static final short COLOR_RED = 3; + + /** The green value of the color */ + public static final short COLOR_GREEN = 4; + + /** The blue value of the color */ + public static final short COLOR_BLUE = 5; + + /** The alpha value of the color */ + public static final short COLOR_ALPHA = 6; + + /** + * creates a new operation + * + * @param id to write value to + * @param colorId of long to calculate on + * @param type the type of calculation + */ + public ColorAttribute(int id, int colorId, short type) { + this.mId = id; + this.mColorId = colorId; + this.mType = type; + } + + @Override + public void write(@NonNull WireBuffer buffer) { + apply(buffer, mId, mColorId, mType); + } + + @Override + public @NonNull String toString() { + return CLASS_NAME + "[" + mId + "] = " + mColorId + " " + mType; + } + + /** + * The name of the class + * + * @return the name + */ + public static @NonNull String name() { + return CLASS_NAME; + } + + /** + * The OP_CODE for this command + * + * @return the opcode + */ + public static int id() { + return OP_CODE; + } + + /** + * Writes out the operation to the buffer + * + * @param buffer write command to this buffer + * @param id the id + * @param textId the id + * @param type the value of the float + */ + public static void apply(@NonNull WireBuffer buffer, int id, int textId, short type) { + buffer.start(OP_CODE); + buffer.writeInt(id); + buffer.writeInt(textId); + buffer.writeShort(type); + } + + /** + * 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 textId = buffer.readInt(); + short type = (short) buffer.readShort(); + operations.add(new ColorAttribute(id, textId, type)); + } + + /** + * 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("Color Operations", OP_CODE, CLASS_NAME) + .description("Calculate Information about a Color") + .field(INT, "id", "id to output") + .field(INT, "longId", "id of color") + .field(SHORT, "type", "the type information to extract"); + } + + @NonNull + @Override + public String deepToString(@NonNull String indent) { + return indent + toString(); + } + + @NonNull float[] mBounds = new float[4]; + + @Override + public void paint(@NonNull PaintContext context) { + int val = mType & 255; + + RemoteContext ctx = context.getContext(); + int color = ctx.getColor(mColorId); + switch (val) { + case COLOR_HUE: + ctx.loadFloat(mId, Utils.getHue(color)); + break; + case COLOR_SATURATION: + ctx.loadFloat(mId, Utils.getSaturation(color)); + break; + case COLOR_BRIGHTNESS: + ctx.loadFloat(mId, Utils.getBrightness(color)); + break; + case COLOR_RED: + ctx.loadFloat(mId, ((color >> 16) & 0xFF) / 255.0f); + break; + case COLOR_GREEN: + ctx.loadFloat(mId, ((color >> 8) & 0xFF) / 255.0f); + break; + case COLOR_BLUE: + ctx.loadFloat(mId, (color & 0xFF) / 255.0f); + break; + case COLOR_ALPHA: + ctx.loadFloat(mId, ((color >> 24) & 0xFF) / 255.0f); + break; + } + } + + @Override + public void serialize(MapSerializer serializer) { + serializer + .addType(CLASS_NAME) + .add("id", mId) + .add("timeId", mColorId) + .addType(getTypeString()); + } + + private String getTypeString() { + int val = mType & 255; + switch (val) { + case COLOR_HUE: + return "COLOR_HUE"; + case COLOR_SATURATION: + return "COLOR_SATURATION"; + case COLOR_BRIGHTNESS: + return "COLOR_BRIGHTNESS"; + case COLOR_RED: + return "COLOR_RED"; + case COLOR_GREEN: + return "COLOR_GREEN"; + case COLOR_BLUE: + return "COLOR_BLUE"; + case COLOR_ALPHA: + return "COLOR_ALPHA"; + default: + return "INVALID_TIME_TYPE"; + } + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ComponentData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ComponentData.java new file mode 100644 index 000000000000..e67482594931 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ComponentData.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2025 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; + +/** Tagging interface to operations providing data within a component */ +public interface ComponentData {} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ConditionalOperations.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ConditionalOperations.java index da6035e1e87e..a288142cef8d 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/ConditionalOperations.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ConditionalOperations.java @@ -83,6 +83,11 @@ public class ConditionalOperations extends PaintOperation public void updateVariables(RemoteContext context) { mVarAOut = Float.isNaN(mVarA) ? context.getFloat(Utils.idFromNan(mVarA)) : mVarA; mVarBOut = Float.isNaN(mVarB) ? context.getFloat(Utils.idFromNan(mVarB)) : mVarB; + for (Operation op : mList) { + if (op instanceof VariableSupport && op.isDirty()) { + ((VariableSupport) op).updateVariables(context); + } + } } /** diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java index 20bebaa1ddb2..21d694bf3661 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java @@ -37,8 +37,8 @@ import java.util.List; public class DataListFloat extends Operation implements VariableSupport, ArrayAccess, Serializable { private static final int OP_CODE = Operations.FLOAT_LIST; private static final String CLASS_NAME = "IdListData"; - private final int mId; - @NonNull private final float[] mValues; + public final int mId; + @NonNull private float[] mValues; private static final int MAX_FLOAT_ARRAY = 2000; public DataListFloat(int id, @NonNull float[] values) { @@ -153,4 +153,13 @@ public class DataListFloat extends Operation implements VariableSupport, ArrayAc public void serialize(MapSerializer serializer) { serializer.addType(CLASS_NAME).add("id", mId).add("values", List.of(mValues)); } + + /** + * Update the DataListFloat with values from a new one + * + * @param lc + */ + public void update(DataListFloat lc) { + mValues = lc.mValues; + } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapFontText.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapFontText.java index bff87fda2fd9..87a99343c28c 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapFontText.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapFontText.java @@ -226,7 +226,11 @@ public class DrawBitmapFontText extends PaintOperation implements VariableSuppor xPos += glyph.mMarginLeft; float xPos2 = xPos + glyph.mBitmapWidth; context.drawBitmap( - glyph.mBitmapId, xPos, mY + glyph.mMarginTop, xPos2, mY + glyph.mBitmapHeight); + glyph.mBitmapId, + xPos, + mY + glyph.mMarginTop, + xPos2, + mY + glyph.mBitmapHeight + glyph.mMarginTop); xPos = xPos2 + glyph.mMarginRight; } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java index 0901ae3eca75..08ec8baa07d5 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java @@ -45,7 +45,8 @@ import java.util.List; * like injecting the width of the component int draw rect As well as supporting generalized * animation floats. The floats represent a RPN style calculator */ -public class FloatExpression extends Operation implements VariableSupport, Serializable { +public class FloatExpression extends Operation + implements ComponentData, VariableSupport, Serializable { private static final int OP_CODE = Operations.ANIMATED_FLOAT; private static final String CLASS_NAME = "FloatExpression"; public int mId; @@ -153,33 +154,44 @@ public class FloatExpression extends Operation implements VariableSupport, Seria if (Float.isNaN(mLastChange)) { mLastChange = t; } - if (mFloatAnimation != null && !Float.isNaN(mLastCalculatedValue)) { + if (mFloatAnimation != null) { // support animations + if (Float.isNaN(mLastCalculatedValue)) { // startup + try { + mLastCalculatedValue = + mExp.eval( + context.getCollectionsAccess(), + mPreCalcValue, + mPreCalcValue.length); + mFloatAnimation.setTargetValue(mLastCalculatedValue); + if (Float.isNaN(mFloatAnimation.getInitialValue())) { + mFloatAnimation.setInitialValue(mLastCalculatedValue); + } + } catch (Exception e) { + throw new RuntimeException( + this.toString() + " len = " + mPreCalcValue.length, e); + } + } float lastComputedValue = mFloatAnimation.get(t - mLastChange); if (lastComputedValue != mLastAnimatedValue) { mLastAnimatedValue = lastComputedValue; context.loadFloat(mId, lastComputedValue); context.needsRepaint(); - if (mFloatAnimation.isPropagate()) { - markDirty(); - } + markDirty(); } - } else if (mSpring != null) { + } else if (mSpring != null) { // support damped spring animation float lastComputedValue = mSpring.get(t - mLastChange); if (lastComputedValue != mLastAnimatedValue) { mLastAnimatedValue = lastComputedValue; context.loadFloat(mId, lastComputedValue); context.needsRepaint(); } - } else { + } else { // no animation float v = 0; try { v = mExp.eval(context.getCollectionsAccess(), mPreCalcValue, mPreCalcValue.length); } catch (Exception e) { throw new RuntimeException(this.toString() + " len = " + mPreCalcValue.length, e); } - if (mFloatAnimation != null) { - mFloatAnimation.setTargetValue(v); - } context.loadFloat(mId, v); } } 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 3d6316b67e8f..916eb4d87cbe 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 @@ -87,6 +87,8 @@ public class Header extends Operation implements RemoteComposeOperation { /** The source of the document */ public static final short DOC_SOURCE = 11; + public static final short DOC_DATA_UPDATE = 12; + /** The object is an integer */ private static final short DATA_TYPE_INT = 0; @@ -105,7 +107,8 @@ public class Header extends Operation implements RemoteComposeOperation { DOC_DENSITY_AT_GENERATION, DOC_DESIRED_FPS, DOC_CONTENT_DESCRIPTION, - DOC_SOURCE + DOC_SOURCE, + DOC_DATA_UPDATE }; private static final String[] KEY_NAMES = { "DOC_WIDTH", @@ -164,6 +167,9 @@ public class Header extends Operation implements RemoteComposeOperation { } private int getInt(int key, int defaultValue) { + if (mProperties == null) { + return defaultValue; + } Integer value = (Integer) mProperties.get(key); if (value != null) { return value; @@ -173,6 +179,9 @@ public class Header extends Operation implements RemoteComposeOperation { } private long getLong(int key, long defaultValue) { + if (mProperties == null) { + return defaultValue; + } Long value = (Long) mProperties.get(key); if (value != null) { return value; @@ -182,6 +191,9 @@ public class Header extends Operation implements RemoteComposeOperation { } private float getFloat(int key, float defaultValue) { + if (mProperties == null) { + return defaultValue; + } Float value = (Float) mProperties.get(key); if (value != null) { return value; @@ -191,6 +203,9 @@ public class Header extends Operation implements RemoteComposeOperation { } private String getString(int key, String defaultValue) { + if (mProperties == null) { + return defaultValue; + } String value = (String) mProperties.get(key); if (value != null) { return value; @@ -531,6 +546,7 @@ public class Header extends Operation implements RemoteComposeOperation { * @param document */ public void setVersion(CoreDocument document) { + document.setUpdateDoc(getInt(DOC_DATA_UPDATE, 0) != 0); document.setVersion(mMajorVersion, mMinorVersion, mPatchVersion); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java index 70197c6d085d..4d9891ba9e49 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java @@ -35,7 +35,8 @@ import com.android.internal.widget.remotecompose.core.serialize.Serializable; import java.util.List; /** Paint data operation */ -public class PaintData extends PaintOperation implements VariableSupport, Serializable { +public class PaintData extends PaintOperation + implements ComponentData, VariableSupport, Serializable { private static final int OP_CODE = Operations.PAINT_VALUES; private static final String CLASS_NAME = "PaintData"; @NonNull public PaintBundle mPaintData = new PaintBundle(); 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 3e5dff8ad277..f12c5f004cf0 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 @@ -47,7 +47,7 @@ import java.util.List; * animation. */ public class TouchExpression extends Operation - implements VariableSupport, TouchListener, Serializable { + implements ComponentData, VariableSupport, TouchListener, Serializable { private static final int OP_CODE = Operations.TOUCH_EXPRESSION; private static final String CLASS_NAME = "TouchExpression"; private float mDefValue; diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java index 5f505409e254..51e7bb273f27 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java @@ -306,4 +306,90 @@ public class Utils { int b = (int) (blue * 255.0f + 0.5f); return (a << 24 | r << 16 | g << 8 | b); } + + /** + * Returns the hue of an ARGB int + * + * @param argb the color int + * @return + */ + public static float getHue(int argb) { + int r = (argb >> 16) & 0xFF; + int g = (argb >> 8) & 0xFF; + int b = argb & 0xFF; + float hsl1, hsl2, hsl3; + final float rf = r / 255f; + final float gf = g / 255f; + final float bf = b / 255f; + final float max = Math.max(rf, Math.max(gf, bf)); + final float min = Math.min(rf, Math.min(gf, bf)); + final float deltaMaxMin = max - min; + float h; + if (max == min) { + // Monochromatic + h = 0f; + } else { + if (max == rf) { + h = ((gf - bf) / deltaMaxMin) % 6f; + } else if (max == gf) { + h = ((bf - rf) / deltaMaxMin) + 2f; + } else { + h = ((rf - gf) / deltaMaxMin) + 4f; + } + } + h = (h * 60f) % 360f; + if (h < 0) { + h += 360f; + } + return h / 360f; + } + + /** + * Get the saturation of an ARGB int + * + * @param argb the color int + * @return + */ + public static float getSaturation(int argb) { + int r = (argb >> 16) & 0xFF; + int g = (argb >> 8) & 0xFF; + int b = argb & 0xFF; + + final float rf = r / 255f; + final float gf = g / 255f; + final float bf = b / 255f; + final float max = Math.max(rf, Math.max(gf, bf)); + final float min = Math.min(rf, Math.min(gf, bf)); + final float deltaMaxMin = max - min; + float s; + float l = (max + min) / 2f; + if (max == min) { + // Monochromatic + s = 0f; + } else { + + s = deltaMaxMin / (1f - Math.abs(2f * l - 1f)); + } + + return s; + } + + /** + * Get the brightness of an ARGB int + * + * @param argb the color int + * @return + */ + public static float getBrightness(int argb) { + int r = (argb >> 16) & 0xFF; + int g = (argb >> 8) & 0xFF; + int b = argb & 0xFF; + final float rf = r / 255f; + final float gf = g / 255f; + final float bf = b / 255f; + final float max = Math.max(rf, Math.max(gf, bf)); + final float min = Math.min(rf, Math.min(gf, bf)); + + return (max + min) / 2f; + } } 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 bc099e3a3b9d..d33431ee6574 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 @@ -25,12 +25,10 @@ import com.android.internal.widget.remotecompose.core.RemoteContext; import com.android.internal.widget.remotecompose.core.TouchListener; import com.android.internal.widget.remotecompose.core.VariableSupport; import com.android.internal.widget.remotecompose.core.operations.BitmapData; -import com.android.internal.widget.remotecompose.core.operations.FloatExpression; +import com.android.internal.widget.remotecompose.core.operations.ComponentData; import com.android.internal.widget.remotecompose.core.operations.MatrixRestore; import com.android.internal.widget.remotecompose.core.operations.MatrixSave; import com.android.internal.widget.remotecompose.core.operations.MatrixTranslate; -import com.android.internal.widget.remotecompose.core.operations.PaintData; -import com.android.internal.widget.remotecompose.core.operations.TouchExpression; import com.android.internal.widget.remotecompose.core.operations.layout.animation.AnimationSpec; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentModifiers; import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentVisibilityOperation; @@ -48,6 +46,7 @@ import com.android.internal.widget.remotecompose.core.serialize.MapSerializer; import com.android.internal.widget.remotecompose.core.serialize.SerializeTags; import java.util.ArrayList; +import java.util.HashMap; /** Component with modifiers and children */ public class LayoutComponent extends Component { @@ -185,9 +184,7 @@ public class LayoutComponent extends Component { ((ScrollModifierOperation) op).inflate(this); } mComponentModifiers.add((ModifierOperation) op); - } else if (op instanceof TouchExpression - || (op instanceof PaintData) - || (op instanceof FloatExpression)) { + } else if (op instanceof ComponentData) { supportedOperations.add(op); if (op instanceof TouchListener) { ((TouchListener) op).setComponent(this); @@ -346,6 +343,8 @@ public class LayoutComponent extends Component { context.restore(); } + protected final HashMap<Integer, Object> mCachedAttributes = new HashMap<>(); + @Override public void paintingComponent(@NonNull PaintContext context) { Component prev = context.getContext().mLastComponent; @@ -359,27 +358,9 @@ public class LayoutComponent extends Component { } if (mGraphicsLayerModifier != null) { context.startGraphicsLayer((int) getWidth(), (int) getHeight()); - float scaleX = mGraphicsLayerModifier.getScaleX(); - float scaleY = mGraphicsLayerModifier.getScaleY(); - float rotationX = mGraphicsLayerModifier.getRotationX(); - float rotationY = mGraphicsLayerModifier.getRotationY(); - float rotationZ = mGraphicsLayerModifier.getRotationZ(); - float shadowElevation = mGraphicsLayerModifier.getShadowElevation(); - float transformOriginX = mGraphicsLayerModifier.getTransformOriginX(); - float transformOriginY = mGraphicsLayerModifier.getTransformOriginY(); - float alpha = mGraphicsLayerModifier.getAlpha(); - int renderEffectId = mGraphicsLayerModifier.getRenderEffectId(); - context.setGraphicsLayer( - scaleX, - scaleY, - rotationX, - rotationY, - rotationZ, - shadowElevation, - transformOriginX, - transformOriginY, - alpha, - renderEffectId); + mCachedAttributes.clear(); + mGraphicsLayerModifier.fillInAttributes(mCachedAttributes); + context.setGraphicsLayer(mCachedAttributes); } mComponentModifiers.paint(context); float tx = mPaddingLeft + getScrollX(); 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 e5914eb7fd28..9a06ef165c3b 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 @@ -73,6 +73,11 @@ public class LoopOperation extends PaintOperation mUntilOut = Float.isNaN(mUntil) ? context.getFloat(Utils.idFromNan(mUntil)) : mUntil; mFromOut = Float.isNaN(mFrom) ? context.getFloat(Utils.idFromNan(mFrom)) : mFrom; mStepOut = Float.isNaN(mStep) ? context.getFloat(Utils.idFromNan(mStep)) : mStep; + for (Operation op : mList) { + if (op instanceof VariableSupport && op.isDirty()) { + ((VariableSupport) op).updateVariables(context); + } + } } public LoopOperation(int indexId, float from, float step, float until) { diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java index 6ee18bbbd7e0..73caa1b997ee 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java @@ -120,10 +120,6 @@ public class BoxLayout extends LayoutManager { size.setHeight(Math.max(size.getHeight(), m.getH())); } } - // add padding - size.setWidth(Math.max(size.getWidth(), computeModifierDefinedWidth(context.getContext()))); - size.setHeight( - Math.max(size.getHeight(), computeModifierDefinedHeight(context.getContext()))); } @Override diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/FitBoxLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/FitBoxLayout.java index ff7a3af11f20..fa16c12ecd2f 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/FitBoxLayout.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/FitBoxLayout.java @@ -156,11 +156,6 @@ public class FitBoxLayout extends LayoutManager { } else { self.setVisibility(Visibility.VISIBLE); } - - // add padding - size.setWidth(Math.max(size.getWidth(), computeModifierDefinedWidth(context.getContext()))); - size.setHeight( - Math.max(size.getHeight(), computeModifierDefinedHeight(context.getContext()))); } @Override diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java index eb10ead34781..09dd842981ec 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java @@ -26,6 +26,8 @@ import com.android.internal.widget.remotecompose.core.operations.layout.measure. import com.android.internal.widget.remotecompose.core.operations.layout.measure.Measurable; import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass; import com.android.internal.widget.remotecompose.core.operations.layout.measure.Size; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightInModifierOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthInModifierOperation; /** Base class for layout managers -- resizable components. */ public abstract class LayoutManager extends LayoutComponent implements Measurable { @@ -133,9 +135,7 @@ public abstract class LayoutManager extends LayoutComponent implements Measurabl return mHeightModifier.isFill(); } - /** Base implementation of the measure resolution */ - @Override - public void measure( + private void measure_v0_4_0( @NonNull PaintContext context, float minWidth, float maxWidth, @@ -279,6 +279,182 @@ public abstract class LayoutManager extends LayoutComponent implements Measurabl internalLayoutMeasure(context, measure); } + private void measure_v0_4_1( + @NonNull PaintContext context, + float minWidth, + float maxWidth, + float minHeight, + float maxHeight, + @NonNull MeasurePass measure) { + + float measuredWidth = Math.min(maxWidth, computeModifierDefinedWidth(context.getContext())); + float measuredHeight = + Math.min(maxHeight, computeModifierDefinedHeight(context.getContext())); + + if (mWidthModifier.isIntrinsicMin()) { + maxWidth = minIntrinsicWidth(context.getContext()) + mPaddingLeft + mPaddingRight; + } + if (mHeightModifier.isIntrinsicMin()) { + maxHeight = minIntrinsicHeight(context.getContext()) + mPaddingTop + mPaddingBottom; + } + WidthInModifierOperation widthIn = mWidthModifier.getWidthIn(); + if (widthIn != null) { + minWidth = Math.max(minWidth, widthIn.getMin()); + maxWidth = Math.min(maxWidth, widthIn.getMax()); + } + HeightInModifierOperation heightIn = mHeightModifier.getHeightIn(); + if (heightIn != null) { + minHeight = Math.max(minHeight, heightIn.getMin()); + maxHeight = Math.min(maxHeight, heightIn.getMax()); + } + + float insetMaxWidth = maxWidth - mPaddingLeft - mPaddingRight; + float insetMaxHeight = maxHeight - mPaddingTop - mPaddingBottom; + + boolean hasHorizontalWrap = false; + boolean hasVerticalWrap = false; + + if (isInHorizontalFill()) { + measuredWidth = maxWidth; + } else if (mWidthModifier.hasWeight()) { + measuredWidth = + Math.max(measuredWidth, computeModifierDefinedWidth(context.getContext())); + } else { + measuredWidth = Math.max(measuredWidth, minWidth); + measuredWidth = Math.min(measuredWidth, maxWidth); + hasHorizontalWrap = mWidthModifier.isWrap() || mWidthModifier.isIntrinsicMin(); + } + + if (isInVerticalFill()) { + measuredHeight = maxHeight; + } else if (mHeightModifier.hasWeight()) { + measuredHeight = + Math.max(measuredHeight, computeModifierDefinedHeight(context.getContext())); + } else { + measuredHeight = Math.max(measuredHeight, minHeight); + measuredHeight = Math.min(measuredHeight, maxHeight); + hasVerticalWrap = mHeightModifier.isWrap() || mHeightModifier.isIntrinsicMin(); + } + + if (minWidth == maxWidth) { + measuredWidth = maxWidth; + } + if (minHeight == maxHeight) { + measuredHeight = maxHeight; + } + + if (hasHorizontalWrap || hasVerticalWrap) { + mCachedWrapSize.setWidth(0f); + mCachedWrapSize.setHeight(0f); + computeWrapSize( + context, + insetMaxWidth, + insetMaxHeight, + mWidthModifier.isWrap(), + mHeightModifier.isWrap(), + measure, + mCachedWrapSize); + int selfVisibilityAfterMeasure = measure.get(this).getVisibility(); + if (Visibility.hasOverride(selfVisibilityAfterMeasure) + && mScheduledVisibility != selfVisibilityAfterMeasure) { + mScheduledVisibility = selfVisibilityAfterMeasure; + } + if (hasHorizontalWrap) { + measuredWidth = mCachedWrapSize.getWidth(); + measuredWidth += mPaddingLeft + mPaddingRight; + measuredWidth = Math.max(measuredWidth, minWidth); + } + if (hasVerticalWrap) { + measuredHeight = mCachedWrapSize.getHeight(); + measuredHeight += mPaddingTop + mPaddingBottom; + measuredHeight = Math.max(measuredHeight, minHeight); + } + } else { + if (hasHorizontalIntrinsicDimension()) { + mCachedWrapSize.setWidth(0f); + mCachedWrapSize.setHeight(0f); + computeWrapSize( + context, + Float.MAX_VALUE, + maxHeight, + false, + false, + measure, + mCachedWrapSize); + float w = mCachedWrapSize.getWidth(); + if (hasHorizontalScroll()) { + computeSize(context, 0f, w, 0, measuredHeight, measure); + mComponentModifiers.setHorizontalScrollDimension(measuredWidth, w); + } else { + computeSize( + context, + 0f, + Math.min(measuredWidth, insetMaxWidth), + 0f, + Math.min(measuredHeight, insetMaxHeight), + measure); + } + } else if (hasVerticalIntrinsicDimension()) { + mCachedWrapSize.setWidth(0f); + mCachedWrapSize.setHeight(0f); + computeWrapSize( + context, maxWidth, Float.MAX_VALUE, false, false, measure, mCachedWrapSize); + float h = mCachedWrapSize.getHeight(); + if (hasVerticalScroll()) { + computeSize(context, 0f, measuredWidth, 0, h, measure); + mComponentModifiers.setVerticalScrollDimension(measuredHeight, h); + } else { + computeSize( + context, + 0f, + Math.min(measuredWidth, insetMaxWidth), + 0f, + Math.min(measuredHeight, insetMaxHeight), + measure); + } + } else { + float maxChildWidth = measuredWidth - mPaddingLeft - mPaddingRight; + float maxChildHeight = measuredHeight - mPaddingTop - mPaddingBottom; + computeSize(context, 0, maxChildWidth, 0, maxChildHeight, measure); + } + } + + if (mContent != null) { + ComponentMeasure cm = measure.get(mContent); + cm.setX(0f); + cm.setY(0f); + cm.setW(measuredWidth); + cm.setH(measuredHeight); + } + + measuredWidth = Math.max(measuredWidth, minWidth); + measuredHeight = Math.max(measuredHeight, minHeight); + + ComponentMeasure m = measure.get(this); + m.setW(measuredWidth); + m.setH(measuredHeight); + m.setVisibility(mScheduledVisibility); + + internalLayoutMeasure(context, measure); + } + + /** Base implementation of the measure resolution */ + @Override + public void measure( + @NonNull PaintContext context, + float minWidth, + float maxWidth, + float minHeight, + float maxHeight, + @NonNull MeasurePass measure) { + + if (context.supportsVersion(0, 4, 1)) { + measure_v0_4_1(context, minWidth, maxWidth, minHeight, maxHeight, measure); + } else { + measure_v0_4_0(context, minWidth, maxWidth, minHeight, maxHeight, measure); + } + } + private boolean hasHorizontalScroll() { return mComponentModifiers.hasHorizontalScroll(); } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java index 120c740eccda..a8e29d957249 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java @@ -195,8 +195,18 @@ public class TextLayout extends LayoutManager implements VariableSupport, Access @Override public void paintingComponent(@NonNull PaintContext context) { + Component prev = context.getContext().mLastComponent; + RemoteContext remoteContext = context.getContext(); + remoteContext.mLastComponent = this; + context.save(); context.translate(mX, mY); + if (mGraphicsLayerModifier != null) { + context.startGraphicsLayer((int) getWidth(), (int) getHeight()); + mCachedAttributes.clear(); + mGraphicsLayerModifier.fillInAttributes(mCachedAttributes); + context.setGraphicsLayer(mCachedAttributes); + } mComponentModifiers.paint(context); float tx = mPaddingLeft; float ty = mPaddingTop; @@ -226,7 +236,7 @@ public class TextLayout extends LayoutManager implements VariableSupport, Access break; case TEXT_ALIGN_RIGHT: case TEXT_ALIGN_END: - px = (mWidth - mPaddingRight - mTextW); + px = (mWidth - mPaddingLeft - mPaddingRight - mTextW); break; case TEXT_ALIGN_LEFT: case TEXT_ALIGN_START: @@ -263,8 +273,13 @@ public class TextLayout extends LayoutManager implements VariableSupport, Access context.restorePaint(); ////////////////////////////////////////////////////////// + if (mGraphicsLayerModifier != null) { + context.endGraphicsLayer(); + } + context.translate(-tx, -ty); context.restore(); + context.getContext().mLastComponent = prev; } @NonNull diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java index 361438b51a47..05e97eb12248 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java @@ -15,10 +15,8 @@ */ package com.android.internal.widget.remotecompose.core.operations.layout.modifiers; -import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT; -import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT; - import android.annotation.NonNull; +import android.annotation.Nullable; import com.android.internal.widget.remotecompose.core.Operation; import com.android.internal.widget.remotecompose.core.Operations; @@ -32,177 +30,228 @@ import com.android.internal.widget.remotecompose.core.operations.utilities.Strin import com.android.internal.widget.remotecompose.core.serialize.MapSerializer; import com.android.internal.widget.remotecompose.core.serialize.SerializeTags; +import java.util.HashMap; import java.util.List; -/** - * Represents a padding modifier. Padding modifiers can be chained and will impact following - * modifiers. - */ +/** Represents a graphics layer modifier. */ public class GraphicsLayerModifierOperation extends DecoratorModifierOperation { private static final int OP_CODE = Operations.MODIFIER_GRAPHICS_LAYER; public static final String CLASS_NAME = "GraphicsLayerModifierOperation"; - AnimatableValue mScaleX; - AnimatableValue mScaleY; - AnimatableValue mRotationX; - AnimatableValue mRotationY; - AnimatableValue mRotationZ; - AnimatableValue mTransformOriginX; - AnimatableValue mTransformOriginY; - AnimatableValue mShadowElevation; - AnimatableValue mAlpha; - AnimatableValue mCameraDistance; - int mBlendMode; - int mSpotShadowColorId; - int mAmbientShadowColorId; - int mColorFilterId; - int mRenderEffectId; - - public GraphicsLayerModifierOperation( - float scaleX, - float scaleY, - float rotationX, - float rotationY, - float rotationZ, - float shadowElevation, - float transformOriginX, - float transformOriginY, - float alpha, - float cameraDistance, - int blendMode, - int spotShadowColorId, - int ambientShadowColorId, - int colorFilterId, - int renderEffectId) { - mScaleX = new AnimatableValue(scaleX); - mScaleY = new AnimatableValue(scaleY); - mRotationX = new AnimatableValue(rotationX); - mRotationY = new AnimatableValue(rotationY); - mRotationZ = new AnimatableValue(rotationZ); - mShadowElevation = new AnimatableValue(shadowElevation); - mTransformOriginX = new AnimatableValue(transformOriginX); - mTransformOriginY = new AnimatableValue(transformOriginY); - mAlpha = new AnimatableValue(alpha); - mCameraDistance = new AnimatableValue(cameraDistance); - mBlendMode = blendMode; - mSpotShadowColorId = spotShadowColorId; - mAmbientShadowColorId = ambientShadowColorId; - mColorFilterId = colorFilterId; - mRenderEffectId = renderEffectId; - } - - public float getScaleX() { - return mScaleX.getValue(); - } - - public float getScaleY() { - return mScaleY.getValue(); - } - - public float getRotationX() { - return mRotationX.getValue(); - } - - public float getRotationY() { - return mRotationY.getValue(); - } - - public float getRotationZ() { - return mRotationZ.getValue(); - } - - public float getShadowElevation() { - return mShadowElevation.getValue(); - } - - public float getTransformOriginX() { - return mTransformOriginX.getValue(); - } - - public float getTransformOriginY() { - return mTransformOriginY.getValue(); - } - - public float getAlpha() { - return mAlpha.getValue(); - } - - public float getCameraDistance() { - return mCameraDistance.getValue(); - } - - // TODO: add implementation for blendmode - public int getBlendModeId() { - return mBlendMode; - } - - // TODO: add implementation for shadow - public int getSpotShadowColorId() { - return mSpotShadowColorId; - } + public static final int SCALE_X = 0; + public static final int SCALE_Y = 1; + public static final int ROTATION_X = 2; + public static final int ROTATION_Y = 3; + public static final int ROTATION_Z = 4; + public static final int TRANSFORM_ORIGIN_X = 5; + public static final int TRANSFORM_ORIGIN_Y = 6; + public static final int TRANSLATION_X = 7; + public static final int TRANSLATION_Y = 8; + public static final int TRANSLATION_Z = 9; + public static final int SHADOW_ELEVATION = 10; + public static final int ALPHA = 11; + public static final int CAMERA_DISTANCE = 12; + public static final int COMPOSITING_STRATEGY = 13; + public static final int SPOT_SHADOW_COLOR = 14; + public static final int AMBIENT_SHADOW_COLOR = 15; + public static final int HAS_BLUR = 16; + public static final int BLUR_RADIUS_X = 17; + public static final int BLUR_RADIUS_Y = 18; + public static final int BLUR_TILE_MODE = 19; + public static final int SHAPE = 20; + public static final int SHAPE_RADIUS = 21; + + public static final int SHAPE_RECT = 0; + public static final int SHAPE_ROUND_RECT = 1; + public static final int SHAPE_CIRCLE = 2; + + public static final int TILE_MODE_CLAMP = 0; + public static final int TILE_MODE_REPEATED = 1; + public static final int TILE_MODE_MIRROR = 2; + public static final int TILE_MODE_DECAL = 3; + + /** The object is an integer */ + private static final short DATA_TYPE_INT = 0; + + /** The object is an float */ + private static final short DATA_TYPE_FLOAT = 1; + + AttributeValue[] mValues = { + new AttributeValue(SCALE_X, "SCALE_X", 1f), + new AttributeValue(SCALE_Y, "SCALE_Y", 1f), + new AttributeValue(ROTATION_X, "ROTATION_X", 0f), + new AttributeValue(ROTATION_Y, "ROTATION_Y", 0f), + new AttributeValue(ROTATION_Z, "ROTATION_Z", 0f), + new AttributeValue(TRANSFORM_ORIGIN_X, "TRANSFORM_ORIGIN_X", 0f), + new AttributeValue(TRANSFORM_ORIGIN_Y, "TRANSFORM_ORIGIN_Y", 0f), + new AttributeValue(TRANSLATION_X, "TRANSLATION_X", 0f), + new AttributeValue(TRANSLATION_Y, "TRANSLATION_Y", 0f), + new AttributeValue(TRANSLATION_Z, "TRANSLATION_Z", 0f), + new AttributeValue(SHADOW_ELEVATION, "SHADOW_ELEVATION", 0f), + new AttributeValue(ALPHA, "ALPHA", 1f), + new AttributeValue(CAMERA_DISTANCE, "CAMERA_DISTANCE", 8f), + new AttributeValue(COMPOSITING_STRATEGY, "COMPOSITING_STRATEGY", 0), + new AttributeValue(SPOT_SHADOW_COLOR, "SPOT_SHADOW_COLOR", 0), + new AttributeValue(AMBIENT_SHADOW_COLOR, "AMBIENT_SHADOW_COLOR", 0), + new AttributeValue(HAS_BLUR, "HAS_BLUR", 0), + new AttributeValue(BLUR_RADIUS_X, "BLUR_RADIUS_X", 0f), + new AttributeValue(BLUR_RADIUS_Y, "BLUR_RADIUS_Y", 0f), + new AttributeValue(BLUR_TILE_MODE, "BLUR_TILE_MODE", TILE_MODE_CLAMP), + new AttributeValue(SHAPE, "SHAPE", -1), + new AttributeValue(SHAPE_RADIUS, "SHAPE_RADIUS", 0f), + }; + + boolean mHasBlurEffect = false; - public int getAmbientShadowColorId() { - return mAmbientShadowColorId; - } - - // TODO: add implementation for color filters - public int getColorFilterId() { - return mColorFilterId; + /** + * Fill in the hashmap with the attributes values + * + * @param attributes + */ + public void fillInAttributes(HashMap<Integer, Object> attributes) { + for (int i = 0; i < mValues.length; i++) { + if (mValues[i].needsToWrite()) { + attributes.put(i, mValues[i].getObjectValue()); + } + } } - public int getRenderEffectId() { - return mRenderEffectId; + static final int FLOAT_VALUE = 0; + static final int INT_VALUE = 1; + + /** Utility class to manage attributes */ + static class AttributeValue { + String mName; + int mId; + @Nullable AnimatableValue mAnimatableValue; + float mDefaultValue = 0f; + int mIntValue = 0; + int mIntDefaultValue = 0; + int mType = FLOAT_VALUE; + + AttributeValue(int id, String name, float defaultValue) { + mId = id; + mName = name; + mDefaultValue = defaultValue; + mType = FLOAT_VALUE; + } + + AttributeValue(int id, String name, int defaultValue) { + mId = id; + mName = name; + mIntDefaultValue = defaultValue; + mType = INT_VALUE; + } + + public float getValue() { + if (mType == FLOAT_VALUE) { + if (mAnimatableValue != null) { + return mAnimatableValue.getValue(); + } + return mDefaultValue; + } else { + return mIntValue; + } + } + + public int getIntValue() { + if (mType == FLOAT_VALUE) { + if (mAnimatableValue != null) { + return (int) mAnimatableValue.getValue(); + } + } else if (mType == INT_VALUE) { + return mIntValue; + } + return 0; + } + + public void evaluate(PaintContext context) { + if (mAnimatableValue != null) { + mAnimatableValue.evaluate(context); + } + } + + public boolean needsToWrite() { + if (mType == FLOAT_VALUE) { + if (mAnimatableValue != null) { + return mAnimatableValue.getValue() != mDefaultValue; + } + return false; + } else if (mType == INT_VALUE) { + return mIntValue != mIntDefaultValue; + } + return false; + } + + public void write(WireBuffer buffer) { + buffer.writeInt(mId); + if (mType == FLOAT_VALUE) { + buffer.writeFloat(getValue()); + } else if (mType == INT_VALUE) { + buffer.writeInt(getIntValue()); + } + } + + public Object getObjectValue() { + if (mType == FLOAT_VALUE) { + return getValue(); + } + return getIntValue(); + } + + public void setValue(float value) { + mAnimatableValue = new AnimatableValue(value); + } + + public void setValue(int value) { + mIntValue = value; + } } @Override - public void write(WireBuffer buffer) { - apply( - buffer, - mScaleX.getValue(), - mScaleY.getValue(), - mRotationX.getValue(), - mRotationY.getValue(), - mRotationZ.getValue(), - mShadowElevation.getValue(), - mTransformOriginX.getValue(), - mTransformOriginY.getValue(), - mAlpha.getValue(), - mCameraDistance.getValue(), - mBlendMode, - mSpotShadowColorId, - mAmbientShadowColorId, - mColorFilterId, - mRenderEffectId); + public void write(@NonNull WireBuffer buffer) { + buffer.start(OP_CODE); + buffer.writeInt(mValues.length); + for (int i = 0; i < mValues.length; i++) { + AttributeValue value = mValues[i]; + if (value.needsToWrite()) { + value.write(buffer); + } + } } @Override public void serializeToString(int indent, StringSerializer serializer) { - serializer.append(indent, "GRAPHICS_LAYER = [" + mScaleX + ", " + mScaleY + "]"); + serializer.append( + indent, + "GRAPHICS_LAYER = [" + + mValues[SCALE_X].getValue() + + ", " + + mValues[SCALE_Y].getValue() + + "]"); } @NonNull @Override public String deepToString(@NonNull String indent) { - return (indent != null ? indent : "") + toString(); + return indent + toString(); } @Override - public void paint(PaintContext context) { - mScaleX.evaluate(context); - mScaleY.evaluate(context); - mRotationX.evaluate(context); - mRotationY.evaluate(context); - mRotationZ.evaluate(context); - mTransformOriginX.evaluate(context); - mTransformOriginY.evaluate(context); - mShadowElevation.evaluate(context); - mAlpha.evaluate(context); - mCameraDistance.evaluate(context); + public void paint(@NonNull PaintContext context) { + for (int i = 0; i < mValues.length; i++) { + AttributeValue v = mValues[i]; + v.evaluate(context); + } } @Override public String toString() { - return "GraphicsLayerModifierOperation(" + mScaleX + ", " + mScaleY + ")"; + return "GraphicsLayerModifierOperation(" + + mValues[SCALE_X].getValue() + + ", " + + mValues[SCALE_Y].getValue() + + ")"; } /** @@ -228,55 +277,46 @@ public class GraphicsLayerModifierOperation extends DecoratorModifierOperation { * Write the operation to the buffer * * @param buffer a WireBuffer - * @param scaleX scaleX of the layer - * @param scaleY scaleY of the layer - * @param rotationX rotationX of the layer - * @param rotationY rotationY of the layer - * @param rotationZ rotationZ of the layer - * @param shadowElevation the shadow elevation - * @param transformOriginX the X origin of the transformations - * @param transformOriginY the Y origin of the transformations - * @param alpha the alpha of the layer - * @param cameraDistance the camera distance - * @param blendMode blending mode of the layer - * @param spotShadowColorId the spot shadow color id - * @param ambientShadowColorId the ambient shadow color id - * @param colorFilterId the color filter id - * @param renderEffectId the render effect id + * @param values attributes of the layer */ - public static void apply( - WireBuffer buffer, - float scaleX, - float scaleY, - float rotationX, - float rotationY, - float rotationZ, - float shadowElevation, - float transformOriginX, - float transformOriginY, - float alpha, - float cameraDistance, - int blendMode, - int spotShadowColorId, - int ambientShadowColorId, - int colorFilterId, - int renderEffectId) { + public static void apply(WireBuffer buffer, HashMap<Integer, Object> values) { buffer.start(OP_CODE); - buffer.writeFloat(scaleX); - buffer.writeFloat(scaleY); - buffer.writeFloat(rotationX); - buffer.writeFloat(rotationY); - buffer.writeFloat(rotationZ); - buffer.writeFloat(shadowElevation); - buffer.writeFloat(transformOriginX); - buffer.writeFloat(transformOriginY); - buffer.writeFloat(alpha); - buffer.writeFloat(cameraDistance); - buffer.writeInt(blendMode); - buffer.writeInt(spotShadowColorId); - buffer.writeInt(ambientShadowColorId); - buffer.writeInt(colorFilterId); - buffer.writeInt(renderEffectId); + int size = values.size(); + buffer.writeInt(size); + for (Integer key : values.keySet()) { + Object value = values.get(key); + if (value instanceof Integer) { + writeIntAttribute(buffer, key, (Integer) value); + } else if (value instanceof Float) { + writeFloatAttribute(buffer, key, (Float) value); + } + } + } + + /** + * Utility to write an integer attribute + * + * @param buffer + * @param type + * @param value + */ + private static void writeIntAttribute(WireBuffer buffer, int type, int value) { + int tag = type | (DATA_TYPE_INT << 10); + buffer.writeInt(tag); + buffer.writeInt(value); + } + + /** + * Utility to write a float attribute + * + * @param buffer + * @param type + * @param value + */ + private static void writeFloatAttribute(WireBuffer buffer, int type, float value) { + int tag = type | (DATA_TYPE_FLOAT << 10); + buffer.writeInt(tag); + buffer.writeFloat(value); } /** @@ -286,38 +326,34 @@ public class GraphicsLayerModifierOperation extends DecoratorModifierOperation { * @param operations the list of operations read so far */ public static void read(WireBuffer buffer, List<Operation> operations) { - float scaleX = buffer.readFloat(); - float scaleY = buffer.readFloat(); - float rotationX = buffer.readFloat(); - float rotationY = buffer.readFloat(); - float rotationZ = buffer.readFloat(); - float shadowElevation = buffer.readFloat(); - float transformOriginX = buffer.readFloat(); - float transformOriginY = buffer.readFloat(); - float alpha = buffer.readFloat(); - float cameraDistance = buffer.readFloat(); - int blendMode = buffer.readInt(); - int spotShadowColorId = buffer.readInt(); - int ambientShadowColorId = buffer.readInt(); - int colorFilterId = buffer.readInt(); - int renderEffectId = buffer.readInt(); - operations.add( - new GraphicsLayerModifierOperation( - scaleX, - scaleY, - rotationX, - rotationY, - rotationZ, - shadowElevation, - transformOriginX, - transformOriginY, - alpha, - cameraDistance, - blendMode, - spotShadowColorId, - ambientShadowColorId, - colorFilterId, - renderEffectId)); + int length = buffer.readInt(); + GraphicsLayerModifierOperation op = new GraphicsLayerModifierOperation(); + for (int i = 0; i < length; i++) { + op.readAttributeValue(buffer); + } + operations.add(op); + } + + /** + * Read a single attribute value from the buffer + * + * @param buffer + */ + private void readAttributeValue(WireBuffer buffer) { + int tag = buffer.readInt(); + int dataType = tag >> 10; + int index = (short) (tag & 0x3F); + if (index == BLUR_RADIUS_X || index == BLUR_RADIUS_Y) { + mHasBlurEffect = true; + mValues[HAS_BLUR].setValue(1); + } + if (dataType == DATA_TYPE_FLOAT) { + float value = buffer.readFloat(); + mValues[index].setValue(value); + } else if (dataType == DATA_TYPE_INT) { + int value = buffer.readInt(); + mValues[index].setValue(value); + } } /** @@ -327,22 +363,7 @@ public class GraphicsLayerModifierOperation extends DecoratorModifierOperation { */ public static void documentation(DocumentationBuilder doc) { doc.operation("Modifier Operations", OP_CODE, CLASS_NAME) - .description("define the GraphicsLayer Modifier") - .field(FLOAT, "scaleX", "") - .field(FLOAT, "scaleY", "") - .field(FLOAT, "rotationX", "") - .field(FLOAT, "rotationY", "") - .field(FLOAT, "rotationZ", "") - .field(FLOAT, "shadowElevation", "") - .field(FLOAT, "transformOriginX", "") - .field(FLOAT, "transformOriginY", "") - .field(FLOAT, "alpha", "") - .field(FLOAT, "cameraDistance", "") - .field(INT, "blendMode", "") - .field(INT, "spotShadowColorId", "") - .field(INT, "ambientShadowColorId", "") - .field(INT, "colorFilterId", "") - .field(INT, "renderEffectId", ""); + .description("define the GraphicsLayer Modifier"); } @Override @@ -353,20 +374,21 @@ public class GraphicsLayerModifierOperation extends DecoratorModifierOperation { serializer .addTags(SerializeTags.MODIFIER) .addType("GraphicsLayerModifierOperation") - .add("scaleX", mScaleX) - .add("scaleX", mScaleX) - .add("rotationX", mRotationX) - .add("rotationY", mRotationY) - .add("rotationZ", mRotationZ) - .add("shadowElevation", mShadowElevation) - .add("transformOriginX", mTransformOriginX) - .add("transformOriginY", mTransformOriginY) - .add("alpha", mAlpha) - .add("cameraDistance", mCameraDistance) - .add("blendMode", mBlendMode) - .add("spotShadowColorId", mSpotShadowColorId) - .add("ambientShadowColorId", mAmbientShadowColorId) - .add("colorFilterId", mColorFilterId) - .add("renderEffectId", mRenderEffectId); + .add("scaleX", mValues[SCALE_X].getValue()) + .add("scaleY", mValues[SCALE_Y].getValue()) + .add("rotationX", mValues[ROTATION_X].getValue()) + .add("rotationY", mValues[ROTATION_Y].getValue()) + .add("rotationZ", mValues[ROTATION_Z].getValue()) + .add("shadowElevation", mValues[SHADOW_ELEVATION].getValue()) + .add("transformOriginX", mValues[TRANSFORM_ORIGIN_X].getValue()) + .add("transformOriginY", mValues[TRANSFORM_ORIGIN_Y].getValue()) + .add("translationX", mValues[TRANSLATION_X].getValue()) + .add("translationY", mValues[TRANSLATION_Y].getValue()) + .add("translationZ", mValues[TRANSLATION_Z].getValue()) + .add("alpha", mValues[ALPHA].getValue()) + .add("cameraDistance", mValues[CAMERA_DISTANCE].getValue()) + .add("compositingStrategy", mValues[COMPOSITING_STRATEGY].getIntValue()) + .add("spotShadowColorId", mValues[SPOT_SHADOW_COLOR].getIntValue()) + .add("ambientShadowColorId", mValues[AMBIENT_SHADOW_COLOR].getIntValue()); } } diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RunActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RunActionOperation.java new file mode 100644 index 000000000000..049c004a9c19 --- /dev/null +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RunActionOperation.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.widget.remotecompose.core.operations.layout.modifiers; + +import android.annotation.NonNull; + +import com.android.internal.widget.remotecompose.core.CoreDocument; +import com.android.internal.widget.remotecompose.core.Operation; +import com.android.internal.widget.remotecompose.core.Operations; +import com.android.internal.widget.remotecompose.core.PaintContext; +import com.android.internal.widget.remotecompose.core.PaintOperation; +import com.android.internal.widget.remotecompose.core.WireBuffer; +import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder; +import com.android.internal.widget.remotecompose.core.operations.layout.ActionOperation; +import com.android.internal.widget.remotecompose.core.operations.layout.Component; +import com.android.internal.widget.remotecompose.core.operations.layout.Container; +import com.android.internal.widget.remotecompose.core.serialize.MapSerializer; + +import java.util.ArrayList; +import java.util.List; + +/** Contains actions and immediately runs them */ +public class RunActionOperation extends PaintOperation implements Container { + private static final int OP_CODE = Operations.RUN_ACTION; + private static final String CLASS_NAME = "RunActionOperation"; + + @NonNull public ArrayList<Operation> mList = new ArrayList<>(); + + public RunActionOperation() {} + + @NonNull + @Override + public String toString() { + return "RunActionOperation()"; + } + + /** + * The returns a list to be filled + * + * @return list to be filled + */ + @NonNull + public ArrayList<Operation> getList() { + return mList; + } + + /** + * Returns the serialized name for this operation + * + * @return the serialized name + */ + @NonNull + public String serializedName() { + return "RUN_ACTION"; + } + + @Override + public void serialize(MapSerializer serializer) { + serializer.addType(CLASS_NAME).add("list", mList); + } + + @Override + public void paint(@NonNull PaintContext context) { + CoreDocument document = context.getContext().getDocument(); + Component component = context.getContext().mLastComponent; + if (document == null || component == null) { + return; + } + for (Operation op : getList()) { + if (op instanceof ActionOperation) { + ActionOperation actionOperation = (ActionOperation) op; + actionOperation.runAction(context.getContext(), document, component, 0f, 0f); + } + } + } + + @NonNull + @Override + public String deepToString(@NonNull String indent) { + return (indent != null ? indent : "") + toString(); + } + + @Override + public boolean isDirty() { + // Always execute + return true; + } + + @Override + public void markNotDirty() { + // nothing + } + + @Override + public void markDirty() { + // nothing + } + + @Override + public void write(@NonNull WireBuffer buffer) { + apply(buffer); + } + + /** + * Write the operation to the buffer + * + * @param buffer a WireBuffer + */ + 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 RunActionOperation()); + } + + /** + * 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, "RunAction") + .description("This operation runs child actions"); + } +} diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java index 55b64364e4e5..9c6f0a47b1c1 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/PaintBundle.java @@ -1411,7 +1411,7 @@ public class PaintBundle implements Serializable { list.add( orderedOf( "type", - "LinearGradient", + "RadialGradient", "colors", colors, "stops", @@ -1431,7 +1431,7 @@ public class PaintBundle implements Serializable { list.add( orderedOf( "type", - "LinearGradient", + "SweepGradient", "colors", colors, "stops", diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java index 349ab6117b2b..03fafaee631e 100644 --- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java +++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java @@ -437,8 +437,8 @@ public class FloatAnimation extends Easing implements Serializable { serializer .addType("FloatAnimation") .add("initialValue", mInitialValue) - .add("targetValue", mInitialValue) - .add("duration", mInitialValue) + .add("targetValue", mTargetValue) + .add("duration", mDuration) .add("easing", Easing.getString(mEasingCurve.getType())); } } diff --git a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java index 2a3f3be714b9..e1a5a49d1fa9 100644 --- a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java +++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java @@ -146,4 +146,8 @@ public class RemoteComposeDocument { return 0; } + + public boolean isUpdateDoc() { + return mDocument.isUpdateDoc(); + } } diff --git a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java index f5b2cca15e43..510e4c4cc2c6 100644 --- a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java +++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java @@ -21,6 +21,7 @@ import static com.android.internal.widget.remotecompose.core.CoreDocument.MINOR_ import android.app.Application; import android.content.Context; import android.content.res.TypedArray; +import android.graphics.Bitmap; import android.graphics.Color; import android.hardware.Sensor; import android.hardware.SensorEvent; @@ -136,6 +137,10 @@ public class RemoteComposePlayer extends FrameLayout implements RemoteContextAwa if (value != null) { if (value.canBeDisplayed( MAX_SUPPORTED_MAJOR_VERSION, MAX_SUPPORTED_MINOR_VERSION, 0L)) { + if (value.isUpdateDoc()) { + updateDocument(value); + return; + } mInner.setDocument(value); int contentBehavior = value.getDocument().getContentScroll(); applyContentBehavior(contentBehavior); @@ -149,6 +154,7 @@ public class RemoteComposePlayer extends FrameLayout implements RemoteContextAwa RemoteComposeTouchHelper.REGISTRAR.clearAccessibilityDelegate(this); } + mapColors(); setupSensors(); mInner.setHapticEngine( @@ -263,6 +269,45 @@ public class RemoteComposePlayer extends FrameLayout implements RemoteContextAwa } /** + * Set an override for a user domain int resource + * + * @param name name of the int + * @param value value of the int + */ + public void setUserLocalColor(String name, int value) { + mInner.setLocalColor("USER:" + name, value); + } + + /** + * Set an override for a user domain float resource + * + * @param name name of the float + * @param value value of the float + */ + public void setUserLocalFloat(String name, float value) { + mInner.setLocalFloat("USER:" + name, value); + } + + /** + * Set an override for a user domain int resource + * + * @param name name of the int + * @param value value of the int + */ + public void setUserLocalBitmap(String name, Bitmap value) { + mInner.setLocalBitmap("USER:" + name, value); + } + + /** + * Clear the override of the given user bitmap + * + * @param name name of the bitmap + */ + public void clearUserLocalBitmap(String name) { + mInner.clearLocalBitmap("USER:" + name); + } + + /** * Clear the override of the given user string * * @param name name of the string @@ -281,6 +326,24 @@ public class RemoteComposePlayer extends FrameLayout implements RemoteContextAwa } /** + * Clear the override of the given user color + * + * @param name name of the color + */ + public void clearUserLocalColor(String name) { + mInner.clearLocalColor("USER:" + name); + } + + /** + * Clear the override of the given user int + * + * @param name name of the int + */ + public void clearUserLocalFloat(String name) { + mInner.clearLocalFloat("USER:" + name); + } + + /** * Set an override for a system domain string resource * * @param name name of the string diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java index 680a221cc2db..f60905db9c4d 100644 --- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java +++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java @@ -23,6 +23,7 @@ import android.graphics.BitmapShader; import android.graphics.BlendMode; import android.graphics.Canvas; import android.graphics.LinearGradient; +import android.graphics.Outline; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PorterDuff; @@ -48,10 +49,12 @@ import com.android.internal.widget.remotecompose.core.operations.ClipPath; import com.android.internal.widget.remotecompose.core.operations.ShaderData; import com.android.internal.widget.remotecompose.core.operations.Utils; import com.android.internal.widget.remotecompose.core.operations.layout.managers.TextLayout; +import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.GraphicsLayerModifierOperation; import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle; import com.android.internal.widget.remotecompose.core.operations.paint.PaintChanges; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; /** @@ -144,31 +147,127 @@ public class AndroidPaintContext extends PaintContext { } @Override - public void setGraphicsLayer( - float scaleX, - float scaleY, - float rotationX, - float rotationY, - float rotationZ, - float shadowElevation, - float transformOriginX, - float transformOriginY, - float alpha, - int renderEffectId) { + public void setGraphicsLayer(@NonNull HashMap<Integer, Object> attributes) { if (mNode == null) { return; } - mNode.setScaleX(scaleX); - mNode.setScaleY(scaleY); - mNode.setRotationX(rotationX); - mNode.setRotationY(rotationY); - mNode.setRotationZ(rotationZ); - mNode.setPivotX(transformOriginX * mNode.getWidth()); - mNode.setPivotY(transformOriginY * mNode.getHeight()); - mNode.setAlpha(alpha); - if (renderEffectId == 1) { - - RenderEffect effect = RenderEffect.createBlurEffect(8f, 8f, Shader.TileMode.CLAMP); + boolean hasBlurEffect = false; + boolean hasOutline = false; + for (Integer key : attributes.keySet()) { + Object value = attributes.get(key); + switch (key) { + case GraphicsLayerModifierOperation.SCALE_X: + mNode.setScaleX((Float) value); + break; + case GraphicsLayerModifierOperation.SCALE_Y: + mNode.setScaleY((Float) value); + break; + case GraphicsLayerModifierOperation.ROTATION_X: + mNode.setRotationX((Float) value); + break; + case GraphicsLayerModifierOperation.ROTATION_Y: + mNode.setRotationY((Float) value); + break; + case GraphicsLayerModifierOperation.ROTATION_Z: + mNode.setRotationZ((Float) value); + break; + case GraphicsLayerModifierOperation.TRANSFORM_ORIGIN_X: + mNode.setPivotX((Float) value * mNode.getWidth()); + break; + case GraphicsLayerModifierOperation.TRANSFORM_ORIGIN_Y: + mNode.setPivotY((Float) value * mNode.getWidth()); + break; + case GraphicsLayerModifierOperation.TRANSLATION_X: + mNode.setTranslationX((Float) value); + break; + case GraphicsLayerModifierOperation.TRANSLATION_Y: + mNode.setTranslationY((Float) value); + break; + case GraphicsLayerModifierOperation.TRANSLATION_Z: + mNode.setTranslationZ((Float) value); + break; + case GraphicsLayerModifierOperation.SHAPE: + hasOutline = true; + break; + case GraphicsLayerModifierOperation.SHADOW_ELEVATION: + mNode.setElevation((Float) value); + break; + case GraphicsLayerModifierOperation.ALPHA: + mNode.setAlpha((Float) value); + break; + case GraphicsLayerModifierOperation.CAMERA_DISTANCE: + mNode.setCameraDistance((Float) value); + break; + case GraphicsLayerModifierOperation.SPOT_SHADOW_COLOR: + mNode.setSpotShadowColor((Integer) value); + break; + case GraphicsLayerModifierOperation.AMBIENT_SHADOW_COLOR: + mNode.setAmbientShadowColor((Integer) value); + break; + case GraphicsLayerModifierOperation.HAS_BLUR: + hasBlurEffect = ((Integer) value) != 0; + break; + } + } + if (hasOutline) { + Outline outline = new Outline(); + outline.setAlpha(1f); + Object oShape = attributes.get(GraphicsLayerModifierOperation.SHAPE); + if (oShape != null) { + Object oShapeRadius = attributes.get(GraphicsLayerModifierOperation.SHAPE_RADIUS); + int type = (Integer) oShape; + if (type == GraphicsLayerModifierOperation.SHAPE_RECT) { + outline.setRect(0, 0, mNode.getWidth(), mNode.getHeight()); + } else if (type == GraphicsLayerModifierOperation.SHAPE_ROUND_RECT) { + if (oShapeRadius != null) { + float radius = (Float) oShapeRadius; + outline.setRoundRect( + new Rect(0, 0, mNode.getWidth(), mNode.getHeight()), radius); + } else { + outline.setRect(0, 0, mNode.getWidth(), mNode.getHeight()); + } + } else if (type == GraphicsLayerModifierOperation.SHAPE_CIRCLE) { + float radius = Math.min(mNode.getWidth(), mNode.getHeight()) / 2f; + outline.setRoundRect( + new Rect(0, 0, mNode.getWidth(), mNode.getHeight()), radius); + } + } + mNode.setOutline(outline); + } + if (hasBlurEffect) { + Object oBlurRadiusX = attributes.get(GraphicsLayerModifierOperation.BLUR_RADIUS_X); + float blurRadiusX = 0f; + if (oBlurRadiusX != null) { + blurRadiusX = (Float) oBlurRadiusX; + } + Object oBlurRadiusY = attributes.get(GraphicsLayerModifierOperation.BLUR_RADIUS_Y); + float blurRadiusY = 0f; + if (oBlurRadiusY != null) { + blurRadiusY = (Float) oBlurRadiusY; + } + int blurTileMode = 0; + Object oBlurTileMode = attributes.get(GraphicsLayerModifierOperation.BLUR_TILE_MODE); + if (oBlurTileMode != null) { + blurTileMode = (Integer) oBlurTileMode; + } + Shader.TileMode tileMode = Shader.TileMode.CLAMP; + switch (blurTileMode) { + case GraphicsLayerModifierOperation.TILE_MODE_CLAMP: + tileMode = Shader.TileMode.CLAMP; + break; + case GraphicsLayerModifierOperation.TILE_MODE_DECAL: + tileMode = Shader.TileMode.DECAL; + + break; + case GraphicsLayerModifierOperation.TILE_MODE_MIRROR: + tileMode = Shader.TileMode.MIRROR; + break; + case GraphicsLayerModifierOperation.TILE_MODE_REPEATED: + tileMode = Shader.TileMode.REPEAT; + break; + } + + RenderEffect effect = RenderEffect.createBlurEffect(blurRadiusX, blurRadiusY, tileMode); mNode.setRenderEffect(effect); } } @@ -177,7 +276,11 @@ public class AndroidPaintContext extends PaintContext { public void endGraphicsLayer() { mNode.endRecording(); mCanvas = mPreviousCanvas; - mCanvas.drawRenderNode(mNode); + if (mCanvas.isHardwareAccelerated()) { + mCanvas.enableZ(); + mCanvas.drawRenderNode(mNode); + mCanvas.disableZ(); + } // node.discardDisplayList(); mNode = null; } @@ -276,8 +379,7 @@ public class AndroidPaintContext extends PaintContext { } mPaint.getFontMetrics(mCachedFontMetrics); mPaint.getTextBounds(str, start, end, mTmpRect); - if ((flags & PaintContext.TEXT_MEASURE_SPACES) != 0 - && (str.startsWith(" ") || str.endsWith(" "))) { + if ((flags & PaintContext.TEXT_MEASURE_SPACES) != 0) { bounds[0] = 0f; bounds[2] = mPaint.measureText(str, start, end); } else { 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 e76fb0654df6..4dc664612b87 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 @@ -28,6 +28,7 @@ import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.widget.FrameLayout; +import android.widget.TextView; import com.android.internal.widget.remotecompose.core.CoreDocument; import com.android.internal.widget.remotecompose.core.RemoteContext; @@ -117,6 +118,8 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta updateClickAreas(); requestLayout(); mARContext.loadFloat(RemoteContext.ID_TOUCH_EVENT_TIME, -Float.MAX_VALUE); + mARContext.loadFloat(RemoteContext.ID_FONT_SIZE, getDefaultTextSize()); + invalidate(); Integer fps = (Integer) mDocument.getDocument().getProperty(Header.DOC_DESIRED_FPS); if (fps != null && fps > 0) { @@ -253,6 +256,31 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta } } + /** + * Set a local named color + * + * @param name + * @param content + */ + public void setLocalColor(String name, int content) { + mARContext.setNamedColorOverride(name, content); + if (mDocument != null) { + mDocument.invalidate(); + } + } + + /** + * Clear a local named color + * + * @param name + */ + public void clearLocalColor(String name) { + mARContext.clearNamedDataOverride(name); + if (mDocument != null) { + mDocument.invalidate(); + } + } + public void setLocalFloat(String name, Float content) { mARContext.setNamedFloatOverride(name, content); if (mDocument != null) { @@ -619,4 +647,8 @@ public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachSta canvas.drawText(str, x, y, paint); } + + private float getDefaultTextSize() { + return new TextView(getContext()).getTextSize(); + } } |