From c0c8e9869dcfeaa08b33d8ff2119e7b92ffd5006 Mon Sep 17 00:00:00 2001 From: Michael Hoisie Date: Sun, 14 Apr 2024 11:27:42 -0700 Subject: Use Bitmap.copyPixelsFromBuffer to copy image data ImageReader in HW rendering When HardwareRenderer is used, the underlying Bitmap data is stored as premultiplied alpha. Previously, when that data is copied to an Android Bitmap object, Bitmap.setPixels was used, which assumed the int array passed was un-premultiplied data. Update to use Bitmap.copyPixelsFromBuffer, which does not make any assumptions about the underlying data. By default Android bitmaps are stored as premultiplied alpha. This fixes an issue with alpha blending in HW rendering. PiperOrigin-RevId: 624739361 --- .../robolectric/shadows/HardwareRenderingScreenshot.java | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/HardwareRenderingScreenshot.java b/shadows/framework/src/main/java/org/robolectric/shadows/HardwareRenderingScreenshot.java index fea0a9a97..fbbe12385 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/HardwareRenderingScreenshot.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/HardwareRenderingScreenshot.java @@ -16,7 +16,6 @@ import android.util.DisplayMetrics; import android.view.Surface; import android.view.View; import com.android.internal.R; -import java.nio.IntBuffer; import org.robolectric.annotation.GraphicsMode; import org.robolectric.util.ReflectionHelpers; @@ -71,21 +70,8 @@ public final class HardwareRenderingScreenshot { renderer.setContentRoot(node); renderer.createRenderRequest().syncAndDraw(); - - int[] renderPixels = new int[width * height]; - Plane[] planes = nativeImage.getPlanes(); - IntBuffer srcBuff = planes[0].getBuffer().asIntBuffer(); - srcBuff.get(renderPixels); - - destBitmap.setPixels( - renderPixels, - /* offset= */ 0, - /* stride= */ width, - /* x= */ 0, - /* y= */ 0, - width, - height); + destBitmap.copyPixelsFromBuffer(planes[0].getBuffer()); surface.release(); } } -- cgit v1.2.3 From 2720630fde3f4e3d17a3b3c55e8f05fada5b950b Mon Sep 17 00:00:00 2001 From: Soonil Nagarkar Date: Mon, 15 Apr 2024 12:41:14 -0700 Subject: Fix ShadowLocationManager time comparison. ShadowLocationManager should be using elapsed realtime not system time for comparisons. PiperOrigin-RevId: 625049478 --- .../test/java/org/robolectric/shadows/ShadowLocationManagerTest.java | 4 ++-- .../src/main/java/org/robolectric/shadows/ShadowLocationManager.java | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowLocationManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowLocationManagerTest.java index e9e51f909..2c7bb282f 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowLocationManagerTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowLocationManagerTest.java @@ -1224,9 +1224,9 @@ public class ShadowLocationManagerTest { @Test public void testSimulateLocation_FastestInterval() { Location loc1 = createLocation(MY_PROVIDER); - loc1.setTime(1); + loc1.setElapsedRealtimeNanos(1000000); Location loc2 = createLocation(MY_PROVIDER); - loc2.setTime(10); + loc2.setElapsedRealtimeNanos(10000000); TestLocationListener myListener = new TestLocationListener(); diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocationManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocationManager.java index d33e639c6..02d6b1776 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocationManager.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocationManager.java @@ -1968,7 +1968,9 @@ public class ShadowLocationManager { ArrayList deliverableLocations = new ArrayList<>(locations.length); for (Location location : locations) { if (lastDeliveredLocation != null) { - if (location.getTime() - lastDeliveredLocation.getTime() + if (NANOSECONDS.toMillis( + location.getElapsedRealtimeNanos() + - lastDeliveredLocation.getElapsedRealtimeNanos()) < request.getMinUpdateIntervalMillis()) { Log.w(TAG, "location rejected for simulated delivery - too fast"); continue; -- cgit v1.2.3 From 6970abf2416fd09ac8fb0d4d993b20aaa2834bfe Mon Sep 17 00:00:00 2001 From: Michael Hoisie Date: Tue, 16 Apr 2024 09:07:00 -0700 Subject: Rename `robolectric.screenshot.hwrdr.native` to `robolectric.pixelCopyRenderMode` The system property `robolectric.screenshot.hwrdr.native` is starting to be used for some screenshot tests in external Gradle projects. The goal was to have this be a short-lived internal system property. However, it may need to persist until HW rendering is more mature (e.g. supported on all RNG SDK levels) and can support all relevant PixelCopy use cases. Rename the system property to `robolectric.pixelCopyRenderMode` to make it clear and unambiguous what the effect of the system property will be. If `robolectric.pixelCopyRenderMode` is set to `hardware`, HW rendering will be used for PixelCopy operations. Otherwise, SW rendering will be used for PixelCopy. Otherwise it has no effect. PiperOrigin-RevId: 625351004 --- .../integration/roborazzi/RoborazziCaptureTest.kt | 31 ++++++++++------------ .../shadows/HardwareRenderingScreenshot.java | 4 +-- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/integration_tests/roborazzi/src/test/java/org/robolectric/integration/roborazzi/RoborazziCaptureTest.kt b/integration_tests/roborazzi/src/test/java/org/robolectric/integration/roborazzi/RoborazziCaptureTest.kt index 9adbe305e..6a54739fd 100644 --- a/integration_tests/roborazzi/src/test/java/org/robolectric/integration/roborazzi/RoborazziCaptureTest.kt +++ b/integration_tests/roborazzi/src/test/java/org/robolectric/integration/roborazzi/RoborazziCaptureTest.kt @@ -51,12 +51,7 @@ class RoborazziCaptureTest { RoborazziRule.Options( outputDirectoryPath = OUTPUT_DIRECTORY_PATH, roborazziOptions = - RoborazziOptions( - recordOptions = - RoborazziOptions.RecordOptions( - resizeScale = 0.5, - ) - ) + RoborazziOptions(recordOptions = RoborazziOptions.RecordOptions(resizeScale = 0.5)), ) ) @@ -99,13 +94,16 @@ class RoborazziCaptureTest { |run `./gradlew integration_tests:roborazzi:recordRoborazziDebug -Drobolectric.alwaysIncludeVariantMarkersInTestName=true` and commit the changes. |""" .trimMargin(), - e + e, ) } } companion object { + // TODO(hoisie): `robolectric.screenshot.hwrdr.native` is obsolete, remove it after the next + // Robolectric point release. const val USE_HARDWARE_RENDERER_NATIVE_ENV = "robolectric.screenshot.hwrdr.native" + const val PIXEL_COPY_RENDER_MODE = "robolectric.pixelCopyRenderMode" } } @@ -113,17 +111,14 @@ private fun registerActivityToPackageManager(activity: String) { val appContext: Application = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as Application Shadows.shadowOf(appContext.packageManager) - .addActivityIfNotPresent( - ComponentName( - appContext.packageName, - activity, - ) - ) + .addActivityIfNotPresent(ComponentName(appContext.packageName, activity)) } private fun hardwareRendererEnvironment(block: () -> Unit) { val originalHwrdrOption = System.getProperty(RoborazziCaptureTest.USE_HARDWARE_RENDERER_NATIVE_ENV, null) + val originalPixelCopyOption = + System.getProperty(RoborazziCaptureTest.PIXEL_COPY_RENDER_MODE, null) // This cause ClassNotFoundException: java.nio.NioUtils // TODO: Remove comment out after fix this issue // https://github.com/robolectric/robolectric/issues/8081#issuecomment-1858726896 @@ -133,8 +128,10 @@ private fun hardwareRendererEnvironment(block: () -> Unit) { } finally { if (originalHwrdrOption == null) { System.clearProperty(RoborazziCaptureTest.USE_HARDWARE_RENDERER_NATIVE_ENV) + System.clearProperty(RoborazziCaptureTest.PIXEL_COPY_RENDER_MODE) } else { System.setProperty(RoborazziCaptureTest.USE_HARDWARE_RENDERER_NATIVE_ENV, originalHwrdrOption) + System.setProperty(RoborazziCaptureTest.PIXEL_COPY_RENDER_MODE, originalPixelCopyOption) } } } @@ -158,9 +155,9 @@ private class RoborazziViewWithElevationTestActivity : Activity() { }, LinearLayout.LayoutParams( LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.MATCH_PARENT + LinearLayout.LayoutParams.MATCH_PARENT, ) - .apply { setMargins(10.toDp(), 10.toDp(), 10.toDp(), 10.toDp()) } + .apply { setMargins(10.toDp(), 10.toDp(), 10.toDp(), 10.toDp()) }, ) } ) @@ -181,9 +178,9 @@ private class RoborazziDialogTestActivity : Activity() { TextView(this.context).apply { text = "Under the dialog" }, LinearLayout.LayoutParams( LinearLayout.LayoutParams.WRAP_CONTENT, - LinearLayout.LayoutParams.WRAP_CONTENT + LinearLayout.LayoutParams.WRAP_CONTENT, ) - .apply { setMargins(10.toDp(), 10.toDp(), 10.toDp(), 10.toDp()) } + .apply { setMargins(10.toDp(), 10.toDp(), 10.toDp(), 10.toDp()) }, ) } ) diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/HardwareRenderingScreenshot.java b/shadows/framework/src/main/java/org/robolectric/shadows/HardwareRenderingScreenshot.java index fbbe12385..63e9d7bd8 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/HardwareRenderingScreenshot.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/HardwareRenderingScreenshot.java @@ -25,7 +25,7 @@ import org.robolectric.util.ReflectionHelpers; */ public final class HardwareRenderingScreenshot { - static final String USE_HARDWARE_RENDERER_NATIVE_ENV = "robolectric.screenshot.hwrdr.native"; + static final String PIXEL_COPY_RENDER_MODE = "robolectric.pixelCopyRenderMode"; private HardwareRenderingScreenshot() {} @@ -36,7 +36,7 @@ public final class HardwareRenderingScreenshot { */ static boolean canTakeScreenshot() { return VERSION.SDK_INT >= VERSION_CODES.S - && Boolean.getBoolean(HardwareRenderingScreenshot.USE_HARDWARE_RENDERER_NATIVE_ENV) + && "hardware".equalsIgnoreCase(System.getProperty(PIXEL_COPY_RENDER_MODE, "")) && ShadowView.useRealGraphics(); } -- cgit v1.2.3 From 27333928cda4b045448195c27c734992f9781b01 Mon Sep 17 00:00:00 2001 From: Michael Hoisie Date: Tue, 16 Apr 2024 10:41:39 -0700 Subject: Add a shadow method for ViewConfiguration.getScaledMaximumDrawingCacheSize Previously, ViewConfiguration.getScaledMaximumDrawingCacheSize just returned a constant value, the default max value (1536000 == 480 * 800 * 4). The result of ViewConfiguration.getScaledMaximumDrawingCacheSize is used in View.buildDrawingCacheImpl(boolean autoScale), which is used when rendering View objects. For tests that configure large screen sizes, the 1536000 may not sufficient for the cache size. Views that exceed the cache size will not be drawn. Add a shadow for ViewConfiguration.getScaledMaximumDrawingCacheSize that scales with the display size. Also, remove the shadow for ViewConfiguration.getMaximumDrawingCacheSize, the real implementation can be used instead. Some tests depend on a minimum cache size of 1536000, such as when they configure a smaller display and render a large view into it. Add some special logic that keeps the min cache size at 1536000. PiperOrigin-RevId: 625381937 --- .../shadows/ShadowViewConfigurationTest.java | 21 ++++++++++++++++++++- .../shadows/ShadowViewConfiguration.java | 17 +++++++++++++---- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowViewConfigurationTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowViewConfigurationTest.java index 41661af4a..d0dfeadfb 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowViewConfigurationTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowViewConfigurationTest.java @@ -11,6 +11,7 @@ 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; @RunWith(AndroidJUnit4.class) public class ShadowViewConfigurationTest { @@ -40,7 +41,7 @@ public class ShadowViewConfigurationTest { assertEquals(16, ViewConfiguration.getWindowTouchSlop()); assertEquals(50, ViewConfiguration.getMinimumFlingVelocity()); assertEquals(4000, ViewConfiguration.getMaximumFlingVelocity()); - assertEquals(320 * 480 * 4, ViewConfiguration.getMaximumDrawingCacheSize()); + assertEquals(480 * 800 * 4, ViewConfiguration.getMaximumDrawingCacheSize()); assertEquals(3000, ViewConfiguration.getZoomControlsTimeout()); assertEquals(500, ViewConfiguration.getGlobalActionKeyTimeout()); assertThat(ViewConfiguration.getScrollFriction()).isEqualTo(0.015f); @@ -56,6 +57,8 @@ public class ShadowViewConfigurationTest { assertEquals(16, viewConfiguration.getScaledWindowTouchSlop()); assertEquals(50, viewConfiguration.getScaledMinimumFlingVelocity()); assertEquals(4000, viewConfiguration.getScaledMaximumFlingVelocity()); + // The min value of getScaledMaximumDrawingCacheSize is 480 * 800 * 4. + assertEquals(480 * 800 * 4, viewConfiguration.getScaledMaximumDrawingCacheSize()); } @Test @@ -83,4 +86,20 @@ public class ShadowViewConfigurationTest { shadowViewConfiguration.setHasPermanentMenuKey(false); assertThat(viewConfiguration.hasPermanentMenuKey()).isFalse(); } + + @Config(qualifiers = "w420dp-h800dp-xxxhdpi") + @Test + public void getScaledMaximumFlingVelocity_scalesWithDisplaySize() { + ViewConfiguration viewConfiguration = ViewConfiguration.get(context); + int expected = 4 * (4 * 420) * (4 * 800); + assertThat(viewConfiguration.getScaledMaximumDrawingCacheSize()).isEqualTo(expected); + } + + @Config(qualifiers = "w100dp-h500dp") + @Test + public void getScaledMaximumFlingVelocity_minValue() { + ViewConfiguration viewConfiguration = ViewConfiguration.get(context); + int expected = 480 * 800 * 4; // The min value + assertThat(viewConfiguration.getScaledMaximumDrawingCacheSize()).isEqualTo(expected); + } } diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowViewConfiguration.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowViewConfiguration.java index 33f800f7d..682f53b6a 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowViewConfiguration.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowViewConfiguration.java @@ -46,7 +46,9 @@ public class ShadowViewConfiguration { private static final int DOUBLE_TAP_SLOP = 100; private static final int WINDOW_TOUCH_SLOP = 16; private static final int MAXIMUM_FLING_VELOCITY = 4000; - private static final int MAXIMUM_DRAWING_CACHE_SIZE = 320 * 480 * 4; + + // The previous hardcoded value for draw cache size. Some screenshot tests depend on this value. + private static final int MIN_MAXIMUM_DRAWING_CACHE_SIZE = 480 * 800 * 4; private int edgeSlop; private int fadingEdgeLength; @@ -57,10 +59,11 @@ public class ShadowViewConfiguration { private int pagingTouchSlop; private int doubleTapSlop; private int windowTouchSlop; + private int maximumDrawingCacheSize; private static boolean hasPermanentMenuKey = true; private void setup(Context context) { - DisplayMetrics metrics = context.getResources().getDisplayMetrics(); + final DisplayMetrics metrics = context.getResources().getDisplayMetrics(); float density = metrics.density; edgeSlop = (int) (density * ViewConfiguration.getEdgeSlop() + 0.5f); @@ -72,6 +75,12 @@ public class ShadowViewConfiguration { pagingTouchSlop = (int) (density * PAGING_TOUCH_SLOP + 0.5f); doubleTapSlop = (int) (density * DOUBLE_TAP_SLOP + 0.5f); windowTouchSlop = (int) (density * WINDOW_TOUCH_SLOP + 0.5f); + // Some screenshot tests were misconfigured and try to draw very large views onto small + // screens using SW rendering. To avoid breaking these tests, we keep the drawing cache a bit + // larger when screens are configured to be arbitrarily small. + // TODO(hoisie): Investigate removing this Math.max logic. + maximumDrawingCacheSize = + Math.max(MIN_MAXIMUM_DRAWING_CACHE_SIZE, 4 * metrics.widthPixels * metrics.heightPixels); } @Implementation @@ -174,8 +183,8 @@ public class ShadowViewConfiguration { } @Implementation - protected static int getMaximumDrawingCacheSize() { - return MAXIMUM_DRAWING_CACHE_SIZE; + protected int getScaledMaximumDrawingCacheSize() { + return maximumDrawingCacheSize; } @Implementation -- cgit v1.2.3 From fef58ca6c1ca56fe2fd49d741fae8fd25c41f9b0 Mon Sep 17 00:00:00 2001 From: Rich King Date: Wed, 17 Apr 2024 05:37:26 -0700 Subject: Provide builder to create EmergencyNumber instances. As all the constuctors are annotated with @hide as such cannot be constucted in tests without reflection. PiperOrigin-RevId: 625651303 --- .../shadows/EmergencyNumberBuilderTest.java | 38 ++++++++++++ .../shadows/EmergencyNumberBuilder.java | 71 ++++++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 robolectric/src/test/java/org/robolectric/shadows/EmergencyNumberBuilderTest.java create mode 100644 shadows/framework/src/main/java/org/robolectric/shadows/EmergencyNumberBuilder.java diff --git a/robolectric/src/test/java/org/robolectric/shadows/EmergencyNumberBuilderTest.java b/robolectric/src/test/java/org/robolectric/shadows/EmergencyNumberBuilderTest.java new file mode 100644 index 000000000..f74e5a11c --- /dev/null +++ b/robolectric/src/test/java/org/robolectric/shadows/EmergencyNumberBuilderTest.java @@ -0,0 +1,38 @@ +package org.robolectric.shadows; + +import static android.os.Build.VERSION_CODES.Q; +import static com.google.common.truth.Truth.assertThat; + +import android.telephony.emergency.EmergencyNumber; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.annotation.Config; + +/** Tests for {@link EmergencyNumberBuilder}. */ +@Config(minSdk = Q) +@RunWith(AndroidJUnit4.class) +public final class EmergencyNumberBuilderTest { + + @Test + public void testBuildEmergencyNumber() { + EmergencyNumber emergencyNumber = + EmergencyNumberBuilder.newBuilder("911", "us", "30") + .setEmergencyServiceCategories(EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE) + .addEmergencyUrn("urn") + .setEmergencyNumberSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE) + .setEmergencyCallRouting(EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL) + .build(); + + assertThat(emergencyNumber.getNumber()).isEqualTo("911"); + assertThat(emergencyNumber.getCountryIso()).isEqualTo("us"); + assertThat(emergencyNumber.getMnc()).isEqualTo("30"); + assertThat(emergencyNumber.getEmergencyServiceCategories()) + .containsExactly(EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE); + assertThat(emergencyNumber.getEmergencyUrns()).containsExactly("urn"); + assertThat(emergencyNumber.getEmergencyNumberSources()) + .containsExactly(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DATABASE); + assertThat(emergencyNumber.getEmergencyCallRouting()) + .isEqualTo(EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL); + } +} diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/EmergencyNumberBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/EmergencyNumberBuilder.java new file mode 100644 index 000000000..45ede54c0 --- /dev/null +++ b/shadows/framework/src/main/java/org/robolectric/shadows/EmergencyNumberBuilder.java @@ -0,0 +1,71 @@ +package org.robolectric.shadows; + +import static android.os.Build.VERSION_CODES.Q; + +import android.annotation.RequiresApi; +import android.telephony.emergency.EmergencyNumber; +import android.telephony.emergency.EmergencyNumber.EmergencyCallRouting; +import android.telephony.emergency.EmergencyNumber.EmergencyNumberSources; +import android.telephony.emergency.EmergencyNumber.EmergencyServiceCategories; +import java.util.ArrayList; +import java.util.List; +import org.robolectric.util.ReflectionHelpers; +import org.robolectric.util.ReflectionHelpers.ClassParameter; + +/** Builder for {@link android.telephony.emergency.EmergencyNumber}. */ +@RequiresApi(Q) +public class EmergencyNumberBuilder { + + private final String number; + private final String countryIso; + private final String mnc; + private final List emergencyUrns = new ArrayList(); + private int emergencyServiceCategories = EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED; + private int emergencyNumberSources = EmergencyNumber.EMERGENCY_NUMBER_SOURCE_DEFAULT; + private int emergencyCallRouting = EmergencyNumber.EMERGENCY_CALL_ROUTING_UNKNOWN; + + private EmergencyNumberBuilder(String number, String countryIso, String mnc) { + this.number = number; + this.countryIso = countryIso; + this.mnc = mnc; + } + + public static EmergencyNumberBuilder newBuilder(String number, String countryIso, String mnc) { + return new EmergencyNumberBuilder(number, countryIso, mnc); + } + + public EmergencyNumberBuilder setEmergencyServiceCategories( + @EmergencyServiceCategories int emergencyServiceCategories) { + this.emergencyServiceCategories = emergencyServiceCategories; + return this; + } + + public EmergencyNumberBuilder addEmergencyUrn(String emergencyUrn) { + emergencyUrns.add(emergencyUrn); + return this; + } + + public EmergencyNumberBuilder setEmergencyNumberSources( + @EmergencyNumberSources int emergencyNumberSources) { + this.emergencyNumberSources = emergencyNumberSources; + return this; + } + + public EmergencyNumberBuilder setEmergencyCallRouting( + @EmergencyCallRouting int emergencyCallRouting) { + this.emergencyCallRouting = emergencyCallRouting; + return this; + } + + public EmergencyNumber build() { + return ReflectionHelpers.callConstructor( + EmergencyNumber.class, + ClassParameter.from(String.class, number), + ClassParameter.from(String.class, countryIso), + ClassParameter.from(String.class, mnc), + ClassParameter.from(int.class, emergencyServiceCategories), + ClassParameter.from(List.class, emergencyUrns), + ClassParameter.from(int.class, emergencyNumberSources), + ClassParameter.from(int.class, emergencyCallRouting)); + } +} -- cgit v1.2.3 From f8bbdb1ea44c931938cfa2f747b0736b9f88ba7f Mon Sep 17 00:00:00 2001 From: Michael Hoisie Date: Wed, 17 Apr 2024 10:04:05 -0700 Subject: Clarify `@DoNotMock` error message in ClassInstrumentor Robolectric's ClassInstrumentor adds a @DoNotMock annotation to Android classes that originally contained the 'final' modifier. The warning message always suggested to create or enhance a Shadow for the Android class being instrumented. However, there are cases when it is preferable to create a Builder, such as when the class is a pure Java data class. Update the `@DoNotMock` message to include this nuance. Note that this @DoNotMock annotation is purely informative and requires special infrastructure to enforce, so it's not on by default for Gradle/third-party projects. PiperOrigin-RevId: 625720769 --- .../java/org/robolectric/internal/bytecode/ClassInstrumentor.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sandbox/src/main/java/org/robolectric/internal/bytecode/ClassInstrumentor.java b/sandbox/src/main/java/org/robolectric/internal/bytecode/ClassInstrumentor.java index a9b532a26..9d3d51af9 100644 --- a/sandbox/src/main/java/org/robolectric/internal/bytecode/ClassInstrumentor.java +++ b/sandbox/src/main/java/org/robolectric/internal/bytecode/ClassInstrumentor.java @@ -177,8 +177,11 @@ public class ClassInstrumentor { .visitAnnotation("Lcom/google/errorprone/annotations/DoNotMock;", true) .visit( "value", - "This class is final. Consider using the real thing, or " - + "adding/enhancing a Robolectric shadow for it."); + "This class is final. Consider either:\n" + + "1. Using the real class.\n" + + "2. If it's a pure data class, adding a Robolectric Builder for it.\n" + + "3. If it cannot function on the JVM, adding or enhancing a Robolectric" + + " Shadow for it"); } mutableClass.classNode.access = mutableClass.classNode.access & ~Opcodes.ACC_FINAL; -- cgit v1.2.3 From 9bb0d6d5a0627a2bd99cdd1fbda497c7d7aaf98e Mon Sep 17 00:00:00 2001 From: Tim Peut Date: Wed, 17 Apr 2024 10:12:50 -0700 Subject: Update some robolectric tests to no longer run on K. Robolectric is preparing to drop support for 19. PiperOrigin-RevId: 625724159 --- .../java/org/robolectric/versioning/AndroidVersionsTest.java | 11 ----------- .../java/org/robolectric/RobolectricTestRunnerSelfTest.java | 7 +++---- .../src/test/java/org/robolectric/android/BootstrapTest.java | 2 -- .../android/controller/ActivityControllerTest.java | 4 +--- .../test/java/org/robolectric/shadows/ShadowBitmapTest.java | 2 -- .../test/java/org/robolectric/shadows/ShadowNetworkTest.java | 8 -------- .../org/robolectric/shadows/ShadowSystemPropertiesTest.java | 6 ------ .../java/org/robolectric/shadows/ShadowUiAutomationTest.java | 4 ---- 8 files changed, 4 insertions(+), 40 deletions(-) diff --git a/annotations/src/test/java/org/robolectric/versioning/AndroidVersionsTest.java b/annotations/src/test/java/org/robolectric/versioning/AndroidVersionsTest.java index 670e695a3..764a7fa2d 100644 --- a/annotations/src/test/java/org/robolectric/versioning/AndroidVersionsTest.java +++ b/annotations/src/test/java/org/robolectric/versioning/AndroidVersionsTest.java @@ -155,15 +155,4 @@ public final class AndroidVersionsTest { assertThat(new AndroidVersions.L().getVersion()).isEqualTo("5.0"); assertThat(new AndroidVersions.L().isReleased()).isEqualTo(true); } - - @Test - public void testStandardInitializationK() { - assertThat(AndroidVersions.K.SDK_INT).isEqualTo(19); - assertThat(AndroidVersions.K.SHORT_CODE).isEqualTo("K"); - assertThat(AndroidVersions.K.VERSION).isEqualTo("4.4"); - assertThat(new AndroidVersions.K().getSdkInt()).isEqualTo(19); - assertThat(new AndroidVersions.K().getShortCode()).isEqualTo("K"); - assertThat(new AndroidVersions.K().getVersion()).isEqualTo("4.4"); - assertThat(new AndroidVersions.K().isReleased()).isEqualTo(true); - } } diff --git a/robolectric/src/test/java/org/robolectric/RobolectricTestRunnerSelfTest.java b/robolectric/src/test/java/org/robolectric/RobolectricTestRunnerSelfTest.java index f1e71b978..a60af551b 100644 --- a/robolectric/src/test/java/org/robolectric/RobolectricTestRunnerSelfTest.java +++ b/robolectric/src/test/java/org/robolectric/RobolectricTestRunnerSelfTest.java @@ -79,11 +79,10 @@ public class RobolectricTestRunnerSelfTest { } @Test - @Config(sdk = Build.VERSION_CODES.KITKAT) + @Config(sdk = Build.VERSION_CODES.LOLLIPOP) public void testVersionConfiguration() { - assertThat(Build.VERSION.SDK_INT) - .isEqualTo(Build.VERSION_CODES.KITKAT); - assertThat(Build.VERSION.RELEASE).isEqualTo("4.4"); + assertThat(Build.VERSION.SDK_INT).isEqualTo(Build.VERSION_CODES.LOLLIPOP); + assertThat(Build.VERSION.RELEASE).isEqualTo("5.0.2"); } @Test public void hamcrestMatchersDontBlowUpDuringLinking() throws Exception { diff --git a/robolectric/src/test/java/org/robolectric/android/BootstrapTest.java b/robolectric/src/test/java/org/robolectric/android/BootstrapTest.java index 3e9eb6a93..6dd5256b9 100644 --- a/robolectric/src/test/java/org/robolectric/android/BootstrapTest.java +++ b/robolectric/src/test/java/org/robolectric/android/BootstrapTest.java @@ -32,7 +32,6 @@ import static android.content.res.Configuration.UI_MODE_NIGHT_YES; import static android.content.res.Configuration.UI_MODE_TYPE_APPLIANCE; import static android.content.res.Configuration.UI_MODE_TYPE_MASK; import static android.content.res.Configuration.UI_MODE_TYPE_NORMAL; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.N; import static android.os.Build.VERSION_CODES.O; import static android.view.Surface.ROTATION_0; @@ -311,7 +310,6 @@ public class BootstrapTest { } @Test - @Config(sdk = KITKAT) public void applyQualifiers_rtlPseudoLocale_shouldSetLayoutDirection() { Bootstrap.applyQualifiers( "ar-rXB", RuntimeEnvironment.getApiLevel(), configuration, displayMetrics); diff --git a/robolectric/src/test/java/org/robolectric/android/controller/ActivityControllerTest.java b/robolectric/src/test/java/org/robolectric/android/controller/ActivityControllerTest.java index 09aec11c9..6fe75980f 100644 --- a/robolectric/src/test/java/org/robolectric/android/controller/ActivityControllerTest.java +++ b/robolectric/src/test/java/org/robolectric/android/controller/ActivityControllerTest.java @@ -15,7 +15,6 @@ import android.app.WindowConfiguration; import android.content.ComponentName; import android.content.Intent; import android.content.res.Configuration; -import android.os.Build; import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.Handler; @@ -253,8 +252,7 @@ public class ActivityControllerTest { } @Test - @Config(sdk = Build.VERSION_CODES.KITKAT) - public void attach_shouldWorkWithAPI19() { + public void attach_shouldWork() { MyActivity activity = Robolectric.buildActivity(MyActivity.class).create().get(); assertThat(activity).isNotNull(); } diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBitmapTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBitmapTest.java index face8d842..ceeb7ef06 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBitmapTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBitmapTest.java @@ -13,7 +13,6 @@ import android.graphics.ColorMatrixColorFilter; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Rect; -import android.os.Build; import android.os.Parcel; import android.util.DisplayMetrics; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -624,7 +623,6 @@ public class ShadowBitmapTest { bitmapOriginal.copyPixelsFromBuffer(buffer); } - @Config(sdk = Build.VERSION_CODES.KITKAT) @Test public void reconfigure_withArgb8888Bitmap_validDimensionsAndConfig_doesNotThrow() { Bitmap original = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowNetworkTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowNetworkTest.java index 53d3cbe7b..d7748729e 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowNetworkTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowNetworkTest.java @@ -1,11 +1,9 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; import static android.os.Build.VERSION_CODES.M; import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertThrows; import android.net.Network; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -29,12 +27,6 @@ public class ShadowNetworkTest { assertThat(shadowNetwork.getNetId()).isEqualTo(netId); } - @Test - @Config(sdk = KITKAT) - public void shadowNetwork_newInstance_fails_preL() { - assertThrows(IllegalStateException.class, () -> ShadowNetwork.newInstance(111)); - } - @Test @Config(minSdk = LOLLIPOP_MR1) public void bindSocketDatagramSocket_shouldNotCrash() throws Exception { diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowSystemPropertiesTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowSystemPropertiesTest.java index b4562ce7a..602cafa01 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowSystemPropertiesTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowSystemPropertiesTest.java @@ -52,12 +52,6 @@ public class ShadowSystemPropertiesTest { // The following readPropFromJarNotClassPathXX tests check build.prop is loaded from appropriate // android-all jar instead of loading build.prop from classpath aka LATEST_SDK. - @Test - @Config(sdk = 19) - public void readPropFromJarNotClassPath19() { - assertThat(SystemProperties.getInt("ro.build.version.sdk", 0)).isEqualTo(19); - } - @Test @Config(sdk = 21) public void readPropFromJarNotClassPath21() { diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowUiAutomationTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowUiAutomationTest.java index 4a3d5d527..eb9e124f7 100644 --- a/robolectric/src/test/java/org/robolectric/shadows/ShadowUiAutomationTest.java +++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowUiAutomationTest.java @@ -1,6 +1,5 @@ package org.robolectric.shadows; -import static android.os.Build.VERSION_CODES.KITKAT; import static com.google.common.truth.Truth.assertThat; import android.app.UiAutomation; @@ -14,13 +13,11 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; import org.junit.Test; import org.junit.runner.RunWith; -import org.robolectric.annotation.Config; import org.robolectric.annotation.LooperMode; /** Test for {@link ShadowUiAutomation}. */ @RunWith(AndroidJUnit4.class) public class ShadowUiAutomationTest { - @Config(sdk = KITKAT) @Test public void setAnimationScale_zero() throws Exception { ShadowUiAutomation.setAnimationScaleCompat(0); @@ -32,7 +29,6 @@ public class ShadowUiAutomationTest { assertThat(Settings.Global.getFloat(cr, Settings.Global.WINDOW_ANIMATION_SCALE)).isEqualTo(0); } - @Config(sdk = KITKAT) @Test public void setAnimationScale_one() throws Exception { ShadowUiAutomation.setAnimationScaleCompat(1); -- cgit v1.2.3 From e54bde4a2055e3cfcb63b5f4f1e9e6db5d1f8928 Mon Sep 17 00:00:00 2001 From: Julia Sullivan Date: Wed, 17 Apr 2024 11:01:25 -0700 Subject: Update ShadowMotionEvent and AndroidTestEnvironment to support Android V In ShadowMotionEvent, nativeGetXOffset was replaced by nativeGetRawXOffset, and nativeGetYOffset nativeGetRawYOffset. The logic for MotionEvent.split was moved into native code, so it needs to be shadowed starting in V. Also, the signature of AppCompatCallbacks.install was updated to include an extra long parameter. PiperOrigin-RevId: 625741342 --- .../android/internal/AndroidTestEnvironment.java | 17 ++- .../java/org/robolectric/shadows/NativeInput.java | 133 ++++++++++++++++++++- .../org/robolectric/shadows/ShadowMotionEvent.java | 30 ++++- 3 files changed, 166 insertions(+), 14 deletions(-) diff --git a/robolectric/src/main/java/org/robolectric/android/internal/AndroidTestEnvironment.java b/robolectric/src/main/java/org/robolectric/android/internal/AndroidTestEnvironment.java index bf626e4a9..e21545d9f 100755 --- a/robolectric/src/main/java/org/robolectric/android/internal/AndroidTestEnvironment.java +++ b/robolectric/src/main/java/org/robolectric/android/internal/AndroidTestEnvironment.java @@ -100,8 +100,10 @@ import org.robolectric.shadows.ShadowView; import org.robolectric.util.Logger; import org.robolectric.util.PerfStatsCollector; import org.robolectric.util.ReflectionHelpers; +import org.robolectric.util.ReflectionHelpers.ClassParameter; import org.robolectric.util.Scheduler; import org.robolectric.util.TempDirectory; +import org.robolectric.versioning.AndroidVersions; import org.robolectric.versioning.AndroidVersions.V; @SuppressLint("NewApi") @@ -399,9 +401,18 @@ public class AndroidTestEnvironment implements TestEnvironment { populateAssetPaths(appResources.getAssets(), appManifest); } - // circument the 'No Compatibility callbacks set!' log. See #8509 - if (RuntimeEnvironment.getApiLevel() >= VERSION_CODES.R) { - AppCompatCallbacks.install(new long[0]); + // Circumvent the 'No Compatibility callbacks set!' log. See #8509 + if (apiLevel >= AndroidVersions.V.SDK_INT) { + // Adds loggableChanges parameter. + ReflectionHelpers.callStaticMethod( + AppCompatCallbacks.class, + "install", + ClassParameter.from(long[].class, new long[0]), + ClassParameter.from(long[].class, new long[0])); + } else if (apiLevel >= AndroidVersions.R.SDK_INT) { + // Invoke the previous version. + ReflectionHelpers.callStaticMethod( + AppCompatCallbacks.class, "install", ClassParameter.from(long[].class, new long[0])); } PerfStatsCollector.getInstance() diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/NativeInput.java b/shadows/framework/src/main/java/org/robolectric/shadows/NativeInput.java index d136be83d..cbda24865 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/NativeInput.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/NativeInput.java @@ -3,6 +3,7 @@ package org.robolectric.shadows; import static com.google.common.base.Preconditions.checkState; import static org.robolectric.shadows.NativeAndroidInput.AINPUT_EVENT_TYPE_MOTION; import static org.robolectric.shadows.NativeAndroidInput.AINPUT_SOURCE_CLASS_POINTER; +import static org.robolectric.shadows.NativeAndroidInput.AKEY_EVENT_FLAG_CANCELED; import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_ACTION_CANCEL; import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_ACTION_DOWN; import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_ACTION_MASK; @@ -28,6 +29,7 @@ import android.view.MotionEvent.PointerProperties; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Optional; import org.robolectric.res.android.Ref; /** @@ -64,11 +66,11 @@ public class NativeInput { */ static class AInputEvent {} - /* + /** * Pointer coordinate data. * - * Deviates from original platform implementation to store axises in simple SparseArray as opposed - * to complicated bitset + array arrangement. + *

Deviates from original platform implementation to store axises in simple SparseArray as + * opposed to complicated bitset + array arrangement. */ static class PointerCoords { @@ -272,9 +274,7 @@ public class NativeInput { // nsecs_t mEventTime; } - /* - * Motion events. - */ + /** Motion events. */ static class MotionEvent extends InputEvent { // constants copied from android bionic/libc/include/math.h @@ -284,6 +284,18 @@ public class NativeInput { @SuppressWarnings("FloatingPointLiteralPrecision") private static final double M_PI_2 = 1.57079632679489661923f; /* pi/2 */ + public static final int ACTION_MASK = 0xff; + public static final int ACTION_DOWN = 0; + public static final int ACTION_UP = 1; + public static final int ACTION_MOVE = 2; + public static final int ACTION_CANCEL = 3; + public static final int ACTION_POINTER_DOWN = 5; + public static final int ACTION_POINTER_UP = 6; + private static final int HISTORY_CURRENT = -0x80000000; + public static final int FLAG_CANCELED = 0x20; + public static final int ACTION_POINTER_INDEX_MASK = 0xff00; + public static final int ACTION_POINTER_INDEX_SHIFT = 8; + private int mAction; private int mActionButton; private int mFlags; @@ -540,6 +552,115 @@ public class NativeInput { return getHistoricalAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, pointerIndex, historicalIndex); } + private android.view.MotionEvent.PointerCoords[] getNativePointerCoords() { + android.view.MotionEvent.PointerCoords[] nativePointerCoords = + new android.view.MotionEvent.PointerCoords[mSamplePointerCoords.size()]; + for (int i = 0; i < mSamplePointerCoords.size(); i++) { + android.view.MotionEvent.PointerCoords newPc = new android.view.MotionEvent.PointerCoords(); + PointerCoords pc = mSamplePointerCoords.get(i); + newPc.x = pc.getX(); + newPc.y = pc.getY(); + newPc.setAxisValue(AMOTION_EVENT_AXIS_X, pc.getX()); + newPc.setAxisValue(AMOTION_EVENT_AXIS_Y, pc.getY()); + nativePointerCoords[i] = newPc; + } + return nativePointerCoords; + } + + private int resolveActionForSplitMotionEvent( + int action, + int flags, + PointerProperties[] pointerProperties, + PointerProperties[] splitPointerProperties) { + int maskedAction = getActionMasked(); + if (maskedAction != AMOTION_EVENT_ACTION_POINTER_DOWN + && maskedAction != AMOTION_EVENT_ACTION_POINTER_UP) { + // The action is unaffected by splitting this motion event. + return action; + } + + int actionIndex = getActionIndex(); + + int affectedPointerId = pointerProperties[actionIndex].id; + Optional splitActionIndex = Optional.empty(); + for (int i = 0; i < splitPointerProperties.length; i++) { + if (affectedPointerId == splitPointerProperties[i].id) { + splitActionIndex = Optional.of(i); + break; + } + } + if (!splitActionIndex.isPresent()) { + // The affected pointer is not part of the split motion event. + return AMOTION_EVENT_ACTION_MOVE; + } + + if (splitPointerProperties.length > 1) { + return maskedAction | (splitActionIndex.get() << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); + } + + if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) { + return ((flags & AKEY_EVENT_FLAG_CANCELED) != 0) + ? AMOTION_EVENT_ACTION_CANCEL + : AMOTION_EVENT_ACTION_UP; + } + return AMOTION_EVENT_ACTION_DOWN; + } + + public android.view.MotionEvent nativeSplit(int idBits) { + final int pointerCount = getPointerCount(); + List pointerProperties = new ArrayList<>(mPointerProperties); + final PointerProperties[] pp = pointerProperties.toArray(new PointerProperties[pointerCount]); + final android.view.MotionEvent.PointerCoords[] pc = getNativePointerCoords(); + + List splitPointerProperties = new ArrayList<>(); + List splitPointerCoords = new ArrayList<>(); + + // Split the matching ids out for the new MotionEvent. + for (int i = 0; i < pointerCount; i++) { + final int idBit = 1 << pp[i].id; + if ((idBit & idBits) != 0) { + splitPointerProperties.add(pp[i]); + } + } + for (int i = 0; i < pc.length; i++) { + final int idBit = 1 << pp[i % pointerCount].id; + if ((idBit & idBits) != 0) { + splitPointerCoords.add(pc[i]); + } + } + + // Convert them to arrays + PointerProperties[] splitPointerPropertiesArray = + new PointerProperties[splitPointerProperties.size()]; + splitPointerProperties.toArray(splitPointerPropertiesArray); + + android.view.MotionEvent.PointerCoords[] splitPointerCoordsArray = + new android.view.MotionEvent.PointerCoords[splitPointerCoords.size()]; + splitPointerCoords.toArray(splitPointerCoordsArray); + + int splitAction = + resolveActionForSplitMotionEvent( + getAction(), getFlags(), pp, splitPointerPropertiesArray); + + android.view.MotionEvent newEvent = + android.view.MotionEvent.obtain( + getDownTime(), + getEventTime(), + splitAction, + splitPointerProperties.size(), + splitPointerPropertiesArray, + splitPointerCoordsArray, + getMetaState(), + getButtonState(), + getXPrecision(), + getYPrecision(), + getDeviceId(), + getEdgeFlags(), + getSource(), + getFlags()); + return newEvent; + } + public int findPointerIndex(int pointerId) { int pointerCount = mPointerProperties.size(); for (int i = 0; i < pointerCount; i++) { diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMotionEvent.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMotionEvent.java index dc5345ace..371cb6d9e 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMotionEvent.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMotionEvent.java @@ -4,6 +4,7 @@ import static android.os.Build.VERSION_CODES.KITKAT_WATCH; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.P; +import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static org.robolectric.shadows.NativeAndroidInput.AMOTION_EVENT_AXIS_ORIENTATION; @@ -33,6 +34,7 @@ import org.robolectric.annotation.RealObject; import org.robolectric.annotation.Resetter; import org.robolectric.res.android.NativeObjRegistry; import org.robolectric.util.ReflectionHelpers; +import org.robolectric.versioning.AndroidVersions.V; /** * Shadow of MotionEvent. @@ -51,7 +53,7 @@ import org.robolectric.util.ReflectionHelpers; * the MotionEvent.obtain methods or via MotionEventBuilder. */ @SuppressWarnings({"UnusedDeclaration"}) -@Implements(MotionEvent.class) +@Implements(value = MotionEvent.class) public class ShadowMotionEvent extends ShadowInputEvent { private static NativeObjRegistry nativeMotionEventRegistry = @@ -814,7 +816,7 @@ public class ShadowMotionEvent extends ShadowInputEvent { return nativeGetXOffset((long) nativePtr); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = UPSIDE_DOWN_CAKE) @HiddenApi @InDevelopment protected static float nativeGetXOffset(long nativePtr) { @@ -828,7 +830,7 @@ public class ShadowMotionEvent extends ShadowInputEvent { return nativeGetYOffset((long) nativePtr); } - @Implementation(minSdk = LOLLIPOP) + @Implementation(minSdk = LOLLIPOP, maxSdk = UPSIDE_DOWN_CAKE) @HiddenApi @InDevelopment protected static float nativeGetYOffset(long nativePtr) { @@ -836,6 +838,24 @@ public class ShadowMotionEvent extends ShadowInputEvent { return event.getYOffset(); } + @Implementation(minSdk = V.SDK_INT) + protected final MotionEvent split(int idBits) { + NativeInput.MotionEvent event = getNativeMotionEvent(); + return event.nativeSplit(idBits); + } + + @Implementation(minSdk = V.SDK_INT) + @HiddenApi + protected static float nativeGetRawXOffset(long nativePtr) { + return getNativeMotionEvent(nativePtr).getXOffset(); + } + + @Implementation(minSdk = V.SDK_INT) + @HiddenApi + protected static float nativeGetRawYOffset(long nativePtr) { + return getNativeMotionEvent(nativePtr).getYOffset(); + } + @Implementation(maxSdk = KITKAT_WATCH) @HiddenApi protected static float nativeGetXPrecision(int nativePtr) { @@ -940,7 +960,7 @@ public class ShadowMotionEvent extends ShadowInputEvent { event.scale(scale); } - private static NativeInput.MotionEvent getNativeMotionEvent(long nativePtr) { + protected static NativeInput.MotionEvent getNativeMotionEvent(long nativePtr) { // check that MotionEvent was initialized properly. This can occur if MotionEvent was mocked checkState( nativePtr > 0, @@ -961,7 +981,7 @@ public class ShadowMotionEvent extends ShadowInputEvent { event.transform(m); } - private NativeInput.MotionEvent getNativeMotionEvent() { + protected NativeInput.MotionEvent getNativeMotionEvent() { long nativePtr; if (RuntimeEnvironment.getApiLevel() <= KITKAT_WATCH) { Integer nativePtrInt = ReflectionHelpers.getField(realMotionEvent, "mNativePtr"); -- cgit v1.2.3 From 92fc90561cdb93bee3bc622ec9af11166e5d2660 Mon Sep 17 00:00:00 2001 From: Raphael Moll Date: Wed, 17 Apr 2024 11:29:11 -0700 Subject: Add PerfStarts for Screenshots. Used to track Software vs PixelCopy vs Hardware rendering modes. PiperOrigin-RevId: 625751188 --- .../java/org/robolectric/shadows/ShadowPixelCopy.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPixelCopy.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPixelCopy.java index a070ce1cb..9039e885e 100644 --- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPixelCopy.java +++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPixelCopy.java @@ -25,6 +25,7 @@ import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.shadow.api.Shadow; import org.robolectric.shadows.ShadowWindowManagerGlobal.WindowManagerGlobalReflector; +import org.robolectric.util.PerfStatsCollector; import org.robolectric.util.reflector.Accessor; import org.robolectric.util.reflector.Constructor; import org.robolectric.util.reflector.ForType; @@ -168,10 +169,18 @@ public class ShadowPixelCopy { Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888); if (HardwareRenderingScreenshot.canTakeScreenshot()) { - HardwareRenderingScreenshot.takeScreenshot(view, bitmap); + PerfStatsCollector.getInstance() + .measure( + "ShadowPixelCopy-Hardware", + () -> HardwareRenderingScreenshot.takeScreenshot(view, bitmap)); } else { - Canvas screenshotCanvas = new Canvas(bitmap); - view.draw(screenshotCanvas); + PerfStatsCollector.getInstance() + .measure( + "ShadowPixelCopy-Software", + () -> { + Canvas screenshotCanvas = new Canvas(bitmap); + view.draw(screenshotCanvas); + }); } Rect dst = new Rect(0, 0, screenshot.getWidth(), screenshot.getHeight()); -- cgit v1.2.3 From 7c563e9b476b301af28757896040057a5256fbed Mon Sep 17 00:00:00 2001 From: Julia Sullivan Date: Wed, 17 Apr 2024 13:41:22 -0700 Subject: Add pickInstrumentation to allow different versions of Instrumentation. By being protected, any service loaded versions of AndroidTestEnvironment can overwrite the Instrumentation version. PiperOrigin-RevId: 625792840 --- .../org/robolectric/android/internal/AndroidTestEnvironment.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/robolectric/src/main/java/org/robolectric/android/internal/AndroidTestEnvironment.java b/robolectric/src/main/java/org/robolectric/android/internal/AndroidTestEnvironment.java index e21545d9f..15918a0c4 100755 --- a/robolectric/src/main/java/org/robolectric/android/internal/AndroidTestEnvironment.java +++ b/robolectric/src/main/java/org/robolectric/android/internal/AndroidTestEnvironment.java @@ -594,8 +594,12 @@ public class AndroidTestEnvironment implements TestEnvironment { } } + protected Instrumentation pickInstrumentation() { + return new RoboMonitoringInstrumentation(); + } + private Instrumentation createInstrumentation() { - Instrumentation androidInstrumentation = new RoboMonitoringInstrumentation(); + Instrumentation androidInstrumentation = pickInstrumentation(); androidInstrumentation.runOnMainSync( () -> { ActivityThread activityThread = ReflectionHelpers.callConstructor(ActivityThread.class); -- cgit v1.2.3 From 71dc7ee3b39a334e2b1eecdffcd24e95b99a130f Mon Sep 17 00:00:00 2001 From: Tim Peut Date: Wed, 17 Apr 2024 16:07:45 -0700 Subject: Remove references to KITKAT from Robolectric framework tests. This is in preparation for Robolectric dropping support for 19. PiperOrigin-RevId: 625836028 --- .../versioning/AndroidVersionsTest.java | 9 ----- .../RobolectricTestRunnerMultiApiTest.java | 46 +++++++++++----------- 2 files changed, 22 insertions(+), 33 deletions(-) diff --git a/integration_tests/versioning/src/test/java/org/robolectric/versioning/AndroidVersionsTest.java b/integration_tests/versioning/src/test/java/org/robolectric/versioning/AndroidVersionsTest.java index 09b394a1a..89bdc6717 100644 --- a/integration_tests/versioning/src/test/java/org/robolectric/versioning/AndroidVersionsTest.java +++ b/integration_tests/versioning/src/test/java/org/robolectric/versioning/AndroidVersionsTest.java @@ -139,13 +139,4 @@ public final class AndroidVersionsTest { assertThat(new AndroidVersions.L().getVersion()).isEqualTo("5.0"); assertThat(AndroidVersions.CURRENT.getShortCode()).isEqualTo("L"); } - - @Test - @Config(sdk = 19) - public void testStandardInitializationK() { - assertThat(AndroidVersions.K.SDK_INT).isEqualTo(19); - assertThat(AndroidVersions.K.SHORT_CODE).isEqualTo("K"); - assertThat(new AndroidVersions.K().getVersion()).isEqualTo("4.4"); - assertThat(AndroidVersions.CURRENT.getShortCode()).isEqualTo("K"); - } } diff --git a/robolectric/src/test/java/org/robolectric/RobolectricTestRunnerMultiApiTest.java b/robolectric/src/test/java/org/robolectric/RobolectricTestRunnerMultiApiTest.java index d50ca474b..d994d8d92 100644 --- a/robolectric/src/test/java/org/robolectric/RobolectricTestRunnerMultiApiTest.java +++ b/robolectric/src/test/java/org/robolectric/RobolectricTestRunnerMultiApiTest.java @@ -1,6 +1,5 @@ package org.robolectric; -import static android.os.Build.VERSION_CODES.KITKAT; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1; import static android.os.Build.VERSION_CODES.M; @@ -39,7 +38,7 @@ import org.robolectric.util.inject.Injector; @RunWith(JUnit4.class) public class RobolectricTestRunnerMultiApiTest { - private static final int[] APIS_FOR_TEST = {KITKAT, LOLLIPOP, LOLLIPOP_MR1, M, N, N_MR1, O}; + private static final int[] APIS_FOR_TEST = {LOLLIPOP, LOLLIPOP_MR1, M, N, N_MR1, O}; private static SdkPicker delegateSdkPicker; private static final Injector INJECTOR = defaultInjector() @@ -82,7 +81,7 @@ public class RobolectricTestRunnerMultiApiTest { public void createChildrenForEachSupportedApi() throws Throwable { runner = runnerOf(TestWithNoConfig.class); assertThat(apisFor(runner.getChildren())) - .containsExactly(KITKAT, LOLLIPOP, LOLLIPOP_MR1, M, N, N_MR1, O); + .containsExactly(LOLLIPOP, LOLLIPOP_MR1, M, N, N_MR1, O); } @Test @@ -101,13 +100,13 @@ public class RobolectricTestRunnerMultiApiTest { assertThat(e.getMessage()) .contains( "sdk and minSdk/maxSdk may not be specified together" - + " (sdk=[19], minSdk=23, maxSdk=24)"); + + " (sdk=[23], minSdk=23, maxSdk=24)"); } } @Test public void withEnabledSdks_createChildrenForEachSupportedSdk() throws Throwable { - delegateSdkPicker = new DefaultSdkPicker(new SdkCollection(() -> map(19, 21)), null); + delegateSdkPicker = new DefaultSdkPicker(new SdkCollection(() -> map(21, 23)), null); runner = runnerOf(TestWithNoConfig.class); assertThat(runner.getChildren()).hasSize(2); @@ -116,18 +115,17 @@ public class RobolectricTestRunnerMultiApiTest { @Test public void shouldAddApiLevelToNameOfAllButHighestNumberedMethodName() throws Throwable { runner = runnerOf(TestMethodUpToAndIncludingN.class); - assertThat(runner.getChildren().get(0).getName()).isEqualTo("testSomeApiLevel[19]"); - assertThat(runner.getChildren().get(1).getName()).isEqualTo("testSomeApiLevel[21]"); - assertThat(runner.getChildren().get(2).getName()).isEqualTo("testSomeApiLevel[22]"); - assertThat(runner.getChildren().get(3).getName()).isEqualTo("testSomeApiLevel[23]"); - assertThat(runner.getChildren().get(4).getName()).isEqualTo("testSomeApiLevel"); + assertThat(runner.getChildren().get(0).getName()).isEqualTo("testSomeApiLevel[21]"); + assertThat(runner.getChildren().get(1).getName()).isEqualTo("testSomeApiLevel[22]"); + assertThat(runner.getChildren().get(2).getName()).isEqualTo("testSomeApiLevel[23]"); + assertThat(runner.getChildren().get(3).getName()).isEqualTo("testSomeApiLevel"); } @Test public void noConfig() throws Throwable { runner = runnerOf(TestWithNoConfig.class); assertThat(apisFor(runner.getChildren())) - .containsExactly(KITKAT, LOLLIPOP, LOLLIPOP_MR1, M, N, N_MR1, O); + .containsExactly(LOLLIPOP, LOLLIPOP_MR1, M, N, N_MR1, O); runner.run(runNotifier); assertThat(runListener.ignored).isEmpty(); @@ -137,24 +135,24 @@ public class RobolectricTestRunnerMultiApiTest { @Test public void classConfigWithSdkGroup() throws Throwable { runner = runnerOf(TestClassConfigWithSdkGroup.class); - assertThat(apisFor(runner.getChildren())).containsExactly(KITKAT, N); + assertThat(apisFor(runner.getChildren())).containsExactly(M, N); runner.run(runNotifier); assertThat(runListener.ignored).isEmpty(); - // Test method should be run for KitKat and N + // Test method should be run for M and N assertThat(runListener.finished).hasSize(2); } @Test public void methodConfigWithSdkGroup() throws Throwable { runner = runnerOf(TestMethodConfigWithSdkGroup.class); - assertThat(apisFor(runner.getChildren())).containsExactly(KITKAT, N); + assertThat(apisFor(runner.getChildren())).containsExactly(M, N); runner.run(runNotifier); assertThat(runListener.ignored).isEmpty(); - // Test method should be run for KitKat and N + // Test method should be run for M, N assertThat(runListener.finished).hasSize(2); } @@ -173,12 +171,12 @@ public class RobolectricTestRunnerMultiApiTest { @Test public void classConfigMaxSdk() throws Throwable { runner = runnerOf(TestClassUpToAndIncludingN.class); - assertThat(apisFor(runner.getChildren())).containsExactly(KITKAT, LOLLIPOP, LOLLIPOP_MR1, M, N); + assertThat(apisFor(runner.getChildren())).containsExactly(LOLLIPOP, LOLLIPOP_MR1, M, N); runner.run(runNotifier); assertThat(runListener.ignored).isEmpty(); - int sdksUpToAndIncludingLollipop = 5; + int sdksUpToAndIncludingLollipop = 4; assertThat(runListener.finished).hasSize(sdksUpToAndIncludingLollipop); } @@ -210,12 +208,12 @@ public class RobolectricTestRunnerMultiApiTest { @Test public void methodConfigMaxSdk() throws Throwable { runner = runnerOf(TestMethodUpToAndIncludingN.class); - assertThat(apisFor(runner.getChildren())).containsExactly(KITKAT, LOLLIPOP, LOLLIPOP_MR1, M, N); + assertThat(apisFor(runner.getChildren())).containsExactly(LOLLIPOP, LOLLIPOP_MR1, M, N); runner.run(runNotifier); assertThat(runListener.ignored).isEmpty(); - int sdksUpToAndIncludingLollipop = 5; + int sdksUpToAndIncludingLollipop = 4; assertThat(runListener.finished).hasSize(sdksUpToAndIncludingLollipop); } @@ -243,19 +241,19 @@ public class RobolectricTestRunnerMultiApiTest { @Test public void test() {} } - @Config(sdk = {KITKAT, N}) + @Config(sdk = {M, N}) public static class TestClassConfigWithSdkGroup { @Test public void testShouldRunApi18() { - assertThat(Build.VERSION.SDK_INT).isIn(Range.closed(KITKAT, N)); + assertThat(Build.VERSION.SDK_INT).isIn(Range.closed(M, N)); } } @Config(sdk = Config.ALL_SDKS) public static class TestMethodConfigWithSdkGroup { - @Config(sdk = {KITKAT, N}) + @Config(sdk = {M, N}) @Test public void testShouldRunApi16() { - assertThat(Build.VERSION.SDK_INT).isIn(Range.closed(KITKAT, N)); + assertThat(Build.VERSION.SDK_INT).isIn(Range.closed(M, N)); } } @@ -317,7 +315,7 @@ public class RobolectricTestRunnerMultiApiTest { @Config(sdk = Config.ALL_SDKS) public static class TestMethodWithSdkAndMinMax { - @Config(sdk = KITKAT, minSdk = M, maxSdk = N) + @Config(sdk = M, minSdk = M, maxSdk = N) @Test public void testWithKitKatAndLollipop() { assertThat(Build.VERSION.SDK_INT).isIn(Range.closed(M, N)); -- cgit v1.2.3 From 1920a4cbbf574f775bd360b901b3e4102b7ad8b4 Mon Sep 17 00:00:00 2001 From: Googler Date: Wed, 17 Apr 2024 16:22:47 -0700 Subject: Small fixes on potential null pointers since ARSC loading support. With the support of ARSC loading, some pointers potentially become null. This CL fixes this. PiperOrigin-RevId: 625839793 --- .../java/org/robolectric/res/android/AssetDir.java | 3 +++ .../org/robolectric/res/android/CppApkAssets.java | 8 +++--- .../org/robolectric/res/android/AssetDirTest.java | 29 ++++++++++++++++++++++ .../robolectric/res/android/CppApkAssetsTest.java | 17 +++++++++++++ 4 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 resources/src/test/java/org/robolectric/res/android/AssetDirTest.java create mode 100644 resources/src/test/java/org/robolectric/res/android/CppApkAssetsTest.java diff --git a/resources/src/main/java/org/robolectric/res/android/AssetDir.java b/resources/src/main/java/org/robolectric/res/android/AssetDir.java index b5a690172..fcf97cb82 100644 --- a/resources/src/main/java/org/robolectric/res/android/AssetDir.java +++ b/resources/src/main/java/org/robolectric/res/android/AssetDir.java @@ -20,6 +20,9 @@ public class AssetDir { * Vector-style access. */ public int getFileCount() { + if (mFileInfo == null) { + return 0; + } return mFileInfo.size(); } diff --git a/resources/src/main/java/org/robolectric/res/android/CppApkAssets.java b/resources/src/main/java/org/robolectric/res/android/CppApkAssets.java index 58d436b69..58370c892 100644 --- a/resources/src/main/java/org/robolectric/res/android/CppApkAssets.java +++ b/resources/src/main/java/org/robolectric/res/android/CppApkAssets.java @@ -5,7 +5,6 @@ package org.robolectric.res.android; import static org.robolectric.res.android.CppAssetManager.FileType.kFileTypeDirectory; import static org.robolectric.res.android.CppAssetManager.FileType.kFileTypeRegular; -import static org.robolectric.res.android.Util.CHECK; import static org.robolectric.res.android.ZipFileRO.OpenArchive; import static org.robolectric.res.android.ZipFileRO.kCompressDeflated; @@ -57,7 +56,7 @@ public class CppApkAssets { // bool ForEachFile(const String& path, // const std::function& f) const; - private CppApkAssets() { + CppApkAssets() { this.zipFileRO = null; } @@ -357,7 +356,10 @@ public class CppApkAssets { boolean ForEachFile(String root_path, ForEachFileCallback f) { - CHECK(zip_handle_ != null); + if (zip_handle_ == null || zipFileRO == null) { + // In this case, the ApkAssets was loaded from a pure ARSC, and does not have assets. + return false; + } String root_path_full = root_path; // if (root_path_full.back() != '/') { diff --git a/resources/src/test/java/org/robolectric/res/android/AssetDirTest.java b/resources/src/test/java/org/robolectric/res/android/AssetDirTest.java new file mode 100644 index 000000000..4337fde56 --- /dev/null +++ b/resources/src/test/java/org/robolectric/res/android/AssetDirTest.java @@ -0,0 +1,29 @@ +package org.robolectric.res.android; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class AssetDirTest { + + @Test + public void getFileCount_returnsZeroIfInitializedTrivially() { + assertThat(new AssetDir().getFileCount()).isEqualTo(0); + } + + @Test + public void getFileCount_returnsCorrectFileCount() { + AssetDir.FileInfo fileInfo1 = new AssetDir.FileInfo(new String8("a/a.txt")); + AssetDir.FileInfo fileInfo2 = new AssetDir.FileInfo(new String8("b/b.txt")); + SortedVector fileInfos = new SortedVector<>(); + fileInfos.add(fileInfo1); + fileInfos.add(fileInfo2); + AssetDir assetDir = new AssetDir(); + assetDir.setFileList(fileInfos); + + assertThat(assetDir.getFileCount()).isEqualTo(2); + } +} diff --git a/resources/src/test/java/org/robolectric/res/android/CppApkAssetsTest.java b/resources/src/test/java/org/robolectric/res/android/CppApkAssetsTest.java new file mode 100644 index 000000000..e4545af7c --- /dev/null +++ b/resources/src/test/java/org/robolectric/res/android/CppApkAssetsTest.java @@ -0,0 +1,17 @@ +package org.robolectric.res.android; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class CppApkAssetsTest { + + @Test + public void forEachFile_returnsFalseIfInitializedTrivially() { + boolean runningResult = new CppApkAssets().ForEachFile("a/robo", (string, type) -> {}); + assertThat(runningResult).isFalse(); + } +} -- cgit v1.2.3