diff --git a/nativeruntime/cpp/CMakeLists.txt b/nativeruntime/cpp/CMakeLists.txt index 85105d8b3ee..d45664f977d 100644 --- a/nativeruntime/cpp/CMakeLists.txt +++ b/nativeruntime/cpp/CMakeLists.txt @@ -3,11 +3,6 @@ cmake_minimum_required(VERSION 3.10) # This is needed to ensure that static libraries can be linked into shared libraries. set(CMAKE_POSITION_INDEPENDENT_CODE ON) -# Building the nativeruntime does not work with GCC due to libstddc++ linker errors. -# TODO: figure out which linker args are needed to build with GCC. -set(CMAKE_C_COMPILER "clang") -set(CMAKE_CXX_COMPILER "clang++") - # Some libutils headers require C++17 set (CMAKE_CXX_STANDARD 17) @@ -100,6 +95,8 @@ target_link_libraries(androidsqlite ${STATIC_ICUI18N_LIBRARY} ${STATIC_ICUUC_LIBRARY} ${STATIC_ICUDATA_LIBRARY} + -ldl + -lpthread ) include_directories(${JNI_INCLUDE_DIRS}) @@ -142,8 +139,6 @@ if (CMAKE_HOST_SYSTEM_NAME MATCHES "Linux") target_link_libraries(nativeruntime -static-libgcc -static-libstdc++ - -ldl - -lpthread -Wl,--no-undefined # print an error if there are any undefined symbols ) endif() diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowDevicePolicyManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowDevicePolicyManagerTest.java index 04e4f3b49c0..3e3e2b0464a 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowDevicePolicyManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowDevicePolicyManagerTest.java @@ -542,6 +542,14 @@ public void isUninstallBlockedWithNullAdminShouldThrowNullPointerExceptionOnLoll assertThat(devicePolicyManager.isUninstallBlocked(/* admin= */ null, app)).isTrue(); } + @Test + @Config(minSdk = R) + public void isUniqueDeviceAttestationSupported() { + shadowOf(devicePolicyManager).setIsUniqueDeviceAttestationSupported(true); + + assertThat(devicePolicyManager.isUniqueDeviceAttestationSupported()).isTrue(); + } + @Test @Config(minSdk = LOLLIPOP) public void setApplicationRestrictionsShouldWorkAsIntendedForDeviceOwner() { diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowRenderNodeTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowRenderNodeTest.java new file mode 100644 index 00000000000..0e0db151cc6 --- /dev/null +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowRenderNodeTest.java @@ -0,0 +1,1078 @@ +package org.robolectric.shadows; + +import static android.os.Build.VERSION_CODES.LOLLIPOP; +import static android.os.Build.VERSION_CODES.Q; +import static com.google.common.truth.Truth.assertThat; +import static org.robolectric.util.ReflectionHelpers.ClassParameter.from; +import static org.robolectric.util.ReflectionHelpers.callInstanceMethod; +import static org.robolectric.util.ReflectionHelpers.callStaticMethod; +import static org.robolectric.util.ReflectionHelpers.loadClass; + +import android.annotation.TargetApi; +import android.graphics.Matrix; +import android.graphics.RenderNode; +import android.os.Build; +import android.view.View; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +/** + * Android-Q only test for {@code RenderNode}'s shadow for both pre-Q & Q (where the latter's {@code + * RenderNode} was moved to a public API to open access to it. + */ +@RunWith(AndroidJUnit4.class) +@Config(minSdk = LOLLIPOP) +public final class ShadowRenderNodeTest { + + @Before + public void setup() { + System.setProperty("robolectric.rendernode.enableMatrix", "true"); + } + + @Test + public void testGetTranslationX_unset_returnsZero() { + RenderNodeAccess renderNode = createRenderNode("test"); + + float translationX = renderNode.getTranslationX(); + + assertThat(translationX).isWithin(1e-3f).of(0.f); + } + + @Test + public void testGetTranslationX_set_returnsSetTranslation() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setTranslationX(1.f); + + float translationX = renderNode.getTranslationX(); + + assertThat(translationX).isWithin(1e-3f).of(1.f); + } + + @Test + public void testGetTranslationY_unset_returnsZero() { + RenderNodeAccess renderNode = createRenderNode("test"); + + float translationY = renderNode.getTranslationY(); + + assertThat(translationY).isWithin(1e-3f).of(0.f); + } + + @Test + public void testGetTranslationY_set_returnsSetTranslation() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setTranslationY(1.f); + + float translationY = renderNode.getTranslationY(); + + assertThat(translationY).isWithin(1e-3f).of(1.f); + } + + @Test + public void testGetRotationX_unset_returnsZero() { + RenderNodeAccess renderNode = createRenderNode("test"); + + float rotationX = renderNode.getRotationX(); + + assertThat(rotationX).isWithin(1e-3f).of(0.f); + } + + @Test + public void testGetRotationX_set_returnsSetRotationX() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setRotationX(45.f); + + float rotationX = renderNode.getRotationX(); + + assertThat(rotationX).isWithin(1e-3f).of(45.f); + } + + @Test + public void testGetRotationY_unset_returnsZero() { + RenderNodeAccess renderNode = createRenderNode("test"); + + float rotationY = renderNode.getRotationY(); + + assertThat(rotationY).isWithin(1e-3f).of(0.f); + } + + @Test + public void testGetRotationY_set_returnsSetRotationY() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setRotationY(45.f); + + float rotationY = renderNode.getRotationY(); + + assertThat(rotationY).isWithin(1e-3f).of(45.f); + } + + @Test + public void testGetRotationZ_unset_returnsZero() { + RenderNodeAccess renderNode = createRenderNode("test"); + + float rotationZ = renderNode.getRotationZ(); + + assertThat(rotationZ).isWithin(1e-3f).of(0.f); + } + + @Test + public void testGetRotationZ_set_returnsSetRotationZ() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setRotationZ(45.f); + + float rotationZ = renderNode.getRotationZ(); + + assertThat(rotationZ).isWithin(1e-3f).of(45.f); + } + + @Test + public void testGetScaleX_unset_returnsOne() { + RenderNodeAccess renderNode = createRenderNode("test"); + + float scaleX = renderNode.getScaleX(); + + assertThat(scaleX).isWithin(1e-3f).of(1.f); + } + + @Test + public void testGetScaleX_set_returnsSetScale() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setScaleX(2.f); + + float scaleX = renderNode.getScaleX(); + + assertThat(scaleX).isWithin(1e-3f).of(2.f); + } + + @Test + public void testGetScaleY_unset_returnsOne() { + RenderNodeAccess renderNode = createRenderNode("test"); + + float scaleY = renderNode.getScaleY(); + + assertThat(scaleY).isWithin(1e-3f).of(1.f); + } + + @Test + public void testGetScaleY_set_returnsSetScale() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setScaleY(2.f); + + float scaleY = renderNode.getScaleY(); + + assertThat(scaleY).isWithin(1e-3f).of(2.f); + } + + @Test + public void testGetPivotX_unset_returnsZero() { + RenderNodeAccess renderNode = createRenderNode("test"); + + float pivotX = renderNode.getPivotX(); + + assertThat(pivotX).isWithin(1e-3f).of(0.f); + } + + @Test + public void testGetPivotX_set_returnsSetPivot() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setPivotX(1.f); + + float pivotX = renderNode.getPivotX(); + + assertThat(pivotX).isWithin(1e-3f).of(1.f); + } + + @Test + public void testGetPivotY_unset_returnsZero() { + RenderNodeAccess renderNode = createRenderNode("test"); + + float pivotY = renderNode.getPivotY(); + + assertThat(pivotY).isWithin(1e-3f).of(0.f); + } + + @Test + public void testGetPivotY_set_returnsSetPivot() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setPivotY(1.f); + + float pivotY = renderNode.getPivotY(); + + assertThat(pivotY).isWithin(1e-3f).of(1.f); + } + + @Test + public void testIsPivotExplicitlySet_defaultNode_returnsFalse() { + RenderNodeAccess renderNode = createRenderNode("test"); + + boolean isExplicitlySet = renderNode.isPivotExplicitlySet(); + + assertThat(isExplicitlySet).isFalse(); + } + + @Test + public void testIsPivotExplicitlySet_setPivotX_returnsTrue() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setPivotX(1.f); + + boolean isExplicitlySet = renderNode.isPivotExplicitlySet(); + + assertThat(isExplicitlySet).isTrue(); + } + + @Test + public void testIsPivotExplicitlySet_setPivotY_returnsTrue() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setPivotY(1.f); + + boolean isExplicitlySet = renderNode.isPivotExplicitlySet(); + + assertThat(isExplicitlySet).isTrue(); + } + + @Test + public void testIsPivotExplicitlySet_setPivotXY_toDefaultValues_returnsFalse() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setPivotX(0.f); + renderNode.setPivotY(0.f); + + boolean isExplicitlySet = renderNode.isPivotExplicitlySet(); + + // Setting the pivot to the center should result in the pivot not being explicitly set. + assertThat(isExplicitlySet).isTrue(); + } + + @Test + public void testHasIdentityMatrix_defaultNode_returnsTrue() { + RenderNodeAccess renderNode = createRenderNode("test"); + + boolean hasIdentityMatrix = renderNode.hasIdentityMatrix(); + + assertThat(hasIdentityMatrix).isTrue(); + } + + @Test + public void testHasIdentityMatrix_updatedTranslationX_returnsFalse() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setTranslationX(1.f); + + boolean hasIdentityMatrix = renderNode.hasIdentityMatrix(); + + assertThat(hasIdentityMatrix).isFalse(); + } + + @Test + public void testHasIdentityMatrix_updatedRotationX_returnsTrue() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setRotationX(90.f); + + boolean hasIdentityMatrix = renderNode.hasIdentityMatrix(); + + // Rotations about the x-axis are not factored into the render node's transformation matrix. + assertThat(hasIdentityMatrix).isTrue(); + } + + @Test + public void testGetMatrix_defaultNode_returnsIdentityMatrix() { + RenderNodeAccess renderNode = createRenderNode("test"); + + Matrix matrix = new Matrix(); + renderNode.getMatrix(matrix); + + assertThat(matrix.isIdentity()).isTrue(); + } + + @Test + public void testGetMatrix_updatedTranslationX_returnsNonIdentityMatrix() { + RenderNodeAccess renderNode = createRenderNode("test"); + + renderNode.setTranslationX(1.f); + + Matrix matrix = new Matrix(); + renderNode.getMatrix(matrix); + assertThat(matrix.isIdentity()).isFalse(); + } + + @Test + public void testGetMatrix_updatedTranslationX_thenY_returnsDifferentMatrix() { + RenderNodeAccess renderNode = createRenderNode("test"); + + renderNode.setTranslationX(1.f); + Matrix matrix1 = new Matrix(); + renderNode.getMatrix(matrix1); + + renderNode.setTranslationY(1.f); + Matrix matrix2 = new Matrix(); + renderNode.getMatrix(matrix2); + + assertThat(matrix1).isNotEqualTo(matrix2); + } + + @Test + public void testGetMatrix_updatedTranslation_returnsMatrixWithTranslation() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setTranslationX(2.f); + renderNode.setTranslationY(3.f); + + Matrix matrix = new Matrix(); + renderNode.getMatrix(matrix); + + float[] values = new float[9]; + matrix.getValues(values); + assertThat(values[Matrix.MTRANS_X]).isWithin(1e-3f).of(2.f); + assertThat(values[Matrix.MTRANS_Y]).isWithin(1e-3f).of(3.f); + } + + @Test + public void testGetMatrix_updatedRotation_noPivot_mappedPoint_pointRotatesCorrectly() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setRotationZ(90.f); + Matrix matrix = new Matrix(); + renderNode.getMatrix(matrix); + + float[] point = {2.f, 5.f}; + matrix.mapPoints(point); + + // A point rotated counterclockwise 90 degrees will now be across the y-axis and flipped. + assertThat(point[0]).isWithin(1e-3f).of(-5.f); + assertThat(point[1]).isWithin(1e-3f).of(2.f); + } + + @Test + public void testGetMatrix_updatedRotation_withPivot_mappedPoint_pointRotatesCorrectly() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setPivotX(1.f); + renderNode.setPivotY(2.f); + renderNode.setRotationZ(90.f); + Matrix matrix = new Matrix(); + renderNode.getMatrix(matrix); + + float[] point = {2.f, 5.f}; + matrix.mapPoints(point); + + /* + * A point rotated counterclockwise 90 degrees will now be across the y-axis and flipped. + * However, it's further translated due to the rotation above the pivot point (1, 2). See: + * Rotation of v about X by D: Rt(v, X, D) = R(D) * (v - X) + X + * where Rm(D) is the rotation transformation counterclockwise by D degrees. The above + * shifts the pivot point to the origin, rotates about the origin, then shifts back. + * Applied: Rt((2, 5), (1, 2), 90) = R(90) * ((2, 5) - (1, 2)) + (1, 2). + * R(90) * (1, 3) = (-3, 1) -> (-3, 1) + (1, 2) = (-2, 3) + */ + assertThat(point[0]).isWithin(1e-3f).of(-2.f); + assertThat(point[1]).isWithin(1e-3f).of(3.f); + } + + @Test + public void testGetMatrix_updatedScale_noPivot_mappedPoint_pointScalesCorrectly() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setScaleX(1.5f); + renderNode.setScaleY(2.f); + Matrix matrix = new Matrix(); + renderNode.getMatrix(matrix); + + float[] point = {2.f, 5.f}; + matrix.mapPoints(point); + + // (2, 5) * (1.5, 2) = (3, 10) + assertThat(point[0]).isWithin(1e-3f).of(3.f); + assertThat(point[1]).isWithin(1e-3f).of(10.f); + } + + @Test + public void testGetMatrix_updatedScale_withPivot_mappedPoint_pointScalesCorrectly() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setScaleX(1.5f); + renderNode.setScaleY(2.f); + renderNode.setPivotX(1); + renderNode.setPivotY(2); + Matrix matrix = new Matrix(); + renderNode.getMatrix(matrix); + + float[] point = {2.f, 5.f}; + matrix.mapPoints(point); + + // See the rotation about a pivot above for the explanation for performing a linear + // transformation about a point. + // 1. (2, 5) - (1, 2) = (1, 3) + // 2. (1, 3) * (1.5, 2) = (1.5, 6) + // 3. (1.5, 6) + (1, 2) = (2.5, 8) + assertThat(point[0]).isWithin(1e-3f).of(2.5f); + assertThat(point[1]).isWithin(1e-3f).of(8.f); + } + + @Test + public void testGetMatrix_updatedScaleTranslationRotation_withPivot_mappedPoint_pointXformed() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setTranslationX(-6.f); + renderNode.setTranslationY(-3.f); + renderNode.setScaleX(1.5f); + renderNode.setScaleY(2.f); + renderNode.setPivotX(1); + renderNode.setPivotY(2); + renderNode.setRotationZ(90.f); + Matrix matrix = new Matrix(); + renderNode.getMatrix(matrix); + + float[] point = {2.f, 5.f}; + matrix.mapPoints(point); + + // See the pivot tests above for scale & rotation to follow this math. Transformations should be + // scale, then rotation, then translation. Both the scale and rotation share a pivot. + // 1. (2, 5) - (1, 2) = (1, 3) + // 2. (1, 3) * (1.5, 2) = (1.5, 6) + // 3. rotate(1.5, 6, 90) = (-6, 1.5) + // 4. (-6, 1.5) + (1, 2) = (-5, 3.5) + // 5. (-5, 3.5) + (-6, -3) = (-11, 0.5) + assertThat(point[0]).isWithin(1e-3f).of(-11.f); + assertThat(point[1]).isWithin(1e-3f).of(0.5f); + } + + @Test + public void testGetInverseMatrix_defaultNode_returnsIdentityMatrix() { + RenderNodeAccess renderNode = createRenderNode("test"); + + Matrix matrix = new Matrix(); + renderNode.getInverseMatrix(matrix); + + assertThat(matrix.isIdentity()).isTrue(); + } + + @Test + public void testGetInverseMatrix_updatedTranslationX_returnsNonIdentityMatrix() { + RenderNodeAccess renderNode = createRenderNode("test"); + + renderNode.setTranslationX(1.f); + + Matrix matrix = new Matrix(); + renderNode.getInverseMatrix(matrix); + assertThat(matrix.isIdentity()).isFalse(); + } + + @Test + public void testGetInverseMatrix_updatedTranslationX_thenY_returnsDifferentMatrix() { + RenderNodeAccess renderNode = createRenderNode("test"); + + renderNode.setTranslationX(1.f); + Matrix matrix1 = new Matrix(); + renderNode.getInverseMatrix(matrix1); + + renderNode.setTranslationY(1.f); + Matrix matrix2 = new Matrix(); + renderNode.getInverseMatrix(matrix2); + + assertThat(matrix1).isNotEqualTo(matrix2); + } + + @Test + public void testGetInverseMatrix_updatedScaleTranslationRotation_withPivot_mappedPoint_inverts() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setTranslationX(-6.f); + renderNode.setTranslationY(-3.f); + renderNode.setScaleX(1.5f); + renderNode.setScaleY(2.f); + renderNode.setPivotX(1); + renderNode.setPivotY(2); + renderNode.setRotationZ(90.f); + Matrix inverse = new Matrix(); + renderNode.getInverseMatrix(inverse); + + float[] point = {-11f, 0.5f}; + inverse.mapPoints(point); + + // See testGetMatrix_updatedScaleTranslationRotation_withPivot_mappedPoint_pointXformed above + // for why the point (-11, 0.5) produces (2, 5) when mapped via the inverse matrix. + assertThat(point[0]).isWithin(1e-3f).of(2.f); + assertThat(point[1]).isWithin(1e-3f).of(5.f); + } + + @Test + public void testGetMatrix_complexMatrix_multipliedByInverse_producesIdentity() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setTranslationX(-6.f); + renderNode.setTranslationY(-3.f); + renderNode.setScaleX(1.5f); + renderNode.setScaleY(2.f); + renderNode.setPivotX(1); + renderNode.setPivotY(2); + renderNode.setRotationZ(90.f); + + Matrix matrix = new Matrix(); + Matrix inverse = new Matrix(); + Matrix product = new Matrix(); + renderNode.getMatrix(matrix); + renderNode.getInverseMatrix(inverse); + product.postConcat(matrix); + product.postConcat(inverse); + + assertThat(product.isIdentity()).isTrue(); + } + + @Test + public void testGetAlpha_unset_returnsOne() { + RenderNodeAccess renderNode = createRenderNode("test"); + + float alpha = renderNode.getAlpha(); + + assertThat(alpha).isWithin(1e-3f).of(1.f); + } + + @Test + public void testGetAlpha_set_returnsSetAlpha() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setAlpha(0.5f); + + float alpha = renderNode.getAlpha(); + + assertThat(alpha).isWithin(1e-3f).of(0.5f); + } + + @Test + public void testGetCameraDistance_unset_returnsZero() { + RenderNodeAccess renderNode = createRenderNode("test"); + + float cameraDistance = renderNode.getCameraDistance(); + + assertThat(cameraDistance).isWithin(1e-3f).of(0.f); + } + + @Test + public void testGetCameraDistance_set_returnsSetCameraDistance() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setCameraDistance(2.3f); + + float cameraDistance = renderNode.getCameraDistance(); + + assertThat(cameraDistance).isWithin(1e-3f).of(2.3f); + } + + @Test + public void testGetClipToOutline_unset_returnsFalse() { + RenderNodeAccess renderNode = createRenderNode("test"); + + boolean clipToOutline = renderNode.getClipToOutline(); + + assertThat(clipToOutline).isFalse(); + } + + @Test + public void testGetClipToOutline_set_returnsTrue() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setClipToOutline(true); + + boolean clipToOutline = renderNode.getClipToOutline(); + + assertThat(clipToOutline).isTrue(); + } + + @Test + public void testGetElevation_unset_returnsZero() { + RenderNodeAccess renderNode = createRenderNode("test"); + + float elevation = renderNode.getElevation(); + + assertThat(elevation).isWithin(1e-3f).of(0.f); + } + + @Test + public void testGetElevation_set_returnsSetElevation() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setElevation(2.f); + + float elevation = renderNode.getElevation(); + + assertThat(elevation).isWithin(1e-3f).of(2.f); + } + + @Test + public void testHasOverlappingRendering_unset_returnsFalse() { + RenderNodeAccess renderNode = createRenderNode("test"); + + boolean hasOverlappingRendering = renderNode.hasOverlappingRendering(); + + assertThat(hasOverlappingRendering).isFalse(); + } + + @Test + public void testHasOverlappingRendering_set_returnsTrue() { + RenderNodeAccess renderNode = createRenderNode("test"); + renderNode.setHasOverlappingRendering(true); + + boolean hasOverlappingRendering = renderNode.hasOverlappingRendering(); + + assertThat(hasOverlappingRendering).isTrue(); + } + + private static RenderNodeAccess createRenderNode(String name) { + if (Build.VERSION.SDK_INT < Q) { + return new RenderNodeAccessPreQ(name); + } else { + return new RenderNodeAccessPostQ(name); + } + } + + /** + * Provides access to a {@code RenderNode} depending on the version of Android being used. This + * class is needed since multiple versions of {@code RenderNode} exist depending on the SDK + * version. + */ + private interface RenderNodeAccess { + boolean setAlpha(float alpha); + + float getAlpha(); + + boolean setCameraDistance(float cameraDistance); + + float getCameraDistance(); + + boolean setClipToOutline(boolean clipToOutline); + + boolean getClipToOutline(); + + boolean setElevation(float lift); + + float getElevation(); + + boolean setHasOverlappingRendering(boolean overlappingRendering); + + boolean hasOverlappingRendering(); + + boolean setRotationZ(float rotationZ); + + float getRotationZ(); + + boolean setRotationX(float rotationX); + + float getRotationX(); + + boolean setRotationY(float rotationY); + + float getRotationY(); + + boolean setScaleX(float scaleX); + + float getScaleX(); + + boolean setScaleY(float scaleY); + + float getScaleY(); + + boolean setTranslationX(float translationX); + + boolean setTranslationY(float translationY); + + boolean setTranslationZ(float translationZ); + + float getTranslationX(); + + float getTranslationY(); + + float getTranslationZ(); + + boolean isPivotExplicitlySet(); + + boolean setPivotX(float pivotX); + + float getPivotX(); + + boolean setPivotY(float pivotY); + + float getPivotY(); + + boolean hasIdentityMatrix(); + + void getMatrix(Matrix outMatrix); + + void getInverseMatrix(Matrix outMatrix); + } + + /** Provides access to {@link android.view.RenderNode}. */ + private static final class RenderNodeAccessPreQ implements RenderNodeAccess { + private final Class renderNodeClass; + private final Object renderNode; + + private RenderNodeAccessPreQ(String name) { + renderNodeClass = + loadClass( + ApplicationProvider.getApplicationContext().getClass().getClassLoader(), + "android.view.RenderNode"); + renderNode = + callStaticMethod( + renderNodeClass, + "create", + from(String.class, name), + from(View.class, /* val= */ null)); + } + + @Override + public boolean setAlpha(float alpha) { + return callInstanceMethod(renderNodeClass, renderNode, "setAlpha", from(float.class, alpha)); + } + + @Override + public float getAlpha() { + return callInstanceMethod(renderNodeClass, renderNode, "getAlpha"); + } + + @Override + public boolean setCameraDistance(float cameraDistance) { + return callInstanceMethod( + renderNodeClass, renderNode, "setCameraDistance", from(float.class, cameraDistance)); + } + + @Override + public float getCameraDistance() { + return callInstanceMethod(renderNodeClass, renderNode, "getCameraDistance"); + } + + @Override + public boolean setClipToOutline(boolean clipToOutline) { + return callInstanceMethod( + renderNodeClass, renderNode, "setClipToOutline", from(boolean.class, clipToOutline)); + } + + @Override + public boolean getClipToOutline() { + return callInstanceMethod(renderNodeClass, renderNode, "getClipToOutline"); + } + + @Override + public boolean setElevation(float lift) { + return callInstanceMethod( + renderNodeClass, renderNode, "setElevation", from(float.class, lift)); + } + + @Override + public float getElevation() { + return callInstanceMethod(renderNodeClass, renderNode, "getElevation"); + } + + @Override + public boolean setHasOverlappingRendering(boolean overlappingRendering) { + return callInstanceMethod( + renderNodeClass, + renderNode, + "setHasOverlappingRendering", + from(boolean.class, overlappingRendering)); + } + + @Override + public boolean hasOverlappingRendering() { + return callInstanceMethod(renderNodeClass, renderNode, "hasOverlappingRendering"); + } + + @Override + public boolean setRotationZ(float rotationZ) { + return callInstanceMethod( + renderNodeClass, renderNode, "setRotation", from(float.class, rotationZ)); + } + + @Override + public float getRotationZ() { + return callInstanceMethod(renderNodeClass, renderNode, "getRotation"); + } + + @Override + public boolean setRotationX(float rotationX) { + return callInstanceMethod( + renderNodeClass, renderNode, "setRotationX", from(float.class, rotationX)); + } + + @Override + public float getRotationX() { + return callInstanceMethod(renderNodeClass, renderNode, "getRotationX"); + } + + @Override + public boolean setRotationY(float rotationY) { + return callInstanceMethod( + renderNodeClass, renderNode, "setRotationY", from(float.class, rotationY)); + } + + @Override + public float getRotationY() { + return callInstanceMethod(renderNodeClass, renderNode, "getRotationY"); + } + + @Override + public boolean setScaleX(float scaleX) { + return callInstanceMethod( + renderNodeClass, renderNode, "setScaleX", from(float.class, scaleX)); + } + + @Override + public float getScaleX() { + return callInstanceMethod(renderNodeClass, renderNode, "getScaleX"); + } + + @Override + public boolean setScaleY(float scaleY) { + return callInstanceMethod( + renderNodeClass, renderNode, "setScaleY", from(float.class, scaleY)); + } + + @Override + public float getScaleY() { + return callInstanceMethod(renderNodeClass, renderNode, "getScaleY"); + } + + @Override + public boolean setTranslationX(float translationX) { + return callInstanceMethod( + renderNodeClass, renderNode, "setTranslationX", from(float.class, translationX)); + } + + @Override + public boolean setTranslationY(float translationY) { + return callInstanceMethod( + renderNodeClass, renderNode, "setTranslationY", from(float.class, translationY)); + } + + @Override + public boolean setTranslationZ(float translationZ) { + return callInstanceMethod( + renderNodeClass, renderNode, "setTranslationZ", from(float.class, translationZ)); + } + + @Override + public float getTranslationX() { + return callInstanceMethod(renderNodeClass, renderNode, "getTranslationX"); + } + + @Override + public float getTranslationY() { + return callInstanceMethod(renderNodeClass, renderNode, "getTranslationY"); + } + + @Override + public float getTranslationZ() { + return callInstanceMethod(renderNodeClass, renderNode, "getTranslationZ"); + } + + @Override + public boolean isPivotExplicitlySet() { + return callInstanceMethod(renderNodeClass, renderNode, "isPivotExplicitlySet"); + } + + @Override + public boolean setPivotX(float pivotX) { + return callInstanceMethod( + renderNodeClass, renderNode, "setPivotX", from(float.class, pivotX)); + } + + @Override + public float getPivotX() { + return callInstanceMethod(renderNodeClass, renderNode, "getPivotX"); + } + + @Override + public boolean setPivotY(float pivotY) { + return callInstanceMethod( + renderNodeClass, renderNode, "setPivotY", from(float.class, pivotY)); + } + + @Override + public float getPivotY() { + return callInstanceMethod(renderNodeClass, renderNode, "getPivotY"); + } + + @Override + public boolean hasIdentityMatrix() { + return callInstanceMethod(renderNodeClass, renderNode, "hasIdentityMatrix"); + } + + @Override + public void getMatrix(Matrix outMatrix) { + callInstanceMethod(renderNodeClass, renderNode, "getMatrix", from(Matrix.class, outMatrix)); + } + + @Override + public void getInverseMatrix(Matrix outMatrix) { + callInstanceMethod( + renderNodeClass, renderNode, "getInverseMatrix", from(Matrix.class, outMatrix)); + } + } + + /** Provides access to {@link android.graphics.RenderNode}. */ + @TargetApi(Q) + private static final class RenderNodeAccessPostQ implements RenderNodeAccess { + private final RenderNode renderNode; + + private RenderNodeAccessPostQ(String name) { + renderNode = new RenderNode(name); + } + + @Override + public boolean setAlpha(float alpha) { + return renderNode.setAlpha(alpha); + } + + @Override + public float getAlpha() { + return renderNode.getAlpha(); + } + + @Override + public boolean setCameraDistance(float cameraDistance) { + return renderNode.setCameraDistance(cameraDistance); + } + + @Override + public float getCameraDistance() { + return renderNode.getCameraDistance(); + } + + @Override + public boolean setClipToOutline(boolean clipToOutline) { + return renderNode.setClipToOutline(clipToOutline); + } + + @Override + public boolean getClipToOutline() { + return renderNode.getClipToOutline(); + } + + @Override + public boolean setElevation(float lift) { + return renderNode.setElevation(lift); + } + + @Override + public float getElevation() { + return renderNode.getElevation(); + } + + @Override + public boolean setHasOverlappingRendering(boolean overlappingRendering) { + return renderNode.setHasOverlappingRendering(overlappingRendering); + } + + @Override + public boolean hasOverlappingRendering() { + return renderNode.hasOverlappingRendering(); + } + + @Override + public boolean setRotationZ(float rotationZ) { + return renderNode.setRotationZ(rotationZ); + } + + @Override + public float getRotationZ() { + return renderNode.getRotationZ(); + } + + @Override + public boolean setRotationX(float rotationX) { + return renderNode.setRotationX(rotationX); + } + + @Override + public float getRotationX() { + return renderNode.getRotationX(); + } + + @Override + public boolean setRotationY(float rotationY) { + return renderNode.setRotationY(rotationY); + } + + @Override + public float getRotationY() { + return renderNode.getRotationY(); + } + + @Override + public boolean setScaleX(float scaleX) { + return renderNode.setScaleX(scaleX); + } + + @Override + public float getScaleX() { + return renderNode.getScaleX(); + } + + @Override + public boolean setScaleY(float scaleY) { + return renderNode.setScaleY(scaleY); + } + + @Override + public float getScaleY() { + return renderNode.getScaleY(); + } + + @Override + public boolean setTranslationX(float translationX) { + return renderNode.setTranslationX(translationX); + } + + @Override + public boolean setTranslationY(float translationY) { + return renderNode.setTranslationY(translationY); + } + + @Override + public boolean setTranslationZ(float translationZ) { + return renderNode.setTranslationZ(translationZ); + } + + @Override + public float getTranslationX() { + return renderNode.getTranslationX(); + } + + @Override + public float getTranslationY() { + return renderNode.getTranslationY(); + } + + @Override + public float getTranslationZ() { + return renderNode.getTranslationZ(); + } + + @Override + public boolean isPivotExplicitlySet() { + return renderNode.isPivotExplicitlySet(); + } + + @Override + public boolean setPivotX(float pivotX) { + return renderNode.setPivotX(pivotX); + } + + @Override + public float getPivotX() { + return renderNode.getPivotX(); + } + + @Override + public boolean setPivotY(float pivotY) { + return renderNode.setPivotY(pivotY); + } + + @Override + public float getPivotY() { + return renderNode.getPivotY(); + } + + @Override + public boolean hasIdentityMatrix() { + return renderNode.hasIdentityMatrix(); + } + + @Override + public void getMatrix(Matrix outMatrix) { + renderNode.getMatrix(outMatrix); + } + + @Override + public void getInverseMatrix(Matrix outMatrix) { + renderNode.getInverseMatrix(outMatrix); + } + } +} diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDatePickerDialog.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDatePickerDialog.java index 2b242fd0c63..84fc3985848 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDatePickerDialog.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDatePickerDialog.java @@ -2,7 +2,6 @@ import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.KITKAT_WATCH; -import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.N; import static org.robolectric.shadow.api.Shadow.invokeConstructor; import static org.robolectric.util.ReflectionHelpers.ClassParameter; @@ -27,24 +26,6 @@ public class ShadowDatePickerDialog extends ShadowAlertDialog { @RealObject protected DatePickerDialog realDatePickerDialog; private Calendar calendar; - @Implementation(maxSdk = M) - protected void __constructor__( - Context context, - int theme, - DatePickerDialog.OnDateSetListener callBack, - int year, - int monthOfYear, - int dayOfMonth) { - - invokeConstructor(DatePickerDialog.class, realDatePickerDialog, - ClassParameter.from(Context.class, context), - ClassParameter.from(int.class, theme), - ClassParameter.from(DatePickerDialog.OnDateSetListener.class, callBack), - ClassParameter.from(int.class, year), - ClassParameter.from(int.class, monthOfYear), - ClassParameter.from(int.class, dayOfMonth)); - } - @Implementation(minSdk = N) protected void __constructor__( Context context, diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyManager.java index 19a708dc87e..1828e2f9e4e 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyManager.java @@ -98,6 +98,7 @@ public class ShadowDevicePolicyManager { private long maximumTimeToLock = 0; private boolean cameraDisabled; private boolean isActivePasswordSufficient; + private boolean isUniqueDeviceAttestationSupported; @PasswordComplexity private int passwordComplexity; private int wipeCalled; @@ -294,6 +295,15 @@ protected boolean isUninstallBlocked(@Nullable ComponentName admin, String packa return uninstallBlockedPackages.contains(packageName); } + public void setIsUniqueDeviceAttestationSupported(boolean supported) { + isUniqueDeviceAttestationSupported = supported; + } + + @Implementation(minSdk = R) + protected boolean isUniqueDeviceAttestationSupported() { + return isUniqueDeviceAttestationSupported; + } + /** @see #setDeviceOwner(ComponentName) */ @Implementation(minSdk = JELLY_BEAN_MR2) protected String getDeviceOwner() { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRenderNode.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRenderNode.java index 69ebf7f309f..75772d9a115 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRenderNode.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRenderNode.java @@ -3,6 +3,9 @@ import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.P; +import android.graphics.Camera; +import android.graphics.Matrix; +import android.graphics.Rect; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; @@ -12,6 +15,8 @@ minSdk = LOLLIPOP, maxSdk = P) public class ShadowRenderNode { + private static final float NON_ZERO_EPSILON = 0.001f; + private float alpha = 1f; private float cameraDistance; private boolean clipToOutline; @@ -28,6 +33,10 @@ public class ShadowRenderNode { private float translationX; private float translationY; private float translationZ; + private int left; + private int top; + private int right; + private int bottom; @Implementation protected boolean setAlpha(float alpha) { @@ -177,6 +186,14 @@ protected boolean isPivotExplicitlySet() { return pivotExplicitlySet; } + @Implementation + protected boolean resetPivot() { + this.pivotExplicitlySet = false; + this.pivotX = 0; + this.pivotY = 0; + return true; + } + @Implementation protected boolean setPivotX(float pivotX) { this.pivotX = pivotX; @@ -201,6 +218,144 @@ protected float getPivotY() { return pivotY; } + @Implementation + protected boolean setLeft(int left) { + this.left = left; + return true; + } + + @Implementation + protected int getLeft() { + return left; + } + + @Implementation + protected boolean setTop(int top) { + this.top = top; + return true; + } + + @Implementation + protected int getTop() { + return top; + } + + @Implementation + protected boolean setRight(int right) { + this.right = right; + return true; + } + + @Implementation + protected int getRight() { + return right; + } + + @Implementation + protected boolean setBottom(int bottom) { + this.bottom = bottom; + return true; + } + + @Implementation + protected int getBottom() { + return bottom; + } + + @Implementation + protected int getWidth() { + return right - left; + } + + @Implementation + protected int getHeight() { + return bottom - top; + } + + @Implementation + protected boolean setLeftTopRightBottom(int left, int top, int right, int bottom) { + return setPosition(left, top, right, bottom); + } + + @Implementation + protected boolean setPosition(int left, int top, int right, int bottom) { + this.left = left; + this.top = top; + this.right = right; + this.bottom = bottom; + return true; + } + + @Implementation + protected boolean setPosition(Rect position) { + this.left = position.left; + this.top = position.top; + this.right = position.right; + this.bottom = position.bottom; + return true; + } + + @Implementation + protected boolean offsetLeftAndRight(int offset) { + this.left += offset; + this.right += offset; + return true; + } + + @Implementation + protected boolean offsetTopAndBottom(int offset) { + this.top += offset; + this.bottom += offset; + return true; + } + + @Implementation + protected void getInverseMatrix(Matrix matrix) { + if (!isMatrixEnabled()) { + return; + } + getMatrix(matrix); + matrix.invert(matrix); + } + + @Implementation + protected void getMatrix(Matrix matrix) { + if (!isMatrixEnabled()) { + return; + } + if (!pivotExplicitlySet) { + pivotX = getWidth() / 2f; + pivotY = getHeight() / 2f; + } + matrix.reset(); + if (isZero(rotationX) && isZero(rotationY)) { + matrix.setTranslate(translationX, translationY); + matrix.preRotate(rotation, pivotX, pivotY); + matrix.preScale(scaleX, scaleY, pivotX, pivotY); + } else { + matrix.preScale(scaleX, scaleY, pivotX, pivotY); + Camera camera = new Camera(); + camera.rotateX(rotationX); + camera.rotateY(rotationY); + camera.rotateZ(-rotation); + Matrix transform = new Matrix(); + camera.getMatrix(transform); + transform.preTranslate(-pivotX, -pivotY); + transform.postTranslate(pivotX + translationX, pivotY + translationY); + matrix.postConcat(transform); + } + } + + @Implementation + protected boolean hasIdentityMatrix() { + if (!isMatrixEnabled()) { + return true; + } + Matrix matrix = new Matrix(); + getMatrix(matrix); + return matrix.isIdentity(); + } + @Implementation protected boolean isValid() { return true; @@ -229,4 +384,13 @@ protected static boolean nSetLayerType(long renderNode, int layerType) { protected static boolean nSetLayerPaint(long renderNode, long paint) { return true; } + + private static boolean isZero(float value) { + return Math.abs(value) <= NON_ZERO_EPSILON; + } + + // Temporarily allow disabling the matrix calculation during migration. + private static boolean isMatrixEnabled() { + return Boolean.parseBoolean(System.getProperty("robolectric.rendernode.enableMatrix", "false")); + } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRenderNodeQ.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRenderNodeQ.java index adefc71f435..62ed4b1c9f1 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRenderNodeQ.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRenderNodeQ.java @@ -1,5 +1,8 @@ package org.robolectric.shadows; +import android.graphics.Camera; +import android.graphics.Matrix; +import android.graphics.Rect; import android.graphics.RenderNode; import android.os.Build; import org.robolectric.annotation.Implementation; @@ -7,6 +10,8 @@ @Implements(value = RenderNode.class, isInAndroidSdk = false, minSdk = Build.VERSION_CODES.Q) public class ShadowRenderNodeQ { + private static final float NON_ZERO_EPSILON = 0.001f; + private float alpha = 1f; private float cameraDistance; private boolean clipToOutline; @@ -23,6 +28,10 @@ public class ShadowRenderNodeQ { private float translationX; private float translationY; private float translationZ; + private int left; + private int top; + private int right; + private int bottom; @Implementation protected boolean setAlpha(float alpha) { @@ -172,6 +181,14 @@ protected boolean isPivotExplicitlySet() { return pivotExplicitlySet; } + @Implementation + protected boolean resetPivot() { + this.pivotExplicitlySet = false; + this.pivotX = 0; + this.pivotY = 0; + return true; + } + @Implementation protected boolean setPivotX(float pivotX) { this.pivotX = pivotX; @@ -196,8 +213,155 @@ protected float getPivotY() { return pivotY; } + @Implementation + protected boolean setLeft(int left) { + this.left = left; + return true; + } + + @Implementation + protected int getLeft() { + return left; + } + + @Implementation + protected boolean setTop(int top) { + this.top = top; + return true; + } + + @Implementation + protected int getTop() { + return top; + } + + @Implementation + protected boolean setRight(int right) { + this.right = right; + return true; + } + + @Implementation + protected int getRight() { + return right; + } + + @Implementation + protected boolean setBottom(int bottom) { + this.bottom = bottom; + return true; + } + + @Implementation + protected int getBottom() { + return bottom; + } + + @Implementation + protected int getWidth() { + return right - left; + } + + @Implementation + protected int getHeight() { + return bottom - top; + } + + @Implementation + protected boolean setLeftTopRightBottom(int left, int top, int right, int bottom) { + return setPosition(left, top, right, bottom); + } + + @Implementation + protected boolean setPosition(int left, int top, int right, int bottom) { + this.left = left; + this.top = top; + this.right = right; + this.bottom = bottom; + return true; + } + + @Implementation + protected boolean setPosition(Rect position) { + this.left = position.left; + this.top = position.top; + this.right = position.right; + this.bottom = position.bottom; + return true; + } + + @Implementation + protected boolean offsetLeftAndRight(int offset) { + this.left += offset; + this.right += offset; + return true; + } + + @Implementation + protected boolean offsetTopAndBottom(int offset) { + this.top += offset; + this.bottom += offset; + return true; + } + + @Implementation + protected void getInverseMatrix(Matrix matrix) { + if (!isMatrixEnabled()) { + return; + } + getMatrix(matrix); + matrix.invert(matrix); + } + + @Implementation + protected void getMatrix(Matrix matrix) { + if (!isMatrixEnabled()) { + return; + } + if (!pivotExplicitlySet) { + pivotX = getWidth() / 2f; + pivotY = getHeight() / 2f; + } + matrix.reset(); + if (isZero(rotationX) && isZero(rotationY)) { + matrix.setTranslate(translationX, translationY); + matrix.preRotate(rotation, pivotX, pivotY); + matrix.preScale(scaleX, scaleY, pivotX, pivotY); + } else { + matrix.preScale(scaleX, scaleY, pivotX, pivotY); + Camera camera = new Camera(); + camera.rotateX(rotationX); + camera.rotateY(rotationY); + camera.rotateZ(-rotation); + Matrix transform = new Matrix(); + camera.getMatrix(transform); + transform.preTranslate(-pivotX, -pivotY); + transform.postTranslate(pivotX + translationX, pivotY + translationY); + matrix.postConcat(transform); + } + } + + @Implementation + protected boolean hasIdentityMatrix() { + if (!isMatrixEnabled()) { + return true; + } + Matrix matrix = new Matrix(); + getMatrix(matrix); + return matrix.isIdentity(); + } + @Implementation protected static boolean nIsValid(long n) { return true; } + + private static boolean isZero(float value) { + return Math.abs(value) <= NON_ZERO_EPSILON; + } + + // Temporarily allow disabling the matrix calculation during migration. + private static boolean isMatrixEnabled() { + return Boolean.parseBoolean(System.getProperty("robolectric.rendernode.enableMatrix", "false")); + } }