add API for 4x4 Matrix to platform graphics

Test: atest CtsUiRenderingTestCases:android.uirendering.cts.testclasses.CanvasTests#testDrawWithConcatenatedMatrix44 && atest CtsGraphicsTestCases:android.graphics.cts.Matrix44Test
Bug: 280116960
Change-Id: Ifd336585f578592985d45bbe6ad0728fbcdafa4b
diff --git a/core/api/current.txt b/core/api/current.txt
index ed4b92a..6516fe2 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -15482,6 +15482,7 @@
     method public boolean clipRect(float, float, float, float);
     method public boolean clipRect(int, int, int, int);
     method public void concat(@Nullable android.graphics.Matrix);
+    method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void concat44(@Nullable android.graphics.Matrix44);
     method public void disableZ();
     method public void drawARGB(int, int, int, int);
     method public void drawArc(@NonNull android.graphics.RectF, float, float, boolean, @NonNull android.graphics.Paint);
@@ -16130,6 +16131,24 @@
     enum_constant public static final android.graphics.Matrix.ScaleToFit START;
   }
 
+  @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public class Matrix44 {
+    ctor @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public Matrix44();
+    ctor @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public Matrix44(@NonNull android.graphics.Matrix);
+    method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 concat(@NonNull android.graphics.Matrix44);
+    method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public float get(int, int);
+    method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void getValues(@NonNull float[]);
+    method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public boolean invert();
+    method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public boolean isIdentity();
+    method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public float[] map(float, float, float, float);
+    method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void map(float, float, float, float, @NonNull float[]);
+    method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void reset();
+    method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 rotate(float, float, float, float);
+    method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 scale(float, float, float);
+    method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void set(int, int, float);
+    method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void setValues(@NonNull float[]);
+    method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 translate(float, float, float);
+  }
+
   public class Mesh {
     ctor public Mesh(@NonNull android.graphics.MeshSpecification, int, @NonNull java.nio.Buffer, int, @NonNull android.graphics.RectF);
     ctor public Mesh(@NonNull android.graphics.MeshSpecification, int, @NonNull java.nio.Buffer, int, @NonNull java.nio.ShortBuffer, @NonNull android.graphics.RectF);
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index d1aceaf..b33a5d2 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -18,6 +18,7 @@
 
 import android.annotation.ColorInt;
 import android.annotation.ColorLong;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -30,6 +31,8 @@
 import android.os.Build;
 import android.text.TextShaper;
 
+import com.android.graphics.hwui.flags.Flags;
+
 import dalvik.annotation.optimization.CriticalNative;
 import dalvik.annotation.optimization.FastNative;
 
@@ -766,6 +769,21 @@
     }
 
     /**
+     * Preconcat the current matrix with the specified matrix. If the specified
+     * matrix is null, this method does nothing. If the canvas's matrix is changed in the z-axis
+     * through this function, the deprecated {@link #getMatrix()} method will return a 3x3 with
+     * z-axis info stripped away.
+     *
+     * @param m The 4x4 matrix to preconcatenate with the current matrix
+     */
+    @FlaggedApi(Flags.FLAG_MATRIX_44)
+    public void concat44(@Nullable Matrix44 m) {
+        if (m != null) {
+            nConcat(mNativeCanvasWrapper, m.mBackingArray);
+        }
+    }
+
+    /**
      * Completely replace the current matrix with the specified matrix. If the
      * matrix parameter is null, then the current matrix is reset to identity.
      *
@@ -1444,6 +1462,8 @@
     private static native void nSkew(long canvasHandle, float sx, float sy);
     @CriticalNative
     private static native void nConcat(long nativeCanvas, long nativeMatrix);
+    @FastNative
+    private static native void nConcat(long nativeCanvas, float[] mat);
     @CriticalNative
     private static native void nSetMatrix(long nativeCanvas, long nativeMatrix);
     @CriticalNative
diff --git a/graphics/java/android/graphics/Matrix44.java b/graphics/java/android/graphics/Matrix44.java
new file mode 100644
index 0000000..7cc0eb7
--- /dev/null
+++ b/graphics/java/android/graphics/Matrix44.java
@@ -0,0 +1,472 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+
+import com.android.graphics.hwui.flags.Flags;
+
+import java.util.Arrays;
+
+/**
+ * The Matrix44 class holds a 4x4 matrix for transforming coordinates. It is similar to
+ * {@link Matrix}, and should be used when you want to manipulate the canvas in 3D. Values are kept
+ * in row-major order. The values and operations are treated as column vectors.
+ */
+@FlaggedApi(Flags.FLAG_MATRIX_44)
+public class Matrix44 {
+    final float[] mBackingArray;
+    /**
+     * The default Matrix44 constructor will instantiate an identity matrix.
+     */
+    @FlaggedApi(Flags.FLAG_MATRIX_44)
+    public Matrix44() {
+        mBackingArray = new float[]{1.0f, 0.0f, 0.0f, 0.0f,
+                                    0.0f, 1.0f, 0.0f, 0.0f,
+                                    0.0f, 0.0f, 1.0f, 0.0f,
+                                    0.0f, 0.0f, 0.0f, 1.0f};
+    }
+
+    /**
+     * Creates and returns a Matrix44 by taking the 3x3 Matrix and placing it on the 0 of the z-axis
+     * by setting row {@code 2} and column {@code 2} to the identity as seen in the following
+     * operation:
+     * <pre class="prettyprint">
+     * [ a b c ]      [ a b 0 c ]
+     * [ d e f ]  ->  [ d e 0 f ]
+     * [ g h i ]      [ 0 0 1 0 ]
+     *                [ g h 0 i ]
+     * </pre>
+     *
+     * @param mat A 3x3 Matrix to be converted (original Matrix will not be changed)
+     */
+    @FlaggedApi(Flags.FLAG_MATRIX_44)
+    public Matrix44(@NonNull Matrix mat) {
+        float[] m = new float[9];
+        mat.getValues(m);
+        mBackingArray = new float[]{m[0], m[1], 0.0f, m[2],
+                                    m[3], m[4], 0.0f, m[5],
+                                    0.0f, 0.0f, 1.0f, 0.0f,
+                                    m[6], m[7], 0.0f, m[8]};
+    }
+
+    /**
+     * Copies matrix values into the provided array in row-major order.
+     *
+     * @param dst The float array where values will be copied, must be of length 16
+     * @throws IllegalArgumentException if the destination float array is not of length 16
+     */
+    @FlaggedApi(Flags.FLAG_MATRIX_44)
+    public void getValues(@NonNull float [] dst) {
+        if (dst.length == 16) {
+            System.arraycopy(mBackingArray, 0, dst, 0, mBackingArray.length);
+        } else {
+            throw new IllegalArgumentException("Dst array must be of length 16");
+        }
+    }
+
+    /**
+     * Replaces the Matrix's values with the values in the provided array.
+     *
+     * @param src A float array of length 16. Floats are treated in row-major order
+     * @throws IllegalArgumentException if the destination float array is not of length 16
+     */
+    @FlaggedApi(Flags.FLAG_MATRIX_44)
+    public void setValues(@NonNull float[] src) {
+        if (src.length == 16) {
+            System.arraycopy(src, 0, mBackingArray, 0, mBackingArray.length);
+        } else {
+            throw new IllegalArgumentException("Src array must be of length 16");
+        }
+    }
+
+    /**
+     * Gets the value at the matrix's row and column.
+     *
+     * @param row An integer from 0 to 4 indicating the row of the value to get
+     * @param col An integer from 0 to 4 indicating the column of the value to get
+     */
+    @FlaggedApi(Flags.FLAG_MATRIX_44)
+    public float get(int row, int col) {
+        if (row >= 0 && row < 4 && col >= 0 && col < 4) {
+            return mBackingArray[row * 4 + col];
+        }
+        throw new IllegalArgumentException("invalid row and column values");
+    }
+
+    /**
+     * Sets the value at the matrix's row and column to the provided value.
+     *
+     * @param row An integer from 0 to 4 indicating the row of the value to change
+     * @param col An integer from 0 to 4 indicating the column of the value to change
+     * @param val The value the element at the specified index will be set to
+     */
+    @FlaggedApi(Flags.FLAG_MATRIX_44)
+    public void set(int row, int col, float val) {
+        if (row >= 0 && row < 4 && col >= 0 && col < 4) {
+            mBackingArray[row * 4 + col] = val;
+        } else {
+            throw new IllegalArgumentException("invalid row and column values");
+        }
+    }
+
+    /**
+     * Sets the Matrix44 to the identity matrix.
+     */
+    @FlaggedApi(Flags.FLAG_MATRIX_44)
+    public void reset() {
+        for (int i = 0; i < mBackingArray.length; i++) {
+            mBackingArray[i] = (i % 4 == i / 4) ? 1.0f : 0.0f;
+        }
+    }
+
+    /**
+     * Inverts the Matrix44, then return true if successful, false if unable to invert.
+     *
+     * @return {@code true} on success, {@code false} otherwise
+     */
+    @FlaggedApi(Flags.FLAG_MATRIX_44)
+    public boolean invert() {
+        float a00 = mBackingArray[0];
+        float a01 = mBackingArray[1];
+        float a02 = mBackingArray[2];
+        float a03 = mBackingArray[3];
+        float a10 = mBackingArray[4];
+        float a11 = mBackingArray[5];
+        float a12 = mBackingArray[6];
+        float a13 = mBackingArray[7];
+        float a20 = mBackingArray[8];
+        float a21 = mBackingArray[9];
+        float a22 = mBackingArray[10];
+        float a23 = mBackingArray[11];
+        float a30 = mBackingArray[12];
+        float a31 = mBackingArray[13];
+        float a32 = mBackingArray[14];
+        float a33 = mBackingArray[15];
+        float b00 = a00 * a11 - a01 * a10;
+        float b01 = a00 * a12 - a02 * a10;
+        float b02 = a00 * a13 - a03 * a10;
+        float b03 = a01 * a12 - a02 * a11;
+        float b04 = a01 * a13 - a03 * a11;
+        float b05 = a02 * a13 - a03 * a12;
+        float b06 = a20 * a31 - a21 * a30;
+        float b07 = a20 * a32 - a22 * a30;
+        float b08 = a20 * a33 - a23 * a30;
+        float b09 = a21 * a32 - a22 * a31;
+        float b10 = a21 * a33 - a23 * a31;
+        float b11 = a22 * a33 - a23 * a32;
+        float det = (b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06);
+        if (det == 0.0f) {
+            return false;
+        }
+        float invDet = 1.0f / det;
+        mBackingArray[0] = ((a11 * b11 - a12 * b10 + a13 * b09) * invDet);
+        mBackingArray[1] = ((-a01 * b11 + a02 * b10 - a03 * b09) * invDet);
+        mBackingArray[2] = ((a31 * b05 - a32 * b04 + a33 * b03) * invDet);
+        mBackingArray[3] = ((-a21 * b05 + a22 * b04 - a23 * b03) * invDet);
+        mBackingArray[4] = ((-a10 * b11 + a12 * b08 - a13 * b07) * invDet);
+        mBackingArray[5] = ((a00 * b11 - a02 * b08 + a03 * b07) * invDet);
+        mBackingArray[6] = ((-a30 * b05 + a32 * b02 - a33 * b01) * invDet);
+        mBackingArray[7] = ((a20 * b05 - a22 * b02 + a23 * b01) * invDet);
+        mBackingArray[8] = ((a10 * b10 - a11 * b08 + a13 * b06) * invDet);
+        mBackingArray[9] = ((-a00 * b10 + a01 * b08 - a03 * b06) * invDet);
+        mBackingArray[10] = ((a30 * b04 - a31 * b02 + a33 * b00) * invDet);
+        mBackingArray[11] = ((-a20 * b04 + a21 * b02 - a23 * b00) * invDet);
+        mBackingArray[12] = ((-a10 * b09 + a11 * b07 - a12 * b06) * invDet);
+        mBackingArray[13] = ((a00 * b09 - a01 * b07 + a02 * b06) * invDet);
+        mBackingArray[14] = ((-a30 * b03 + a31 * b01 - a32 * b00) * invDet);
+        mBackingArray[15] = ((a20 * b03 - a21 * b01 + a22 * b00) * invDet);
+        return true;
+    }
+
+    /**
+     * Returns true if Matrix44 is equal to identity matrix.
+     */
+    @FlaggedApi(Flags.FLAG_MATRIX_44)
+    public boolean isIdentity() {
+        for (int i = 0; i < mBackingArray.length; i++) {
+            float expected = (i % 4 == i / 4) ? 1.0f : 0.0f;
+            if (expected != mBackingArray[i]) return false;
+        }
+        return true;
+    }
+
+    @FlaggedApi(Flags.FLAG_MATRIX_44)
+    private static float dot(Matrix44 a, Matrix44 b, int row, int col) {
+        return (a.get(row, 0) * b.get(0, col))
+                + (a.get(row, 1) * b.get(1, col))
+                + (a.get(row, 2) * b.get(2, col))
+                + (a.get(row, 3) * b.get(3, col));
+    }
+
+    @FlaggedApi(Flags.FLAG_MATRIX_44)
+    private static float dot(float r0, float r1, float r2, float r3,
+                             float c0, float c1, float c2, float c3) {
+        return (r0 * c0) + (r1 * c1) + (r2 * c2) + (r3 * c3);
+    }
+
+    /**
+     * Multiplies (x, y, z, w) vector by the Matrix44, then returns the new (x, y, z, w). Users
+     * should set {@code w} to 1 to indicate the coordinates are normalized.
+     *
+     * @return An array of length 4 that represents the x, y, z, w (where w is perspective) value
+     * after multiplying x, y, z, 1 by the matrix
+     */
+    @FlaggedApi(Flags.FLAG_MATRIX_44)
+    public @NonNull float[] map(float x, float y, float z, float w) {
+        float[] dst = new float[4];
+        this.map(x, y, z, w, dst);
+        return dst;
+    }
+
+    /**
+     * Multiplies (x, y, z, w) vector by the Matrix44, then returns the new (x, y, z, w). Users
+     * should set {@code w} to 1 to indicate the coordinates are normalized.
+     */
+    @FlaggedApi(Flags.FLAG_MATRIX_44)
+    public void map(float x, float y, float z, float w, @NonNull float[] dst) {
+        if (dst.length != 4) {
+            throw new IllegalArgumentException("Dst array must be of length 4");
+        }
+        dst[0] = x * mBackingArray[0] + y * mBackingArray[1]
+                + z * mBackingArray[2] + w * mBackingArray[3];
+        dst[1] = x * mBackingArray[4] + y * mBackingArray[5]
+                + z * mBackingArray[6] + w * mBackingArray[7];
+        dst[2] = x * mBackingArray[8] + y * mBackingArray[9]
+                + z * mBackingArray[10] + w * mBackingArray[11];
+        dst[3] = x * mBackingArray[12] + y * mBackingArray[13]
+                + z * mBackingArray[14] + w * mBackingArray[15];
+    }
+
+    /**
+     * Multiplies `this` matrix (A) and provided Matrix (B) in the order of A * B.
+     * The result is saved in `this` Matrix.
+     *
+     * @param b The second Matrix in the concatenation operation
+     * @return A reference to this Matrix, which can be used to chain Matrix operations
+     */
+    @FlaggedApi(Flags.FLAG_MATRIX_44)
+    public @NonNull Matrix44 concat(@NonNull Matrix44 b) {
+        float val00 = dot(this, b, 0, 0);
+        float val01 = dot(this, b, 0, 1);
+        float val02 = dot(this, b, 0, 2);
+        float val03 = dot(this, b, 0, 3);
+        float val10 = dot(this, b, 1, 0);
+        float val11 = dot(this, b, 1, 1);
+        float val12 = dot(this, b, 1, 2);
+        float val13 = dot(this, b, 1, 3);
+        float val20 = dot(this, b, 2, 0);
+        float val21 = dot(this, b, 2, 1);
+        float val22 = dot(this, b, 2, 2);
+        float val23 = dot(this, b, 2, 3);
+        float val30 = dot(this, b, 3, 0);
+        float val31 = dot(this, b, 3, 1);
+        float val32 = dot(this, b, 3, 2);
+        float val33 = dot(this, b, 3, 3);
+
+        mBackingArray[0] = val00;
+        mBackingArray[1] = val01;
+        mBackingArray[2] = val02;
+        mBackingArray[3] = val03;
+        mBackingArray[4] = val10;
+        mBackingArray[5] = val11;
+        mBackingArray[6] = val12;
+        mBackingArray[7] = val13;
+        mBackingArray[8] = val20;
+        mBackingArray[9] = val21;
+        mBackingArray[10] = val22;
+        mBackingArray[11] = val23;
+        mBackingArray[12] = val30;
+        mBackingArray[13] = val31;
+        mBackingArray[14] = val32;
+        mBackingArray[15] = val33;
+
+        return this;
+    }
+
+    /**
+     * Applies a rotation around a given axis, then returns self.
+     * {@code x}, {@code y}, {@code z} represent the axis by which to rotate around.
+     * For example, pass in {@code 1, 0, 0} to rotate around the x-axis.
+     * The axis provided will be normalized.
+     *
+     * @param deg Amount in degrees to rotate the matrix about the x-axis
+     * @param xComp X component of the rotation axis
+     * @param yComp Y component of the rotation axis
+     * @param zComp Z component of the rotation axis
+     * @return A reference to this Matrix, which can be used to chain Matrix operations
+     */
+    @FlaggedApi(Flags.FLAG_MATRIX_44)
+    public @NonNull Matrix44 rotate(float deg, float xComp, float yComp, float zComp) {
+        float sum = xComp + yComp + zComp;
+        float x = xComp / sum;
+        float y = yComp / sum;
+        float z = zComp / sum;
+
+        float c = (float) Math.cos(deg * Math.PI / 180.0f);
+        float s = (float) Math.sin(deg * Math.PI / 180.0f);
+        float t = 1 - c;
+
+        float rotVals00 = t * x * x + c;
+        float rotVals01 = t * x * y - s * z;
+        float rotVals02 = t * x * z + s * y;
+        float rotVals10 = t * x * y + s * z;
+        float rotVals11 = t * y * y + c;
+        float rotVals12 = t * y * z - s * x;
+        float rotVals20 = t * x * z - s * y;
+        float rotVals21 = t * y * z + s * x;
+        float rotVals22 = t * z * z + c;
+
+        float v00 = dot(mBackingArray[0], mBackingArray[1], mBackingArray[2], mBackingArray[3],
+                rotVals00, rotVals10, rotVals20, 0);
+        float v01 = dot(mBackingArray[0], mBackingArray[1], mBackingArray[2], mBackingArray[3],
+                rotVals01, rotVals11, rotVals21, 0);
+        float v02 = dot(mBackingArray[0], mBackingArray[1], mBackingArray[2], mBackingArray[3],
+                rotVals02, rotVals12, rotVals22, 0);
+        float v03 = dot(mBackingArray[0], mBackingArray[1], mBackingArray[2], mBackingArray[3],
+                0, 0, 0, 1);
+        float v10 = dot(mBackingArray[4], mBackingArray[5], mBackingArray[6], mBackingArray[7],
+                rotVals00, rotVals10, rotVals20, 0);
+        float v11 = dot(mBackingArray[4], mBackingArray[5], mBackingArray[6], mBackingArray[7],
+                rotVals01, rotVals11, rotVals21, 0);
+        float v12 = dot(mBackingArray[4], mBackingArray[5], mBackingArray[6], mBackingArray[7],
+                rotVals02, rotVals12, rotVals22, 0);
+        float v13 = dot(mBackingArray[4], mBackingArray[5], mBackingArray[6], mBackingArray[7],
+                0, 0, 0, 1);
+        float v20 = dot(mBackingArray[8], mBackingArray[9], mBackingArray[10], mBackingArray[11],
+                rotVals00, rotVals10, rotVals20, 0);
+        float v21 = dot(mBackingArray[8], mBackingArray[9], mBackingArray[10], mBackingArray[11],
+                rotVals01, rotVals11, rotVals21, 0);
+        float v22 = dot(mBackingArray[8], mBackingArray[9], mBackingArray[10], mBackingArray[11],
+                rotVals02, rotVals12, rotVals22, 0);
+        float v23 = dot(mBackingArray[8], mBackingArray[9], mBackingArray[10], mBackingArray[11],
+                0, 0, 0, 1);
+        float v30 = dot(mBackingArray[12], mBackingArray[13], mBackingArray[14], mBackingArray[15],
+                rotVals00, rotVals10, rotVals20, 0);
+        float v31 = dot(mBackingArray[12], mBackingArray[13], mBackingArray[14], mBackingArray[15],
+                rotVals01, rotVals11, rotVals21, 0);
+        float v32 = dot(mBackingArray[12], mBackingArray[13], mBackingArray[14], mBackingArray[15],
+                rotVals02, rotVals12, rotVals22, 0);
+        float v33 = dot(mBackingArray[12], mBackingArray[13], mBackingArray[14], mBackingArray[15],
+                0, 0, 0, 1);
+
+        mBackingArray[0] = v00;
+        mBackingArray[1] = v01;
+        mBackingArray[2] = v02;
+        mBackingArray[3] = v03;
+        mBackingArray[4] = v10;
+        mBackingArray[5] = v11;
+        mBackingArray[6] = v12;
+        mBackingArray[7] = v13;
+        mBackingArray[8] = v20;
+        mBackingArray[9] = v21;
+        mBackingArray[10] = v22;
+        mBackingArray[11] = v23;
+        mBackingArray[12] = v30;
+        mBackingArray[13] = v31;
+        mBackingArray[14] = v32;
+        mBackingArray[15] = v33;
+
+        return this;
+    }
+
+    /**
+     * Applies scaling factors to `this` Matrix44, then returns self. Pass 1s for no change.
+     *
+     * @param x Scaling factor for the x-axis
+     * @param y Scaling factor for the y-axis
+     * @param z Scaling factor for the z-axis
+     * @return A reference to this Matrix, which can be used to chain Matrix operations
+     */
+    @FlaggedApi(Flags.FLAG_MATRIX_44)
+    public @NonNull Matrix44 scale(float x, float y, float z) {
+        mBackingArray[0] *= x;
+        mBackingArray[4] *= x;
+        mBackingArray[8] *= x;
+        mBackingArray[12] *= x;
+        mBackingArray[1] *= y;
+        mBackingArray[5] *= y;
+        mBackingArray[9] *= y;
+        mBackingArray[13] *= y;
+        mBackingArray[2] *= z;
+        mBackingArray[6] *= z;
+        mBackingArray[10] *= z;
+        mBackingArray[14] *= z;
+
+        return this;
+    }
+
+    /**
+     * Applies a translation to `this` Matrix44, then returns self.
+     *
+     * @param x Translation for the x-axis
+     * @param y Translation for the y-axis
+     * @param z Translation for the z-axis
+     * @return A reference to this Matrix, which can be used to chain Matrix operations
+     */
+    @FlaggedApi(Flags.FLAG_MATRIX_44)
+    public @NonNull Matrix44 translate(float x, float y, float z) {
+        float newX = x * mBackingArray[0] + y * mBackingArray[1]
+                + z * mBackingArray[2] + mBackingArray[3];
+        float newY = x * mBackingArray[4] + y * mBackingArray[5]
+                + z * mBackingArray[6] + mBackingArray[7];
+        float newZ = x * mBackingArray[8] + y * mBackingArray[9]
+                + z * mBackingArray[10] + mBackingArray[11];
+        float newW = x * mBackingArray[12] + y * mBackingArray[13]
+                + z * mBackingArray[14] + mBackingArray[15];
+
+        mBackingArray[3] = newX;
+        mBackingArray[7] = newY;
+        mBackingArray[11] = newZ;
+        mBackingArray[15] = newW;
+
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("""
+                        | %f %f %f %f |
+                        | %f %f %f %f |
+                        | %f %f %f %f |
+                        | %f %f %f %f |
+                        """, mBackingArray[0], mBackingArray[1], mBackingArray[2], mBackingArray[3],
+                mBackingArray[4], mBackingArray[5], mBackingArray[6], mBackingArray[7],
+                mBackingArray[8], mBackingArray[9], mBackingArray[10], mBackingArray[11],
+                mBackingArray[12], mBackingArray[13], mBackingArray[14], mBackingArray[15]);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof Matrix44) {
+            return Arrays.equals(mBackingArray, ((Matrix44) obj).mBackingArray);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return (int) mBackingArray[0] + (int) mBackingArray[1] + (int) mBackingArray[2]
+                + (int) mBackingArray[3] + (int) mBackingArray[4] + (int) mBackingArray[5]
+                + (int) mBackingArray[6] + (int) mBackingArray[7] + (int) mBackingArray[8]
+                + (int) mBackingArray[9] + (int) mBackingArray[10] + (int) mBackingArray[11]
+                + (int) mBackingArray[12] + (int) mBackingArray[13] + (int) mBackingArray[14]
+                + (int) mBackingArray[15];
+    }
+
+}
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 008ea3a..14b8d8d 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -341,6 +341,10 @@
     mCanvas->concat(matrix);
 }
 
+void SkiaCanvas::concat(const SkM44& matrix) {
+    mCanvas->concat(matrix);
+}
+
 void SkiaCanvas::rotate(float degrees) {
     mCanvas->rotate(degrees);
 }
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index 4bf1790..5e3553b 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -86,6 +86,7 @@
     virtual void getMatrix(SkMatrix* outMatrix) const override;
     virtual void setMatrix(const SkMatrix& matrix) override;
     virtual void concat(const SkMatrix& matrix) override;
+    virtual void concat(const SkM44& matrix) override;
     virtual void rotate(float degrees) override;
     virtual void scale(float sx, float sy) override;
     virtual void skew(float sx, float sy) override;
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 9ec023b..20e3ad2 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -175,6 +175,7 @@
     virtual void setMatrix(const SkMatrix& matrix) = 0;
 
     virtual void concat(const SkMatrix& matrix) = 0;
+    virtual void concat(const SkM44& matrix) = 0;
     virtual void rotate(float degrees) = 0;
     virtual void scale(float sx, float sy) = 0;
     virtual void skew(float sx, float sy) = 0;
diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp
index 8ba7503..5893ef6 100644
--- a/libs/hwui/jni/android_graphics_Canvas.cpp
+++ b/libs/hwui/jni/android_graphics_Canvas.cpp
@@ -158,6 +158,13 @@
     get_canvas(canvasHandle)->concat(*matrix);
 }
 
+static void concat44(JNIEnv* env, jobject obj, jlong canvasHandle, jfloatArray arr) {
+    jfloat* matVals = env->GetFloatArrayElements(arr, 0);
+    const SkM44 matrix = SkM44::RowMajor(matVals);
+    get_canvas(canvasHandle)->concat(matrix);
+    env->ReleaseFloatArrayElements(arr, matVals, 0);
+}
+
 static void rotate(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle, jfloat degrees) {
     get_canvas(canvasHandle)->rotate(degrees);
 }
@@ -755,6 +762,7 @@
     {"nGetMatrix", "(JJ)V", (void*)CanvasJNI::getMatrix},
     {"nSetMatrix","(JJ)V", (void*) CanvasJNI::setMatrix},
     {"nConcat","(JJ)V", (void*) CanvasJNI::concat},
+    {"nConcat","(J[F)V", (void*) CanvasJNI::concat44},
     {"nRotate","(JF)V", (void*) CanvasJNI::rotate},
     {"nScale","(JFF)V", (void*) CanvasJNI::scale},
     {"nSkew","(JFF)V", (void*) CanvasJNI::skew},