aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKevin Liu <congxiliu@google.com>2024-03-12 23:51:48 +0000
committerKevin Liu <congxiliu@google.com>2024-03-13 02:14:32 +0000
commitf30e0656e46c3480ebbc46377df709b63a68470f (patch)
tree617af920454e82793aa4d35c8e7cc2b84dd789d4
parent91e288ba7054a1cb4a95f178e82c3a140eb4d708 (diff)
parent7c688cc0794f6c62fd82dc825b5b55d6991e2490 (diff)
downloadrobolectric-f30e0656e46c3480ebbc46377df709b63a68470f.tar.gz
Merge branch 'upstream-google' into roboUpdate
Pulling updates from Github to Android Bug: 323922587 Test: mma && atest CtesqueRoboTests Change-Id: I09cbfb2246c40f333819959e5faf6de3b0567bdd
-rw-r--r--.github/dependabot.yml2
-rw-r--r--.github/workflows/check_code_style.yml8
-rw-r--r--.github/workflows/codeql.yml83
-rw-r--r--.github/workflows/copybara_build_and_test.yml11
-rw-r--r--.github/workflows/gradle_tasks_validation.yml39
-rw-r--r--.github/workflows/gradle_wrapper_validation.yml6
-rw-r--r--.github/workflows/graphics_tests.yml31
-rw-r--r--.github/workflows/tests.yml56
-rw-r--r--.github/workflows/validate_commit_message.yml4
-rw-r--r--.gitmodules0
-rw-r--r--annotations/src/main/java/org/robolectric/annotation/Implements.java11
-rw-r--r--build.gradle1
-rw-r--r--buildSrc/src/main/groovy/AndroidSdk.groovy2
-rw-r--r--buildSrc/src/main/groovy/CheckApiChangesPlugin.groovy389
-rw-r--r--buildSrc/src/main/groovy/org/robolectric/gradle/AndroidProjectConfigPlugin.groovy2
-rw-r--r--buildSrc/src/main/groovy/org/robolectric/gradle/GradleManagedDevicePlugin.groovy2
-rw-r--r--buildSrc/src/main/groovy/org/robolectric/gradle/RoboJavaModulePlugin.groovy2
-rw-r--r--dependencies.gradle2
-rw-r--r--errorprone/build.gradle4
-rw-r--r--errorprone/src/main/java/org/robolectric/errorprone/bugpatterns/DeprecatedMethodsCheck.java6
-rw-r--r--errorprone/src/main/java/org/robolectric/errorprone/bugpatterns/Helpers.java5
-rw-r--r--errorprone/src/main/java/org/robolectric/errorprone/bugpatterns/RobolectricShadow.java1
-rw-r--r--gradle/libs.versions.toml68
-rw-r--r--gradle/wrapper/gradle-wrapper.jarbin62076 -> 43462 bytes
-rw-r--r--gradle/wrapper/gradle-wrapper.properties3
-rwxr-xr-xgradlew22
-rw-r--r--gradlew.bat20
-rw-r--r--integration_tests/agp/build.gradle2
-rw-r--r--integration_tests/androidx/build.gradle2
-rw-r--r--integration_tests/androidx_test/src/main/java/org/robolectric/integrationtests/axt/ActivityWithAppCompatMenu.java2
-rw-r--r--integration_tests/androidx_test/src/main/res/layout/espresso_activity.xml3
-rw-r--r--integration_tests/androidx_test/src/main/res/menu/menu.xml3
-rw-r--r--integration_tests/androidx_test/src/sharedTest/AndroidManifest.xml10
-rw-r--r--integration_tests/androidx_test/src/test/AndroidManifest-ActivityScenario.xml6
-rw-r--r--integration_tests/androidx_test/src/test/java/org/robolectric/integrationtests/axt/ActivityScenarioTest.java49
-rw-r--r--integration_tests/androidx_test/src/test/java/org/robolectric/integrationtests/axt/EspressoWithSwitchCompatTest.java6
-rw-r--r--integration_tests/compat-target28/build.gradle2
-rw-r--r--integration_tests/compat-target28/src/main/AndroidManifest.xml8
-rw-r--r--integration_tests/compat-target28/src/main/java/org/robolectric/integrationtests/compattarget28/MainActivity.java21
-rw-r--r--integration_tests/compat-target28/src/main/java/org/robolectric/integrationtests/compattarget28/TestAppComponentFactory.java21
-rw-r--r--integration_tests/compat-target28/src/test/java/org/robolectric/integration/compat/target28/NormalCompatibilityTest.kt27
-rw-r--r--integration_tests/ctesque/src/sharedTest/java/android/app/UiAutomationTest.kt5
-rw-r--r--integration_tests/ctesque/src/sharedTest/java/android/graphics/BitmapTest.java65
-rw-r--r--integration_tests/ctesque/src/sharedTest/java/android/text/format/TimeTest.java4
-rw-r--r--integration_tests/ctesque/src/sharedTest/java/android/util/RationalTest.java534
-rw-r--r--integration_tests/ctesque/src/sharedTest/java/android/webkit/CookieManagerTest.java30
-rw-r--r--integration_tests/jacoco-offline/build.gradle1
-rw-r--r--integration_tests/kotlin/build.gradle1
-rw-r--r--integration_tests/kotlin/src/test/kotlin/org/robolectric/integrationtests/kotlin/CustomShadowImageView.kt12
-rw-r--r--integration_tests/kotlin/src/test/kotlin/org/robolectric/integrationtests/kotlin/CustomShadowImageViewTest.kt12
-rw-r--r--integration_tests/memoryleaks/build.gradle4
-rw-r--r--integration_tests/nativegraphics/Android.bp1
-rw-r--r--integration_tests/nativegraphics/build.gradle1
-rw-r--r--integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeAllocationRegistryTest.java40
-rw-r--r--integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeBitmapFactoryTest.java74
-rw-r--r--integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeBitmapTest.java17
-rw-r--r--integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativePaintTest.java2
-rw-r--r--integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeRuntimeShaderTest.java4
-rw-r--r--integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeSurfaceTest.java31
-rw-r--r--integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeVectorDrawableTest.java6
-rw-r--r--integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/SystemFontsQTest.java25
-rw-r--r--integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/testing/util/DrawCountDown.java53
-rw-r--r--integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/testing/util/SneakyThrow.java40
-rw-r--r--integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/testing/util/WebViewReadyHelper.java61
-rw-r--r--integration_tests/play_services/build.gradle2
-rw-r--r--integration_tests/roborazzi/build.gradle65
-rw-r--r--integration_tests/roborazzi/src/screenshots/RoborazziCaptureTest.checkDialogRendering[31].pngbin0 -> 862 bytes
-rw-r--r--integration_tests/roborazzi/src/screenshots/RoborazziCaptureTest.checkViewWithElevationRendering[31].pngbin0 -> 302 bytes
-rw-r--r--integration_tests/roborazzi/src/test/java/org/robolectric/integration/roborazzi/RoborazziCaptureTest.kt196
-rw-r--r--integration_tests/room/build.gradle7
-rw-r--r--nativeruntime/build.gradle2
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/BitmapNatives.java2
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLoader.java9
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/ImageReaderNatives.java2
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/RenderNodeNatives.java2
-rw-r--r--nativeruntime/src/main/resources/icu/icudt.datbin0 -> 28431792 bytes
-rw-r--r--nativeruntime/src/test/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLazyLoadTest.java7
-rw-r--r--preinstrumented/build.gradle22
-rw-r--r--preinstrumented/src/main/java/org/robolectric/preinstrumented/JarInstrumentor.java56
-rw-r--r--preinstrumented/src/test/java/org/robolectric/preinstrumented/JarInstrumentorTest.java27
-rw-r--r--processor/src/main/java/org/robolectric/annotation/processing/validator/ImplementsValidator.java14
-rw-r--r--processor/src/test/java/org/robolectric/annotation/processing/RobolectricProcessorTest.java18
-rw-r--r--processor/src/test/resources/mock-source/org/robolectric/internal/ShadowProvider.java5
-rw-r--r--processor/src/test/resources/org/robolectric/Robolectric_ShadowPickers.java68
-rw-r--r--processor/src/test/resources/org/robolectric/annotation/processing/shadows/ShadowInnerDummyWithPicker.java26
-rw-r--r--resources/src/main/java/org/robolectric/RoboSettings.java8
-rw-r--r--resources/src/main/java/org/robolectric/manifest/AndroidManifest.java2
-rw-r--r--resources/src/main/java/org/robolectric/res/android/Asset.java11
-rw-r--r--resources/src/main/java/org/robolectric/res/android/CppApkAssets.java50
-rw-r--r--resources/src/main/java/org/robolectric/res/android/CppAssetManager.java4
-rw-r--r--resources/src/main/java/org/robolectric/res/android/CppAssetManager2.java70
-rw-r--r--resources/src/main/java/org/robolectric/res/android/LocaleData.java2
-rw-r--r--resources/src/main/java/org/robolectric/res/android/ResTable_config.java4
-rw-r--r--resources/src/main/java/org/robolectric/res/android/ZipFileRO.java2
-rw-r--r--robolectric/Android.bp4
-rw-r--r--robolectric/build.gradle40
-rw-r--r--robolectric/src/main/java/org/robolectric/Robolectric.java95
-rw-r--r--robolectric/src/main/java/org/robolectric/android/internal/AndroidTestEnvironment.java91
-rw-r--r--robolectric/src/main/java/org/robolectric/android/internal/RoboMonitoringInstrumentation.java19
-rw-r--r--robolectric/src/main/java/org/robolectric/internal/BuckManifestFactory.java5
-rw-r--r--robolectric/src/main/java/org/robolectric/plugins/DefaultSdkProvider.java2
-rw-r--r--robolectric/src/test/java/org/robolectric/CustomAppComponentFactory.java43
-rw-r--r--robolectric/src/test/java/org/robolectric/CustomAppComponentFactoryTest.java161
-rw-r--r--robolectric/src/test/java/org/robolectric/CustomConstructorActivity.java15
-rw-r--r--robolectric/src/test/java/org/robolectric/CustomConstructorContentProvider.java48
-rw-r--r--robolectric/src/test/java/org/robolectric/CustomConstructorServices.java67
-rw-r--r--robolectric/src/test/java/org/robolectric/ManifestFactoryTest.java17
-rw-r--r--robolectric/src/test/java/org/robolectric/RuntimeEnvironmentTest.java5
-rw-r--r--robolectric/src/test/java/org/robolectric/android/BootstrapTest.java139
-rw-r--r--robolectric/src/test/java/org/robolectric/android/DeviceConfigTest.java3
-rw-r--r--robolectric/src/test/java/org/robolectric/android/ResourceLoaderTest.java7
-rw-r--r--robolectric/src/test/java/org/robolectric/android/controller/ActivityControllerTest.java1
-rw-r--r--robolectric/src/test/java/org/robolectric/internal/BuckManifestFactoryTest.java23
-rw-r--r--robolectric/src/test/java/org/robolectric/internal/DefaultManifestFactoryTest.java4
-rw-r--r--robolectric/src/test/java/org/robolectric/internal/MavenManifestFactoryTest.java12
-rw-r--r--robolectric/src/test/java/org/robolectric/internal/bytecode/ShadowMapTest.java20
-rw-r--r--robolectric/src/test/java/org/robolectric/manifest/AndroidManifestTest.java60
-rw-r--r--robolectric/src/test/java/org/robolectric/plugins/LegacyDependencyResolverTest.java22
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/AppWidgetProviderInfoBuilderTest.java2
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/AssetManagerCachingTest.java53
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/AudioDeviceInfoBuilderTest.java46
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/AudioProfileBuilderTest.java77
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/CellIdentityLteBuilderTest.java3
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/CellInfoLteBuilderTest.java3
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/CellSignalStrengthLteBuilderTest.java3
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/HealthStatsBuilderTest.java175
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/NetworkSpecifierFactoryTest.java26
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ResourceHelperTest.java40
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowAccessibilityManagerTest.java2
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowAccessibilityNodeInfoTest.java28
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowAccountManagerTest.java36
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowActivityManagerTest.java8
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowActivityTest.java112
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowAlarmManagerTest.java3
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowAmbientDisplayConfigurationTest.java215
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowAppOpsManagerTest.java17
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowAppWidgetManagerTest.java2
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowApplicationTest.java5
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowAudioManagerTest.java184
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowAudioTrackTest.java119
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowBackupManagerTest.java180
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowBasicTagTechnologyTest.java3
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowBinderTest.java4
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowBitmapTest.java11
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothA2dpTest.java91
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothAdapterTest.java61
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothDeviceTest.java39
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothGattServerTest.java245
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothGattTest.java161
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothHeadsetTest.java27
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothLeAdvertiserTest.java392
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothLeScannerTest.java104
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothManagerTest.java2
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowBuildTest.java25
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowCameraTest.java4
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowConfigurationTest.java3
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowConnectivityManagerTest.java133
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowContentProviderTest.java3
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowContentResolverTest.java9
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowContextImplTest.java28
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowContextTest.java5
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowContextWrapperTest.java2
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowDateUtilsTest.java18
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowDevicePolicyManagerTest.java6
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowDialogTest.java3
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayEventReceiverTest.java3
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayManagerGlobalTest.java3
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayManagerTest.java44
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayTest.java2
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowDistanceMeasurementManagerTest.java164
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowDownloadManagerTest.java18
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowEnvironmentTest.java4
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowFloatMathTest.java3
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowICUTest.java19
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowImageReaderTest.java3
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowInputDeviceTest.java4
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowInputEventReceiverTest.java3
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowInputMethodManagerTest.java4
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowInstrumentationTest.java5
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowIsoDepTest.java3
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowLegacyMessageQueueTest.java3
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowLegacySystemClockTest.java3
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowLinuxTest.java9
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowLocaleDataTest.java5
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowLocationManagerTest.java1
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowLogTest.java38
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowLooperResetterTest.java145
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowMediaActionSoundTest.java2
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowMediaRouterTest.java24
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowNfcAdapterTest.java69
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowNotificationBuilderTestBase.java25
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowOpenGLMatrixTest.java5
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowPackageManagerTest.java162
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowPaintDrawableTest.java28
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowParcelFileDescriptorTest.java99
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowPausedAsyncTaskLoaderTest.java3
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowPausedLooperTest.java137
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowPausedSystemClockTest.java75
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowPendingIntentTest.java4
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowPosixTest.java4
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowPreferenceGroupTest.java21
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowRelativeLayoutTest.java17
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowResourcesTest.java64
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowRoleManagerTest.java78
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowSafetyCenterManagerTest.java105
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowScrollViewTest.java31
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowSensorManagerTest.java1
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowServiceManagerTest.java25
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowSettingsTest.java17
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowSmsManagerTest.java2
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowSoundPoolTest.java8
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowStatFsTest.java6
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowStorageManagerTest.java18
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowSubscriptionManagerTest.java52
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowSurfaceControlTest.java2
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowSystemHealthManagerTest.java75
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowTelecomManagerTest.java15
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyManagerTest.java288
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyTest.java3
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowTimeTest.java2
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowTraceTest.java2
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowTransportControlsTest.java163
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowUIModeManagerTest.java27
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowUiAutomationTest.java2
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowUserManagerTest.java23
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowUwbManagerTest.java101
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowValueAnimatorTest.java3
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowViewTest.java31
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowVirtualDeviceManagerTest.java124
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowVisualizerTest.java5
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowWallpaperManagerTest.java14
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowWebSettingsTest.java3
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowWifiConfigurationTest.java1
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowWifiManagerTest.java4
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowWindowManagerGlobalTest.java2
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowWindowTest.java5
-rw-r--r--robolectric/src/test/resources/AndroidManifest.xml13
-rw-r--r--robolectric/src/test/resources/TestAndroidManifestWithAppComponentFactory.xml14
-rw-r--r--robolectric/src/test/resources/TestAndroidManifestWithFlags.xml1
-rw-r--r--robolectric/src/test/resources/TestAndroidManifestWithReceivers.xml2
-rw-r--r--sandbox/src/main/java/org/robolectric/internal/bytecode/ClassHandler.java2
-rw-r--r--sandbox/src/main/java/org/robolectric/internal/bytecode/ClassInstrumentor.java56
-rw-r--r--sandbox/src/main/java/org/robolectric/internal/bytecode/InvokeDynamicSupport.java43
-rw-r--r--sandbox/src/main/java/org/robolectric/internal/bytecode/MethodCallSite.java16
-rw-r--r--sandbox/src/main/java/org/robolectric/internal/bytecode/NativeCallHandler.java137
-rw-r--r--sandbox/src/main/java/org/robolectric/internal/bytecode/RobolectricInternals.java4
-rw-r--r--sandbox/src/main/java/org/robolectric/internal/bytecode/Sandbox.java7
-rw-r--r--sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowDecorator.java2
-rw-r--r--sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowImpl.java56
-rw-r--r--sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowInfo.java8
-rw-r--r--sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowMap.java12
-rw-r--r--sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowWrangler.java35
-rw-r--r--sandbox/src/main/java/org/robolectric/sandbox/NativeMethodNotFoundException.java18
-rw-r--r--sandbox/src/test/java/org/robolectric/NativeMethodInvocationTest.java33
-rw-r--r--sandbox/src/test/java/org/robolectric/internal/bytecode/ClassInstrumentorTest.java151
-rw-r--r--sandbox/src/test/java/org/robolectric/internal/bytecode/NativeCallHandlerTest.java218
-rw-r--r--sandbox/src/test/java/org/robolectric/internal/bytecode/SandboxClassLoaderTest.java3
-rwxr-xr-xscripts/install-android-prebuilt.sh10
-rw-r--r--settings.gradle2
-rw-r--r--shadowapi/src/main/java/org/robolectric/internal/IShadow.java23
-rw-r--r--shadowapi/src/main/java/org/robolectric/shadow/api/Shadow.java4
-rw-r--r--shadowapi/src/main/java/org/robolectric/util/ReflectionHelpers.java19
-rw-r--r--shadows/framework/build.gradle1
-rw-r--r--shadows/framework/src/main/java/android/webkit/RoboCookieManager.java8
-rw-r--r--shadows/framework/src/main/java/org/robolectric/RuntimeEnvironment.java10
-rw-r--r--shadows/framework/src/main/java/org/robolectric/android/Bootstrap.java47
-rw-r--r--shadows/framework/src/main/java/org/robolectric/android/ConfigurationV25.java7
-rw-r--r--shadows/framework/src/main/java/org/robolectric/android/DeviceConfig.java22
-rw-r--r--shadows/framework/src/main/java/org/robolectric/android/internal/DisplayConfig.java13
-rw-r--r--shadows/framework/src/main/java/org/robolectric/android/util/concurrent/InlineExecutorService.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/android/util/concurrent/PausedExecutorService.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/fakes/RoboSplashScreen.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/AssociationInfoBuilder.java4
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/AudioDeviceInfoBuilder.java33
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/AudioProfileBuilder.java104
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/BackupDataOutputFactory.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/BarringInfoBuilder.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/CellIdentityLteBuilder.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/CellIdentityNrBuilder.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/CellInfoLteBuilder.java4
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/CellInfoNrBuilder.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/CellSignalStrengthLteBuilder.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/CellSignalStrengthNrBuilder.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/DeviceStateSensorOrientationBuilder.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/DragEventBuilder.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/GnssStatusBuilder.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/HealthStatsBuilder.java214
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ModuleInfoBuilder.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/NetworkRegistrationInfoTestBuilder.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/NetworkSpecifierFactory.java29
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/PackageRollbackInfoBuilder.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ResourceHelper.java36
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ServiceStateBuilder.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityManager.java41
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityNodeInfo.java41
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityRecord.java3
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccountManager.java21
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowActivity.java75
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowActivityManager.java10
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowAlarmManager.java12
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowAmbientContextManager.java4
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowAmbientDisplayConfiguration.java69
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowAppIntegrityManager.java1
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowAppOpsManager.java7
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowAppWidgetManager.java5
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplicationPackageManager.java57
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscApkAssets9.java20
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager.java27
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager10.java38
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager9.java37
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowAssetManager.java16
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowAudioEffect.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowAudioManager.java121
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowAudioTrack.java69
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowAutofillManager.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowBackgroundThread.java3
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowBackupDataOutput.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowBackupManager.java72
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowBinder.java3
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowBitmap.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowBitmapFactory.java25
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothA2dp.java73
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothAdapter.java56
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothDevice.java30
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothGatt.java38
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothGattServer.java100
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothHeadset.java18
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothLeAdvertiser.java152
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothLeScanner.java19
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothManager.java3
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothServerSocket.java19
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowBroadcastPendingResult.java34
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowBugreportManager.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowBuild.java29
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowCamera.java9
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowCardEmulation.java16
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowChoreographer.java10
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowClipboardManager.java3
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowCompanionDeviceManager.java7
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowConnectivityManager.java3
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentProvider.java3
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentProviderClient.java3
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentResolver.java38
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextHubClient.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextHubManager.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextImpl.java31
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowCursorWrapper.java4
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowDateIntervalFormat.java5
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowDatePickerDialog.java6
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyManager.java10
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyResourcesManager.java4
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplay.java216
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayEventReceiver.java8
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayManager.java26
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayManagerGlobal.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowDistanceMeasurementManager.java159
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowDownloadManager.java16
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowEnvironment.java19
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowFontBuilder.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowHidlSupport.java1
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowIAppOpsService.java4
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowICU.java9
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowIcon.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowImageReader.java15
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowImsMmTelManager.java5
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowInCallService.java58
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowIncidentManager.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowInputDevice.java6
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowInputEventReceiver.java4
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowInputManager.java19
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowInputMethodManager.java17
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowInsetsController.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowInstrumentation.java18
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowLauncherApps.java4
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyAssetManager.java11
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyBitmap.java28
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyCanvas.java3
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyLooper.java3
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyMatrix.java3
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyMessageQueue.java6
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyPath.java8
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacySystemClock.java3
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowLinux.java13
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocaleData.java29
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocaleManager.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocationManager.java28
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowLog.java48
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaActionSound.java3
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaCodec.java3
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaPlayer.java3
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaRouter.java19
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeAllocationRegistry.java6
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeAnimatedImageDrawable.java37
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeAnimatedVectorDrawable.java35
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBaseCanvas.java65
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBaseRecordingCanvas.java62
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmap.java107
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmapFactory.java20
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmapShader.java21
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBlendModeColorFilter.java9
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBlurMaskFilter.java9
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCanvas.java67
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCanvasProperty.java8
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColor.java12
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorFilter.java9
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorMatrixColorFilter.java9
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorSpace.java32
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorSpaceRgb.java13
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeComposePathEffect.java9
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeComposeShader.java9
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCornerPathEffect.java9
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCursorWindow.java41
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeDashPathEffect.java9
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeDiscretePathEffect.java9
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeEmbossMaskFilter.java9
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFont.java64
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontFamily.java34
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontFileUtil.java10
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontsFontFamily.java36
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeHardwareRenderer.java132
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeHardwareRendererObserver.java8
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeImageDecoder.java45
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeImageReader.java27
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeImageReaderSurfaceImage.java18
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeInterpolator.java19
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLightingColorFilter.java9
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLineBreaker.java38
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLinearGradient.java13
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMaskFilter.java9
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMatrix.java101
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMeasuredText.java62
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeNativeInterpolatorFactory.java26
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeNinePatch.java12
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePaint.java242
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePath.java97
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathDashPathEffect.java9
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathEffect.java9
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathMeasure.java22
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathParser.java22
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePicture.java26
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePluralRules.java3
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePorterDuffColorFilter.java6
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePositionedGlyphs.java38
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePropertyValuesHolder.java31
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRadialGradient.java11
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRecordingCanvas.java63
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRegion.java60
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRegionIterator.java13
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderEffect.java23
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderNode.java211
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderNodeAnimator.java30
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRuntimeShader.java56
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSQLiteConnection.java61
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeShader.java9
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSumPathEffect.java9
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSurface.java54
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSweepGradient.java9
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSystemFonts.java10
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeTableMaskFilter.java10
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeTextRunShaper.java11
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeTypeface.java62
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeVectorDrawable.java112
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeVirtualRefBasePtr.java8
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNfcAdapter.java162
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNotification.java11
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNotificationManager.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageManager.java11
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageParser.java16
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowPaint.java10
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowPaintDrawable.java48
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowParcel.java3
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowParcelFileDescriptor.java58
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedChoreographer.java61
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedLooper.java119
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedMessageQueue.java34
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedSystemClock.java64
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowPendingIntent.java5
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowPhoneWindow.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowPixelCopy.java4
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowPorterDuffColorFilter.java6
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowPowerManager.java7
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowProcess.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowResources.java6
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowResourcesManager.java3
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowRoleManager.java110
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowSafetyCenterManager.java70
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowSensorManager.java7
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowServiceManager.java327
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowSettings.java40
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowSliceManager.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowSmsManager.java5
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowSpeechRecognizer.java9
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowStatFs.java16
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowStatusBarManager.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowStorageManager.java10
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowSubscriptionManager.java64
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowSurface.java8
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowSurfaceControl.java3
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemClock.java12
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemHealthManager.java52
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemVibrator.java5
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelecomManager.java17
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephony.java5
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java212
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowTextToSpeech.java49
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowToneGenerator.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowTrace.java9
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowTransportControls.java222
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowUIModeManager.java43
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowUiAutomation.java27
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowUsbDeviceConnection.java8
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowUserManager.java28
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowUwbManager.java69
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowView.java8
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowViewRootImpl.java35
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualDeviceManager.java121
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualInputDevice.java25
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualKeyboard.java25
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualMouse.java47
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualTouchscreen.java28
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowVisualizer.java24
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowWallpaperManager.java13
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowWebSettings.java4
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowWebView.java4
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowWifiInfo.java9
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowWifiManager.java8
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowWifiP2pManager.java4
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindow.java3
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindowManagerGlobal.java6
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindowManagerImpl.java45
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/UiccCardInfoBuilder.java4
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/UiccPortInfoBuilder.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/_Activity_.java4
-rw-r--r--shadows/httpclient/src/main/java/org/robolectric/shadows/httpclient/ShadowDefaultRequestDirector.java13
534 files changed, 12594 insertions, 5039 deletions
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 66f95c264..808af6dd2 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -17,5 +17,3 @@ updates:
# don't auto update nativeruntime-dist-compat since it needs
# to be updated with code changes together
- dependency-name: "org.robolectric:nativeruntime-dist-compat"
- # don't auto update androidx-annotation since it brings higher Kotlin dependency
- - dependency-name: "androidx.annotation:annotation"
diff --git a/.github/workflows/check_code_style.yml b/.github/workflows/check_code_style.yml
index 00c0d13a4..5c020d298 100644
--- a/.github/workflows/check_code_style.yml
+++ b/.github/workflows/check_code_style.yml
@@ -11,6 +11,10 @@ on:
paths-ignore:
- '**.md'
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
permissions:
contents: read
@@ -24,9 +28,9 @@ jobs:
fetch-depth: 0
- name: Set up JDK 17
- uses: actions/setup-java@v3
+ uses: actions/setup-java@v4
with:
- distribution: 'zulu'
+ distribution: 'adopt'
java-version: 17
- name: Download google-java-format 1.9
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644
index 000000000..2e9a93f02
--- /dev/null
+++ b/.github/workflows/codeql.yml
@@ -0,0 +1,83 @@
+# For most projects, this workflow file will not need changing; you simply need
+# to commit it to your repository.
+#
+# You may wish to alter this file to override the set of languages analyzed,
+# or to provide custom queries or build logic.
+#
+# ******** NOTE ********
+# We have attempted to detect the languages in your repository. Please check
+# the `language` matrix defined below to confirm you have the correct set of
+# supported CodeQL languages.
+#
+name: "CodeQL"
+
+on:
+ push:
+ branches: [ "master" ]
+ pull_request:
+ # The branches below must be a subset of the branches above
+ branches: [ "master" ]
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ analyze:
+ name: Analyze
+ # Runner size impacts CodeQL analysis time. To learn more, please see:
+ # - https://gh.io/recommended-hardware-resources-for-running-codeql
+ # - https://gh.io/supported-runners-and-hardware-resources
+ # - https://gh.io/using-larger-runners
+ # Consider using larger runners for possible analysis time improvements.
+ runs-on: ubuntu-22.04
+ timeout-minutes: 360
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [ 'java-kotlin' ]
+ # CodeQL supports [ 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby' ]
+ # Use only 'java-kotlin' to analyze code written in Java, Kotlin or both
+ # Use only 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
+ # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Set up JDK 17
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'adopt'
+ java-version: 17
+
+ - uses: gradle/actions/setup-gradle@v3
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v3
+ with:
+ languages: ${{ matrix.language }}
+ # If you wish to specify custom queries, you can do so here or in a config file.
+ # By default, queries listed here will override any specified in a config file.
+ # Prefix the list here with "+" to use these queries and those in the config file.
+
+ # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
+ # queries: security-extended,security-and-quality
+
+ # ℹ️ Command-line programs to run using the OS shell.
+ # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
+ - name: Build
+ run: |
+ SKIP_ERRORPRONE=true SKIP_JAVADOC=true \
+ ./gradlew assemble testClasses --parallel --stacktrace --no-watch-fs
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v3
+ with:
+ category: "/language:${{matrix.language}}"
diff --git a/.github/workflows/copybara_build_and_test.yml b/.github/workflows/copybara_build_and_test.yml
index e4036f82b..55a995360 100644
--- a/.github/workflows/copybara_build_and_test.yml
+++ b/.github/workflows/copybara_build_and_test.yml
@@ -4,6 +4,10 @@ on:
pull_request:
branches: [ google ]
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
permissions:
contents: read
@@ -15,12 +19,12 @@ jobs:
- uses: actions/checkout@v4
- name: Set up JDK 17
- uses: actions/setup-java@v3
+ uses: actions/setup-java@v4
with:
- distribution: 'zulu'
+ distribution: 'adopt'
java-version: 17
- - uses: gradle/gradle-build-action@v2.9.0
+ - uses: gradle/actions/setup-gradle@v3
- name: Build
run: |
@@ -36,5 +40,6 @@ jobs:
-Drobolectric.alwaysIncludeVariantMarkersInTestName=true \
-Drobolectric.enabledSdks=34 \
-Dorg.gradle.workers.max=2 \
+ -Drobolectric.usePreinstrumentedJars=false \
-x :integration_tests:nativegraphics:test \
)
diff --git a/.github/workflows/gradle_tasks_validation.yml b/.github/workflows/gradle_tasks_validation.yml
index f3ac12e54..f8d065d2b 100644
--- a/.github/workflows/gradle_tasks_validation.yml
+++ b/.github/workflows/gradle_tasks_validation.yml
@@ -11,27 +11,14 @@ on:
paths-ignore:
- '**.md'
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
permissions:
contents: read
jobs:
- run_checkForApiChanges:
- runs-on: ubuntu-22.04
-
- steps:
- - uses: actions/checkout@v4
-
- - name: Set up JDK
- uses: actions/setup-java@v3
- with:
- distribution: 'zulu'
- java-version: 17
-
- - uses: gradle/gradle-build-action@v2.9.0
-
- - name: Run checkForApiChanges
- run: ./gradlew checkForApiChanges
-
run_aggregateDocs:
runs-on: ubuntu-22.04
@@ -39,12 +26,12 @@ jobs:
- uses: actions/checkout@v4
- name: Set up JDK
- uses: actions/setup-java@v3
+ uses: actions/setup-java@v4
with:
- distribution: 'zulu'
+ distribution: 'adopt'
java-version: 17
- - uses: gradle/gradle-build-action@v2.9.0
+ - uses: gradle/actions/setup-gradle@v3
- name: Run aggregateDocs
run: ./gradlew clean aggregateDocs
@@ -56,12 +43,12 @@ jobs:
- uses: actions/checkout@v4
- name: Set up JDK
- uses: actions/setup-java@v3
+ uses: actions/setup-java@v4
with:
- distribution: 'zulu'
+ distribution: 'adopt'
java-version: 17
- - uses: gradle/gradle-build-action@v2.9.0
+ - uses: gradle/actions/setup-gradle@v3
- name: Run javadocJar
run: ./gradlew clean javadocJar
@@ -76,12 +63,12 @@ jobs:
submodules: recursive
- name: Set up JDK
- uses: actions/setup-java@v3
+ uses: actions/setup-java@v4
with:
- distribution: 'zulu'
+ distribution: 'adopt'
java-version: 17
- - uses: gradle/gradle-build-action@v2.9.0
+ - uses: gradle/actions/setup-gradle@v3
- name: Run :preinstrumented:instrumentAll
run: ./gradlew :preinstrumented:instrumentAll
diff --git a/.github/workflows/gradle_wrapper_validation.yml b/.github/workflows/gradle_wrapper_validation.yml
index 0209ccd59..722f2caff 100644
--- a/.github/workflows/gradle_wrapper_validation.yml
+++ b/.github/workflows/gradle_wrapper_validation.yml
@@ -11,6 +11,10 @@ on:
paths-ignore:
- '**.md'
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
permissions:
contents: read
@@ -20,4 +24,4 @@ jobs:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- - uses: gradle/wrapper-validation-action@v1
+ - uses: gradle/wrapper-validation-action@v2
diff --git a/.github/workflows/graphics_tests.yml b/.github/workflows/graphics_tests.yml
index a64e8af7e..1ba2e5670 100644
--- a/.github/workflows/graphics_tests.yml
+++ b/.github/workflows/graphics_tests.yml
@@ -11,6 +11,10 @@ on:
paths-ignore:
- '**.md'
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
permissions:
contents: read
@@ -18,23 +22,29 @@ jobs:
graphics_tests:
strategy:
matrix:
- device: [ macos-12, ubuntu-22.04, self-hosted ]
+ device: [ macos-13, ubuntu-22.04, macos-14 ]
runs-on: ${{ matrix.device }}
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
- uses: actions/setup-java@v3
+ uses: actions/setup-java@v4
with:
- distribution: 'zulu'
+ distribution: 'adopt'
java-version: 17
- - uses: gradle/gradle-build-action@v2.9.0
+ - uses: gradle/actions/setup-gradle@v3
+
+ - name: Show runner info
+ run: |
+ uname -a
- name: Run unit tests
run: |
- SKIP_ERRORPRONE=true SKIP_JAVADOC=true ./gradlew :integration_tests:nativegraphics:testDebugUnitTest \
+ SKIP_ERRORPRONE=true SKIP_JAVADOC=true ./gradlew \
+ :integration_tests:nativegraphics:testDebugUnitTest \
+ :integration_tests:roborazzi:verifyRoborazziDebug \
--info --stacktrace --continue \
--parallel \
--no-watch-fs \
@@ -42,9 +52,12 @@ jobs:
-Dorg.gradle.workers.max=2
- name: Upload Test Results
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
if: always()
with:
- name: test_results
- path: '**/build/test-results/**/TEST-*.xml'
-
+ name: test_results_${{ matrix.device }}
+ path: |
+ **/build/test-results/**/TEST-*.xml
+ **/roborazzi/build/reports/*
+ **/roborazzi/src/screenshots/*
+ **/roborazzi/build/outputs/roborazzi/*
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 544beb7b3..66ca84bb9 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -11,6 +11,10 @@ on:
paths-ignore:
- '**.md'
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
permissions:
contents: read
@@ -25,12 +29,12 @@ jobs:
- uses: actions/checkout@v4
- name: Set up JDK 17
- uses: actions/setup-java@v3
+ uses: actions/setup-java@v4
with:
- distribution: 'zulu'
+ distribution: 'adopt'
java-version: 17
- - uses: gradle/gradle-build-action@v2.9.0
+ - uses: gradle/actions/setup-gradle@v3
- name: Build
run: |
@@ -49,12 +53,12 @@ jobs:
- uses: actions/checkout@v4
- name: Set up JDK 17
- uses: actions/setup-java@v3
+ uses: actions/setup-java@v4
with:
- distribution: 'zulu'
+ distribution: 'adopt'
java-version: 17
- - uses: gradle/gradle-build-action@v2.9.0
+ - uses: gradle/actions/setup-gradle@v3
- name: Run unit tests
run: |
@@ -64,18 +68,20 @@ jobs:
--no-watch-fs \
-Drobolectric.enabledSdks=${{ matrix.api-versions }} \
-Drobolectric.alwaysIncludeVariantMarkersInTestName=true \
+ -Drobolectric.usePreinstrumentedJars=false \
-Dorg.gradle.workers.max=2 \
- -x :integration_tests:nativegraphics:test
+ -x :integration_tests:nativegraphics:test \
+ -x :integration_tests:roborazzi:test
- name: Upload Test Results
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
if: always()
with:
name: test_results_${{ matrix.api-versions }}
path: '**/build/test-results/**/TEST-*.xml'
instrumentation-tests:
- runs-on: macos-12
+ runs-on: ubuntu-22.04
timeout-minutes: 60
needs: build
@@ -89,27 +95,36 @@ jobs:
- uses: actions/checkout@v4
- name: Set up JDK 17
- uses: actions/setup-java@v3
+ uses: actions/setup-java@v4
with:
- distribution: 'zulu'
+ distribution: 'adopt'
java-version: 17
- - uses: gradle/gradle-build-action@v2.9.0
+ - uses: gradle/actions/setup-gradle@v3
+
+ - name: Enable KVM group perms
+ run: |
+ echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
+ sudo udevadm control --reload-rules
+ sudo udevadm trigger --name-match=kvm
- name: Determine emulator target
id: determine-target
run: |
TARGET="google_apis"
+ if [[ ${{ matrix.api-level }} -ge 34 ]]; then
+ TARGET="aosp_atd"
+ fi
echo "TARGET=$TARGET" >> $GITHUB_OUTPUT
- name: AVD cache
- uses: actions/cache@v3
+ uses: actions/cache@v4
id: avd-cache
with:
path: |
~/.android/avd/*
~/.android/adb*
- key: avd-${{ matrix.api-level }}-${{ env.cache-version }}
+ key: avd-ubuntu-${{ matrix.api-level }}-${{ env.cache-version }}
- name: Create AVD and generate snapshot for caching
if: steps.avd-cache.outputs.cache-hit != 'true'
@@ -120,7 +135,7 @@ jobs:
arch: x86_64
force-avd-creation: false
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
- disable-animations: false
+ disable-animations: true
script: echo "Generated AVD snapshot for caching."
- name: Run device tests
@@ -133,15 +148,14 @@ jobs:
force-avd-creation: false
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true
- disable-spellchecker: true
profile: Nexus One
script: |
- ./gradlew cAT || ./gradlew cAT || ./gradlew cAT || exit 1
+ SKIP_ERRORPRONE=true SKIP_JAVADOC=true ./gradlew cAT --info --stacktrace --no-watch-fs -Dorg.gradle.workers.max=2
- name: Upload test results
if: always()
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: test-results-${{ matrix.api-level }}-${{ steps.determine-target.outputs.TARGET }}-${{ matrix.shard }}
path: |
@@ -159,12 +173,12 @@ jobs:
- uses: actions/checkout@v4
- name: Set up JDK 17
- uses: actions/setup-java@v3
+ uses: actions/setup-java@v4
with:
- distribution: 'zulu'
+ distribution: 'adopt'
java-version: 17
- - uses: gradle/gradle-build-action@v2.9.0
+ - uses: gradle/actions/setup-gradle@v3
- name: Publish
run: |
diff --git a/.github/workflows/validate_commit_message.yml b/.github/workflows/validate_commit_message.yml
index b541e4e93..57124e8b2 100644
--- a/.github/workflows/validate_commit_message.yml
+++ b/.github/workflows/validate_commit_message.yml
@@ -4,6 +4,10 @@ on:
pull_request:
branches: [ master, google ]
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
permissions:
contents: read
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index e69de29bb..000000000
--- a/.gitmodules
+++ /dev/null
diff --git a/annotations/src/main/java/org/robolectric/annotation/Implements.java b/annotations/src/main/java/org/robolectric/annotation/Implements.java
index d366003eb..84da01ec6 100644
--- a/annotations/src/main/java/org/robolectric/annotation/Implements.java
+++ b/annotations/src/main/java/org/robolectric/annotation/Implements.java
@@ -68,6 +68,17 @@ public @interface Implements {
Class<? extends ShadowPicker<?>> shadowPicker() default DefaultShadowPicker.class;
/**
+ * If set to true, Robolectric will invoke the native method variant instead of the no-op variant.
+ * This requires the native method to be bound, or an {@link UnsatisfiedLinkError} will occur.
+ *
+ * <p>{@link Implements#callNativeMethodsByDefault()} has precedence over {@link
+ * Implements#callThroughByDefault()} For instance, if both {@link
+ * Implements#callNativeMethodsByDefault()} and {@link Implements#callThroughByDefault()} are
+ * true, the native method variant will be preferred over the no-op native variant.
+ */
+ boolean callNativeMethodsByDefault() default false;
+
+ /**
* An interface used as the default for the {@code picker} param. Indicates that no custom {@link
* ShadowPicker} is being used.
*/
diff --git a/build.gradle b/build.gradle
index d944c3689..29ad8a91c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -17,6 +17,7 @@ buildscript {
classpath libs.kotlin.gradle
classpath libs.spotless.gradle
classpath libs.detekt.gradle
+ classpath libs.roborazzi.gradle
}
}
diff --git a/buildSrc/src/main/groovy/AndroidSdk.groovy b/buildSrc/src/main/groovy/AndroidSdk.groovy
index 12454ec66..59bbb5541 100644
--- a/buildSrc/src/main/groovy/AndroidSdk.groovy
+++ b/buildSrc/src/main/groovy/AndroidSdk.groovy
@@ -1,5 +1,5 @@
class AndroidSdk implements Comparable<AndroidSdk> {
- static final PREINSTRUMENTED_VERSION = 4
+ static final PREINSTRUMENTED_VERSION = 5
static final KITKAT = new AndroidSdk(19, "4.4_r1", "r2")
static final LOLLIPOP = new AndroidSdk(21, "5.0.2_r3", "r0")
diff --git a/buildSrc/src/main/groovy/CheckApiChangesPlugin.groovy b/buildSrc/src/main/groovy/CheckApiChangesPlugin.groovy
deleted file mode 100644
index 2f1476cf3..000000000
--- a/buildSrc/src/main/groovy/CheckApiChangesPlugin.groovy
+++ /dev/null
@@ -1,389 +0,0 @@
-import org.gradle.api.Plugin
-import org.gradle.api.Project
-import org.objectweb.asm.ClassReader
-import org.objectweb.asm.tree.AnnotationNode
-import org.objectweb.asm.tree.ClassNode
-import org.objectweb.asm.tree.MethodNode
-
-import java.util.jar.JarEntry
-import java.util.jar.JarInputStream
-import java.util.regex.Pattern
-
-import static org.objectweb.asm.Opcodes.ACC_PRIVATE
-import static org.objectweb.asm.Opcodes.ACC_PROTECTED
-import static org.objectweb.asm.Opcodes.ACC_PUBLIC
-import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC
-
-class CheckApiChangesPlugin implements Plugin<Project> {
- @Override
- void apply(Project project) {
- project.extensions.create("checkApiChanges", CheckApiChangesExtension)
-
- project.configurations {
- checkApiChangesFrom
- checkApiChangesTo
- }
-
- project.afterEvaluate {
- project.checkApiChanges.from.each {
- project.dependencies.checkApiChangesFrom(it) {
- transitive = false
- }
- }
-
- project.checkApiChanges.to.findAll { it instanceof String }.each {
- project.dependencies.checkApiChangesTo(it) {
- transitive = false
- force = true
- }
- }
- }
-
- project.task('checkForApiChanges', dependsOn: 'jar') {
- doLast {
- Map<ClassMethod, Change> changedClassMethods = new TreeMap<>()
-
- def fromUrls = project.configurations.checkApiChangesFrom*.toURI()*.toURL()
- println "fromUrls = ${fromUrls*.toString()*.replaceAll("^.*/", "")}"
-
- def jarUrls = project.checkApiChanges.to
- .findAll { it instanceof Project }
- .collect { it.jar.archivePath.toURL() }
- def toUrls = jarUrls + project.configurations.checkApiChangesTo*.toURI()*.toURL()
- println "toUrls = ${toUrls*.toString()*.replaceAll("^.*/", "")}"
-
- Analysis prev = new Analysis(fromUrls)
- Analysis cur = new Analysis(toUrls)
-
- Set<String> allMethods = new TreeSet<>(prev.classMethods.keySet())
- allMethods.addAll(cur.classMethods.keySet())
-
- Set<ClassMethod> deprecatedNotRemoved = new TreeSet<>()
- Set<ClassMethod> newlyDeprecated = new TreeSet<>()
-
- for (String classMethodName : allMethods) {
- ClassMethod prevClassMethod = prev.classMethods.get(classMethodName)
- ClassMethod curClassMethod = cur.classMethods.get(classMethodName)
-
- if (prevClassMethod == null) {
- // added
- if (curClassMethod.visible) {
- changedClassMethods.put(curClassMethod, Change.ADDED)
- }
- } else if (curClassMethod == null) {
- def theClass = prevClassMethod.classNode.name.replace('/', '.')
- def methodDesc = prevClassMethod.methodDesc
- while (curClassMethod == null && cur.parents[theClass] != null) {
- theClass = cur.parents[theClass]
- def parentMethodName = "${theClass}#${methodDesc}"
- curClassMethod = cur.classMethods[parentMethodName]
- }
-
- // removed
- if (curClassMethod == null && prevClassMethod.visible && !prevClassMethod.deprecated) {
- if (classMethodName.contains("getActivityTitle")) {
- println "hi!"
- }
- changedClassMethods.put(prevClassMethod, Change.REMOVED)
- }
- } else {
- if (prevClassMethod.deprecated) {
- deprecatedNotRemoved << prevClassMethod;
- } else if (curClassMethod.deprecated) {
- newlyDeprecated << prevClassMethod;
- }
-// println "changed: $classMethodName"
- }
- }
-
- String prevClassName = null
- def introClass = { classMethod ->
- if (classMethod.className != prevClassName) {
- prevClassName = classMethod.className
- println "\n$prevClassName:"
- }
- }
-
- def entryPoints = project.checkApiChanges.entryPoints
- Closure matchesEntryPoint = { ClassMethod classMethod ->
- for (String entryPoint : entryPoints) {
- if (classMethod.className.matches(entryPoint)) {
- return true
- }
- }
- return false
- }
-
- def expectedREs = project.checkApiChanges.expectedChanges.collect { Pattern.compile(it) }
-
- for (Map.Entry<ClassMethod, Change> change : changedClassMethods.entrySet()) {
- def classMethod = change.key
- def changeType = change.value
-
- def showAllChanges = true // todo: only show stuff that's interesting...
- if (matchesEntryPoint(classMethod) || showAllChanges) {
- String classMethodDesc = classMethod.desc
- def expected = expectedREs.any { it.matcher(classMethodDesc).find() }
- if (!expected) {
- introClass(classMethod)
-
- switch (changeType) {
- case Change.ADDED:
- println "+ ${classMethod.methodDesc}"
- break
- case Change.REMOVED:
- println "- ${classMethod.methodDesc}"
- break
- }
- }
- }
- }
-
- if (!deprecatedNotRemoved.empty) {
- println "\nDeprecated but not removed:"
- for (ClassMethod classMethod : deprecatedNotRemoved) {
- introClass(classMethod)
- println "* ${classMethod.methodDesc}"
- }
- }
-
- if (!newlyDeprecated.empty) {
- println "\nNewly deprecated:"
- for (ClassMethod classMethod : newlyDeprecated) {
- introClass(classMethod)
- println "* ${classMethod.methodDesc}"
- }
- }
- }
- }
- }
-
- static class Analysis {
- final Map<String, String> parents = new HashMap<>()
- final Map<String, ClassMethod> classMethods = new HashMap<>()
-
- Analysis(List<URL> baseUrls) {
- for (URL url : baseUrls) {
- if (url.protocol == 'file') {
- def file = new File(url.path)
- def stream = new FileInputStream(file)
- def jarStream = new JarInputStream(stream)
- while (true) {
- JarEntry entry = jarStream.nextJarEntry
- if (entry == null) break
-
- if (!entry.directory && entry.name.endsWith(".class")) {
- def reader = new ClassReader(jarStream)
- def classNode = new ClassNode()
- reader.accept(classNode, ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES)
-
- def superName = classNode.superName.replace('/', '.')
- if (!"java.lang.Object".equals(superName)) {
- parents[classNode.name.replace('/', '.')] = superName
- }
-
- if (bitSet(classNode.access, ACC_PUBLIC) || bitSet(classNode.access, ACC_PROTECTED)) {
- for (MethodNode method : classNode.methods) {
- def classMethod = new ClassMethod(classNode, method, url)
- if (!bitSet(method.access, ACC_SYNTHETIC)) {
- classMethods.put(classMethod.desc, classMethod)
- }
- }
- }
- }
- }
- stream.close()
- }
- }
- classMethods
- }
-
- }
-
- static enum Change {
- REMOVED,
- ADDED,
- }
-
- static class ClassMethod implements Comparable<ClassMethod> {
- final ClassNode classNode
- final MethodNode methodNode
- final URL originUrl
-
- ClassMethod(ClassNode classNode, MethodNode methodNode, URL originUrl) {
- this.classNode = classNode
- this.methodNode = methodNode
- this.originUrl = originUrl
- }
-
- boolean equals(o) {
- if (this.is(o)) return true
- if (getClass() != o.class) return false
-
- ClassMethod that = (ClassMethod) o
-
- if (classNode.name != that.classNode.name) return false
- if (methodNode.name != that.methodNode.name) return false
- if (methodNode.signature != that.methodNode.signature) return false
-
- return true
- }
-
- int hashCode() {
- int result
- result = (classNode.name != null ? classNode.name.hashCode() : 0)
- result = 31 * result + (methodNode.name != null ? methodNode.name.hashCode() : 0)
- result = 31 * result + (methodNode.signature != null ? methodNode.signature.hashCode() : 0)
- return result
- }
-
- public String getDesc() {
- return "$className#$methodDesc"
- }
-
- boolean hasParent() {
- parentClassName() != "java/lang/Object"
- }
-
- String parentClassName() {
- classNode.superName
- }
-
- private String getMethodDesc() {
- def args = new StringBuilder()
- def returnType = new StringBuilder()
- def buf = args
-
- int arrayDepth = 0
- def write = { typeName ->
- if (buf.size() > 0) buf.append(", ")
- buf.append(typeName)
- for (; arrayDepth > 0; arrayDepth--) {
- buf.append("[]")
- }
- }
-
- def chars = methodNode.desc.toCharArray()
- def i = 0
-
- def readObj = {
- if (buf.size() > 0) buf.append(", ")
- def objNameBuf = new StringBuilder()
- for (; i < chars.length; i++) {
- char c = chars[i]
- if (c == ';' as char) break
- objNameBuf.append((c == '/' as char) ? '.' : c)
- }
- buf.append(objNameBuf.toString().replaceAll(/^java\.lang\./, ''))
- }
-
- for (; i < chars.length;) {
- def c = chars[i++]
- switch (c) {
- case '(': break;
- case ')': buf = returnType; break;
- case '[': arrayDepth++; break;
- case 'Z': write('boolean'); break;
- case 'B': write('byte'); break;
- case 'S': write('short'); break;
- case 'I': write('int'); break;
- case 'J': write('long'); break;
- case 'F': write('float'); break;
- case 'D': write('double'); break;
- case 'C': write('char'); break;
- case 'L': readObj(); break;
- case 'V': write('void'); break;
- }
- }
- "$methodAccessString ${isHiddenApi() ? "@HiddenApi " : ""}${isImplementation() ? "@Implementation " : ""}$methodNode.name(${args.toString()}): ${returnType.toString()}"
- }
-
- @Override
- public String toString() {
- internalName
- }
-
- private String getInternalName() {
- classNode.name + "#$methodInternalName"
- }
-
- private String getMethodInternalName() {
- "$methodNode.name$methodNode.desc"
- }
-
- private String getSignature() {
- methodNode.signature == null ? "()V" : methodNode.signature
- }
-
- private String getClassName() {
- classNode.name.replace('/', '.')
- }
-
- boolean isDeprecated() {
- containsAnnotation(classNode.visibleAnnotations, "Ljava/lang/Deprecated;") ||
- containsAnnotation(methodNode.visibleAnnotations, "Ljava/lang/Deprecated;")
- }
-
- boolean isImplementation() {
- containsAnnotation(methodNode.visibleAnnotations, "Lorg/robolectric/annotation/Implementation;")
- }
-
- boolean isHiddenApi() {
- containsAnnotation(methodNode.visibleAnnotations, "Lorg/robolectric/annotation/HiddenApi;")
- }
-
- String getMethodAccessString() {
- return getAccessString(methodNode.access)
- }
-
- private String getClassAccessString() {
- return getAccessString(classNode.access)
- }
-
- String getAccessString(int access) {
- if (bitSet(access, ACC_PROTECTED)) {
- return "protected"
- } else if (bitSet(access, ACC_PUBLIC)) {
- return "public"
- } else if (bitSet(access, ACC_PRIVATE)) {
- return "private"
- } else {
- return "[package]"
- }
- }
-
- boolean isVisible() {
- (bitSet(classNode.access, ACC_PUBLIC) || bitSet(classNode.access, ACC_PROTECTED)) &&
- (bitSet(methodNode.access, ACC_PUBLIC) || bitSet(methodNode.access, ACC_PROTECTED)) &&
- !bitSet(classNode.access, ACC_SYNTHETIC) &&
- !(classNode.name =~ /\$[0-9]/) &&
- !(methodNode.name =~ /^access\$/ || methodNode.name == '<clinit>')
- }
-
- private static boolean containsAnnotation(List<AnnotationNode> annotations, String annotationInternalName) {
- for (AnnotationNode annotationNode : annotations) {
- if (annotationNode.desc == annotationInternalName) {
- return true
- }
- }
- return false
- }
-
- @Override
- int compareTo(ClassMethod o) {
- internalName <=> o.internalName
- }
- }
-
- private static boolean bitSet(int field, int bit) {
- (field & bit) == bit
- }
-}
-
-class CheckApiChangesExtension {
- String[] from
- Object[] to
-
- String[] entryPoints
- String[] expectedChanges
-} \ No newline at end of file
diff --git a/buildSrc/src/main/groovy/org/robolectric/gradle/AndroidProjectConfigPlugin.groovy b/buildSrc/src/main/groovy/org/robolectric/gradle/AndroidProjectConfigPlugin.groovy
index fc2ac09a1..40442bfdc 100644
--- a/buildSrc/src/main/groovy/org/robolectric/gradle/AndroidProjectConfigPlugin.groovy
+++ b/buildSrc/src/main/groovy/org/robolectric/gradle/AndroidProjectConfigPlugin.groovy
@@ -19,7 +19,7 @@ public class AndroidProjectConfigPlugin implements Plugin<Project> {
}
minHeapSize = "2048m"
- maxHeapSize = "8192m"
+ maxHeapSize = "12288m"
if (System.env['GRADLE_MAX_PARALLEL_FORKS'] != null) {
maxParallelForks = Integer.parseInt(System.env['GRADLE_MAX_PARALLEL_FORKS'])
diff --git a/buildSrc/src/main/groovy/org/robolectric/gradle/GradleManagedDevicePlugin.groovy b/buildSrc/src/main/groovy/org/robolectric/gradle/GradleManagedDevicePlugin.groovy
index cdcd3ca83..2589e8327 100644
--- a/buildSrc/src/main/groovy/org/robolectric/gradle/GradleManagedDevicePlugin.groovy
+++ b/buildSrc/src/main/groovy/org/robolectric/gradle/GradleManagedDevicePlugin.groovy
@@ -20,7 +20,7 @@ class GradleManagedDevicePlugin implements Plugin<Project> {
nexusOneApi34(ManagedVirtualDevice) {
device = "Nexus One"
apiLevel = 34
- systemImageSource = "aosp"
+ systemImageSource = "aosp-atd"
}
}
}
diff --git a/buildSrc/src/main/groovy/org/robolectric/gradle/RoboJavaModulePlugin.groovy b/buildSrc/src/main/groovy/org/robolectric/gradle/RoboJavaModulePlugin.groovy
index adffa3810..d48f89e1c 100644
--- a/buildSrc/src/main/groovy/org/robolectric/gradle/RoboJavaModulePlugin.groovy
+++ b/buildSrc/src/main/groovy/org/robolectric/gradle/RoboJavaModulePlugin.groovy
@@ -61,7 +61,7 @@ class RoboJavaModulePlugin implements Plugin<Project> {
}
minHeapSize = "1024m"
- maxHeapSize = "8192m"
+ maxHeapSize = "12288m"
if (System.env['GRADLE_MAX_PARALLEL_FORKS'] != null) {
maxParallelForks = Integer.parseInt(System.env['GRADLE_MAX_PARALLEL_FORKS'])
diff --git a/dependencies.gradle b/dependencies.gradle
index 204e3d354..86602da99 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -1,6 +1,4 @@
ext {
- apiCompatVersion = libs.versions.robolectric.compat.get()
-
// https://github.com/gradle/gradle/issues/21267
axtCoreVersion = libs.versions.androidx.test.core.get()
axtJunitVersion = libs.versions.androidx.test.ext.junit.get()
diff --git a/errorprone/build.gradle b/errorprone/build.gradle
index 5fc561605..ab8db7dcb 100644
--- a/errorprone/build.gradle
+++ b/errorprone/build.gradle
@@ -38,8 +38,6 @@ dependencies {
// Testing dependencies
testImplementation libs.junit4
testImplementation libs.truth
- testImplementation(libs.error.prone.test.helpers) {
- exclude group: 'junit', module: 'junit' // because it depends on a snapshot!?
- }
+ testImplementation libs.error.prone.test.helpers
testCompileOnly(AndroidSdk.MAX_SDK.coordinates)
}
diff --git a/errorprone/src/main/java/org/robolectric/errorprone/bugpatterns/DeprecatedMethodsCheck.java b/errorprone/src/main/java/org/robolectric/errorprone/bugpatterns/DeprecatedMethodsCheck.java
index 6ed6c5ad2..2784a7b95 100644
--- a/errorprone/src/main/java/org/robolectric/errorprone/bugpatterns/DeprecatedMethodsCheck.java
+++ b/errorprone/src/main/java/org/robolectric/errorprone/bugpatterns/DeprecatedMethodsCheck.java
@@ -4,7 +4,6 @@ import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.matchers.Description.NO_MATCH;
import static com.google.errorprone.matchers.Matchers.instanceMethod;
import static com.google.errorprone.matchers.Matchers.staticMethod;
-import static com.google.errorprone.util.ASTHelpers.hasAnnotation;
import static org.robolectric.errorprone.bugpatterns.Helpers.isCastableTo;
import static org.robolectric.errorprone.bugpatterns.Helpers.isInShadowClass;
@@ -19,6 +18,7 @@ import com.google.errorprone.fixes.Fix;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.method.MethodMatchers.MethodNameMatcher;
+import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ImportTree;
import com.sun.source.tree.MethodInvocationTree;
@@ -41,7 +41,6 @@ import org.robolectric.annotation.Implements;
*/
@AutoService(BugChecker.class)
@BugPattern(
- name = "DeprecatedMethods",
summary = "Prefer supported APIs.",
severity = WARNING,
documentSuppression = false,
@@ -127,7 +126,8 @@ public class DeprecatedMethodsCheck extends BugChecker implements ClassTreeMatch
@Override
public Void visitClass(ClassTree classTree, VisitorState visitorState) {
boolean priorInShadowClass = inShadowClass;
- inShadowClass = hasAnnotation(classTree, Implements.class, visitorState);
+ inShadowClass =
+ ASTHelpers.hasAnnotation(classTree, Implements.class.getName(), visitorState);
try {
return super.visitClass(classTree, visitorState);
} finally {
diff --git a/errorprone/src/main/java/org/robolectric/errorprone/bugpatterns/Helpers.java b/errorprone/src/main/java/org/robolectric/errorprone/bugpatterns/Helpers.java
index 234b96e5b..96dab0134 100644
--- a/errorprone/src/main/java/org/robolectric/errorprone/bugpatterns/Helpers.java
+++ b/errorprone/src/main/java/org/robolectric/errorprone/bugpatterns/Helpers.java
@@ -1,7 +1,6 @@
package org.robolectric.errorprone.bugpatterns;
import static com.google.errorprone.util.ASTHelpers.findEnclosingNode;
-import static com.google.errorprone.util.ASTHelpers.hasAnnotation;
import com.google.errorprone.VisitorState;
import com.google.errorprone.predicates.TypePredicate;
@@ -14,7 +13,7 @@ import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.tree.JCTree.JCClassDecl;
import org.robolectric.annotation.Implements;
-/** Matchers for {@link ShadowUsageCheck}. */
+/** Matchers for {@link DeprecatedMethodsCheck}. */
public class Helpers {
/** Match sub-types or implementations of the given type. */
@@ -33,7 +32,7 @@ public class Helpers {
? (JCClassDecl) leaf
: findEnclosingNode(state.getPath(), JCClassDecl.class);
- return hasAnnotation(classDecl, Implements.class, state);
+ return ASTHelpers.hasAnnotation(classDecl, Implements.class.getName(), state);
}
/** Matches implementations of the given interface. */
diff --git a/errorprone/src/main/java/org/robolectric/errorprone/bugpatterns/RobolectricShadow.java b/errorprone/src/main/java/org/robolectric/errorprone/bugpatterns/RobolectricShadow.java
index 646c78e60..7ec111108 100644
--- a/errorprone/src/main/java/org/robolectric/errorprone/bugpatterns/RobolectricShadow.java
+++ b/errorprone/src/main/java/org/robolectric/errorprone/bugpatterns/RobolectricShadow.java
@@ -49,7 +49,6 @@ import org.robolectric.annotation.Implements;
* @author christianw@google.com (Christian Williams)
*/
@BugPattern(
- name = "RobolectricShadow",
summary = "Robolectric @Implementation methods should be protected.",
severity = SUGGESTION,
documentSuppression = false,
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 0efed346b..8e569c11a 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,9 +1,8 @@
[versions]
-robolectric-compat = "4.11.1"
-robolectric-nativeruntime-dist-compat = "1.0.4"
+robolectric-nativeruntime-dist-compat = "1.0.8"
# https://developer.android.com/studio/releases
-android-gradle = "8.1.4"
+android-gradle = "8.2.2"
# https://github.com/google/conscrypt/tags
conscrypt = "2.5.2"
@@ -28,16 +27,16 @@ error-prone-javac = "9+181-r4173-1"
error-prone-gradle = "3.1.0"
# https://kotlinlang.org/docs/releases.html#release-details
-kotlin = "1.9.20"
+kotlin = "1.9.22"
# https://github.com/Kotlin/kotlinx.coroutines/releases/
kotlinx-coroutines = '1.7.3'
# https://github.com/diffplug/spotless/blob/main/plugin-gradle/CHANGES.md
-spotless-gradle = "6.22.0"
+spotless-gradle = "6.25.0"
# https://detekt.dev/changelog
-detekt-gradle = "1.23.3"
+detekt-gradle = "1.23.5"
# https://hc.apache.org/news.html
apache-http-core = "4.0.1"
@@ -51,6 +50,7 @@ auto-common = "1.2.2"
auto-service = "1.1.1"
auto-value = "1.10.4"
+# https://github.com/google/compile-testing/releases
compile-testing = "0.21.0"
# https://github.com/google/guava/releases
@@ -60,11 +60,12 @@ guava-jre = "31.1-jre"
gson = "2.10.1"
# https://github.com/google/truth/releases
-truth = "1.1.5"
+truth = "1.4.0"
# https://github.com/unicode-org/icu/releases
-icu4j = "74.1"
+icu4j = "74.2"
+# https://www.eclemma.org/jacoco/
jacoco = "0.8.11"
# https://github.com/javaee/javax.annotation/tags
@@ -79,7 +80,7 @@ jetbrains-annotations = "24.1.0"
junit4 = "4.13.2"
# https://github.com/google/libphonenumber/releases
-libphonenumber = "8.13.25"
+libphonenumber = "8.13.30"
# https://github.com/mockito/mockito/releases
mockito = "4.11.0"
@@ -87,6 +88,9 @@ mockito = "4.11.0"
# https://github.com/mockk/mockk/releases
mockk = "1.13.7"
+# https://github.com/takahirom/roborazzi/releases
+roborazzi = "1.9.0"
+
# https://square.github.io/okhttp/changelogs/changelog/
okhttp = "4.12.0"
@@ -96,7 +100,7 @@ powermock = "2.0.9"
sqlite4java = "1.0.392"
# https://developer.android.com/jetpack/androidx/versions
-androidx-annotation = "1.3.0"
+androidx-annotation = "1.7.1"
androidx-appcompat = "1.6.1"
androidx-biometric = "1.1.0"
androidx-constraintlayout = "2.1.4"
@@ -104,15 +108,14 @@ androidx-core = "1.12.0"
androidx-fragment = "1.6.2"
androidx-multidex = "2.0.1"
androidx-window = "1.2.0"
+androidx-room = "2.6.1"
# https://github.com/android/android-test/tags
-androidx-test-annotation = "1.0.1"
androidx-test-core = "1.5.0"
androidx-test-espresso = "3.5.1"
androidx-test-ext-junit = "1.1.5"
androidx-test-ext-truth = "1.5.0"
-androidx-test-monitor="1.6.1"
-androidx-test-orchestrator="1.4.2"
+androidx-test-monitor = "1.6.1"
androidx-test-runner = "1.5.2"
androidx-test-services = "1.4.2"
@@ -120,6 +123,9 @@ androidx-test-services = "1.4.2"
androidx-fragment-for-shadows = "1.2.0"
play-services-base-for-shadows = "8.4.0"
+# https://developers.google.com/android/guides/releases
+play-services-basement = "18.0.1"
+
[libraries]
android-gradle = { module = "com.android.tools.build:gradle", version.ref = "android-gradle" }
kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
@@ -127,7 +133,7 @@ spotless-gradle = { module = "com.diffplug.spotless:spotless-plugin-gradle", ver
detekt-gradle = { module = "io.gitlab.arturbosch.detekt:detekt-gradle-plugin", version.ref = "detekt-gradle" }
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
-kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines"}
+kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
auto-common = { module = "com.google.auto:auto-common", version.ref = "auto-common" }
auto-service-annotations = { module = "com.google.auto.service:auto-service-annotations", version.ref = "auto-service" }
@@ -147,12 +153,12 @@ compile-testing = { module = "com.google.testing.compile:compile-testing", versi
aggregate-javadocs-gradle = { module = "com.netflix.nebula:gradle-aggregate-javadocs-plugin", version.ref = "aggregate-javadocs-gradle" }
-error-prone-core = { module = "com.google.errorprone:error_prone_core", version.ref = "error-prone" }
+error-prone-core = { module = "com.google.errorprone:error_prone_core", version.ref = "error-prone" }
error-prone-annotations = { module = "com.google.errorprone:error_prone_annotation", version.ref = "error-prone" }
-error-prone-refaster= { module = "com.google.errorprone:error_prone_refaster", version.ref = "error-prone" }
+error-prone-refaster = { module = "com.google.errorprone:error_prone_refaster", version.ref = "error-prone" }
error-prone-check-api = { module = "com.google.errorprone:error_prone_check_api", version.ref = "error-prone" }
error-prone-test-helpers = { module = "com.google.errorprone:error_prone_test_helpers", version.ref = "error-prone" }
-error-prone-javac = { module = "com.google.errorprone:javac", version.ref = "error-prone-javac" }
+error-prone-javac = { module = "com.google.errorprone:javac", version.ref = "error-prone-javac" }
error-prone-gradle = { module = "net.ltgt.gradle:gradle-errorprone-plugin", version.ref = "error-prone-gradle" }
@@ -167,12 +173,11 @@ hamcrest-junit = { module = "org.hamcrest:hamcrest-junit", version.ref = "hamcre
icu4j = { module = "com.ibm.icu:icu4j", version.ref = "icu4j" }
-jacoco-agent = { module = "org.jacoco:org.jacoco.agent", version.ref = "jacoco" }
junit4 = { module = "junit:junit", version.ref = "junit4" }
javax-annotation-api = { module = "javax.annotation:javax.annotation-api", version.ref = "javax-annotation-api" }
javax-annotation-jsr250-api = { module = "javax.annotation:jsr250-api", version.ref = "javax-annotation-jsr250-api" }
-javax-inject = { module = "javax.inject:javax.inject", version.ref = "javax.inject" }
+javax-inject = { module = "javax.inject:javax.inject", version.ref = "javax-inject" }
jetbrains-annotations = { module = "org.jetbrains:annotations", version.ref = "jetbrains-annotations" }
@@ -202,6 +207,10 @@ mockito = { module = "org.mockito:mockito-core", version.ref = "mockito" }
mockito-inline = { module = "org.mockito:mockito-inline", version.ref = "mockito" }
mockk = { module = "io.mockk:mockk", version.ref = "mockk" }
+roborazzi = { module = "io.github.takahirom.roborazzi:roborazzi", version.ref = "roborazzi" }
+roborazzi-rule = { module = "io.github.takahirom.roborazzi:roborazzi-junit-rule", version.ref = "roborazzi" }
+roborazzi-gradle = { module = "io.github.takahirom.roborazzi:roborazzi-gradle-plugin", version.ref = "roborazzi" }
+
androidx-annotation = { module = "androidx.annotation:annotation", version.ref = "androidx-annotation" }
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" }
androidx-biometric = { module = "androidx.biometric:biometric", version.ref = "androidx-biometric" }
@@ -211,26 +220,17 @@ androidx-fragment = { module = "androidx.fragment:fragment", version.ref = "andr
androidx-fragment-testing = { module = "androidx.fragment:fragment-testing", version.ref = "androidx-fragment" }
androidx-multidex = { module = "androidx.multidex:multidex", version.ref = "androidx-multidex" }
androidx-window = { module = "androidx.window:window", version.ref = "androidx-window" }
+androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "androidx-room" }
+androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "androidx-room" }
-androidx-test-annotation = { module = "androidx.test:annotation", version.ref = "androidx-test-annotation" }
androidx-test-core = { module = "androidx.test:core", version.ref = "androidx-test-core" }
androidx-test-monitor = { module = "androidx.test:monitor", version.ref = "androidx-test-monitor" }
-androidx-test-orchestrator = { module = "androidx.test:orchestrator", version.ref = "androidx-test-orchestrator" }
androidx-test-rules = { module = "androidx.test:rules", version.ref = "androidx-test-core" }
androidx-test-runner = { module = "androidx.test:runner", version.ref = "androidx-test-runner" }
androidx-test-services = { module = "androidx.test.services:test-services", version.ref = "androidx-test-services" }
-androidx-test-services-storage = { module = "androidx.test.services:storage", version.ref = "androidx-test-services" }
androidx-test-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidx-test-espresso" }
-androidx-test-espresso-accessibility = { module = "androidx.test.espresso:espresso-accessibility", version.ref = "androidx-test-espresso" }
-androidx-test-espresso-contrib = { module = "androidx.test.espresso:espresso-contrib", version.ref = "androidx-test-espresso" }
androidx-test-espresso-intents = { module = "androidx.test.espresso:espresso-intents", version.ref = "androidx-test-espresso" }
-androidx-test-espresso-remote = { module = "androidx.test.espresso:espresso-remote", version.ref = "androidx-test-espresso" }
-androidx-test-espresso-web = { module = "androidx.test.espresso:espresso-web", version.ref = "androidx-test-espresso" }
-
-androidx-test-espresso-idling-resource = { module = "androidx.test.espresso:espresso-idling-resource", version.ref = "androidx-test-espresso" }
-androidx-test-espresso-idling-concurrent = { module = "androidx.test.espresso.idling:idling-concurrent", version.ref = "androidx-test-espresso" }
-androidx-test-espresso-idling-net = { module = "androidx.test.espresso.idling:idling-net", version.ref = "androidx-test-espresso" }
androidx-test-ext-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-test-ext-junit" }
androidx-test-ext-truth = { module = "androidx.test.ext:truth", version.ref = "androidx-test-ext-truth" }
@@ -239,9 +239,11 @@ androidx-fragment-for-shadows = { module = "androidx.fragment:fragment", version
play-services-base-for-shadows = { module = "com.google.android.gms:play-services-base", version.ref = "play-services-base-for-shadows" }
play-services-basement-for-shadows = { module = "com.google.android.gms:play-services-basement", version.ref = "play-services-base-for-shadows" }
+play-services-basement = { module = "com.google.android.gms:play-services-basement", version.ref = "play-services-basement" }
+
[bundles]
-play-services-base-for-shadows = [ "androidx-fragment-for-shadows", "play-services-base-for-shadows", "play-services-basement-for-shadows" ]
-powermock = [ "powermock-module-junit4", "powermock-module-junit4-rule", "powermock-api-mockito2", "powermock-classloading-xstream" ]
-sqlite4java-native = [ "sqlite4java-osx", "sqlite4java-linux-amd64", "sqlite4java-win32-x64", "sqlite4java-linux-i386", "sqlite4java-win32-x86" ]
+play-services-base-for-shadows = ["androidx-fragment-for-shadows", "play-services-base-for-shadows", "play-services-basement-for-shadows"]
+powermock = ["powermock-module-junit4", "powermock-module-junit4-rule", "powermock-api-mockito2", "powermock-classloading-xstream"]
+sqlite4java-native = ["sqlite4java-osx", "sqlite4java-linux-amd64", "sqlite4java-win32-x64", "sqlite4java-linux-i386", "sqlite4java-win32-x86"]
[plugins]
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index c1962a79e..d64cd4917 100644
--- a/gradle/wrapper/gradle-wrapper.jar
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index c30b486a8..a80b22ce5 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
networkTimeout=10000
+validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
index aeb74cbb4..1aa94a426 100755
--- a/gradlew
+++ b/gradlew
@@ -83,7 +83,8 @@ done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
-APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -130,10 +131,13 @@ location of your Java installation."
fi
else
JAVACMD=java
- which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
+ fi
fi
# Increase the maximum file descriptors if we can.
@@ -141,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
- # shellcheck disable=SC3045
+ # shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
@@ -149,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
- # shellcheck disable=SC3045
+ # shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -198,11 +202,11 @@ fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
-# Collect all arguments for the java command;
-# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
-# shell script including quotes and variable substitutions, so put them in
-# double quotes to make sure that they get re-expanded; and
-# * put everything else in single quotes, so that it's not re-expanded.
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
diff --git a/gradlew.bat b/gradlew.bat
index 6689b85be..7101f8e46 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -43,11 +43,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
goto fail
@@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
goto fail
diff --git a/integration_tests/agp/build.gradle b/integration_tests/agp/build.gradle
index 14a8b862b..71b9e74f7 100644
--- a/integration_tests/agp/build.gradle
+++ b/integration_tests/agp/build.gradle
@@ -22,7 +22,7 @@ android {
dependencies {
// Testing dependencies
- testImplementation project(path: ':testapp')
+ testImplementation project(":testapp")
testImplementation project(":robolectric")
testImplementation project(":integration_tests:agp:testsupport")
diff --git a/integration_tests/androidx/build.gradle b/integration_tests/androidx/build.gradle
index 8f722119a..2664b2c2e 100644
--- a/integration_tests/androidx/build.gradle
+++ b/integration_tests/androidx/build.gradle
@@ -31,7 +31,7 @@ dependencies {
implementation libs.androidx.window
// Testing dependencies
- testImplementation project(path: ':testapp')
+ testImplementation project(":testapp")
testImplementation project(":robolectric")
testImplementation libs.junit4
testImplementation libs.androidx.test.core
diff --git a/integration_tests/androidx_test/src/main/java/org/robolectric/integrationtests/axt/ActivityWithAppCompatMenu.java b/integration_tests/androidx_test/src/main/java/org/robolectric/integrationtests/axt/ActivityWithAppCompatMenu.java
index 937abf213..8f17a77b5 100644
--- a/integration_tests/androidx_test/src/main/java/org/robolectric/integrationtests/axt/ActivityWithAppCompatMenu.java
+++ b/integration_tests/androidx_test/src/main/java/org/robolectric/integrationtests/axt/ActivityWithAppCompatMenu.java
@@ -1,10 +1,10 @@
package org.robolectric.integrationtests.axt;
import android.os.Bundle;
-import androidx.appcompat.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+import androidx.appcompat.app.AppCompatActivity;
import org.robolectric.integration.axt.R;
/** {@link EspressoWithMenuTest} fixture activity that uses appcompat menu's */
diff --git a/integration_tests/androidx_test/src/main/res/layout/espresso_activity.xml b/integration_tests/androidx_test/src/main/res/layout/espresso_activity.xml
index c412b90f0..639ac1b11 100644
--- a/integration_tests/androidx_test/src/main/res/layout/espresso_activity.xml
+++ b/integration_tests/androidx_test/src/main/res/layout/espresso_activity.xml
@@ -10,7 +10,8 @@
<EditText
android:id="@+id/edit_text"
android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
+ android:layout_height="wrap_content"
+ android:inputType="textMultiLine" />
<Button
android:id="@+id/button"
diff --git a/integration_tests/androidx_test/src/main/res/menu/menu.xml b/integration_tests/androidx_test/src/main/res/menu/menu.xml
index 05ff42d6f..33517017f 100644
--- a/integration_tests/androidx_test/src/main/res/menu/menu.xml
+++ b/integration_tests/androidx_test/src/main/res/menu/menu.xml
@@ -3,6 +3,5 @@
<item
android:id="@+id/menu_filter"
android:title="menu_title"
-
- android:showAsAction="never" />
+ app:showAsAction="never" />
</menu> \ No newline at end of file
diff --git a/integration_tests/androidx_test/src/sharedTest/AndroidManifest.xml b/integration_tests/androidx_test/src/sharedTest/AndroidManifest.xml
index 7f55dcc85..5ba96949d 100644
--- a/integration_tests/androidx_test/src/sharedTest/AndroidManifest.xml
+++ b/integration_tests/androidx_test/src/sharedTest/AndroidManifest.xml
@@ -1,7 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools">
- <application>
+ <application
+ android:appComponentFactory="org.robolectric.integrationtests.axt.ActivityScenarioTest$CustomAppComponentFactory"
+ tools:replace="android:appComponentFactory">
<activity
android:name="org.robolectric.integrationtests.axt.ActivityTestRuleTest$TranscriptActivity"
android:exported="true"/>
@@ -18,6 +21,9 @@
<activity
android:name="org.robolectric.integrationtests.axt.ActivityScenarioTest$TranscriptActivity"
android:exported = "true"/>
+ <activity
+ android:name="org.robolectric.integrationtests.axt.ActivityScenarioTest$ActivityWithCustomConstructor"
+ android:exported = "true"/>
<activity-alias
android:name="org.robolectric.integrationtests.axt.ActivityScenarioTestAlias"
android:targetActivity="org.robolectric.integrationtests.axt.ActivityScenarioTest$TranscriptActivity" />
diff --git a/integration_tests/androidx_test/src/test/AndroidManifest-ActivityScenario.xml b/integration_tests/androidx_test/src/test/AndroidManifest-ActivityScenario.xml
index 9bcc80ee9..62402b2fd 100644
--- a/integration_tests/androidx_test/src/test/AndroidManifest-ActivityScenario.xml
+++ b/integration_tests/androidx_test/src/test/AndroidManifest-ActivityScenario.xml
@@ -7,13 +7,17 @@
android:minSdkVersion="19"
android:targetSdkVersion="34"/>
- <application>
+ <application
+ android:appComponentFactory="org.robolectric.integrationtests.axt.ActivityScenarioTest$CustomAppComponentFactory">
<activity
android:name="org.robolectric.integrationtests.axt.ActivityScenarioTest$LifecycleOwnerActivity"
android:exported="true"/>
<activity
android:name="org.robolectric.integrationtests.axt.ActivityScenarioTest$TranscriptActivity"
android:exported = "true"/>
+ <activity
+ android:name="org.robolectric.integrationtests.axt.ActivityScenarioTest$ActivityWithCustomConstructor"
+ android:exported="true"/>
<activity-alias
android:name="org.robolectric.integrationtests.axt.ActivityScenarioTestAlias"
android:targetActivity="org.robolectric.integrationtests.axt.ActivityScenarioTest$TranscriptActivity" />
diff --git a/integration_tests/androidx_test/src/test/java/org/robolectric/integrationtests/axt/ActivityScenarioTest.java b/integration_tests/androidx_test/src/test/java/org/robolectric/integrationtests/axt/ActivityScenarioTest.java
index a444f1dfe..8f9760846 100644
--- a/integration_tests/androidx_test/src/test/java/org/robolectric/integrationtests/axt/ActivityScenarioTest.java
+++ b/integration_tests/androidx_test/src/test/java/org/robolectric/integrationtests/axt/ActivityScenarioTest.java
@@ -1,19 +1,22 @@
package org.robolectric.integrationtests.axt;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
+import static android.os.Build.VERSION_CODES.P;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import android.app.Activity;
+import android.app.AppComponentFactory;
import android.app.UiAutomation;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.Looper;
-import androidx.fragment.app.Fragment;
-import androidx.appcompat.app.AppCompatActivity;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.appcompat.R;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.fragment.app.Fragment;
import androidx.lifecycle.Lifecycle.State;
import androidx.test.core.app.ActivityScenario;
import androidx.test.core.app.ApplicationProvider;
@@ -106,6 +109,32 @@ public class ActivityScenarioTest {
callbacks.clear();
}
+ public static class ActivityWithCustomConstructor extends Activity {
+ private final int intValue;
+
+ public ActivityWithCustomConstructor(int intValue) {
+ this.intValue = intValue;
+ }
+
+ public int getIntValue() {
+ return intValue;
+ }
+ }
+
+ public static class CustomAppComponentFactory extends AppComponentFactory {
+
+ @NonNull
+ @Override
+ public Activity instantiateActivity(
+ @NonNull ClassLoader cl, @NonNull String className, @Nullable Intent intent)
+ throws ClassNotFoundException, IllegalAccessException, InstantiationException {
+ if (className.contains(ActivityWithCustomConstructor.class.getName())) {
+ return new ActivityWithCustomConstructor(100);
+ }
+ return super.instantiateActivity(cl, className, intent);
+ }
+ }
+
@Test
public void launch_callbackSequence() {
try (ActivityScenario<TranscriptActivity> activityScenario =
@@ -247,7 +276,6 @@ public class ActivityScenarioTest {
}
}
- @Config(minSdk = JELLY_BEAN_MR2)
@Test
public void setRotation_recreatesActivity() {
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
@@ -314,4 +342,17 @@ public class ActivityScenarioTest {
});
}
}
+
+ @Test
+ @Config(minSdk = P)
+ public void launchActivityWithCustomConstructor() {
+ try (ActivityScenario<ActivityWithCustomConstructor> activityScenario =
+ ActivityScenario.launch(ActivityWithCustomConstructor.class)) {
+ assertThat(activityScenario.getState()).isEqualTo(State.RESUMED);
+ activityScenario.onActivity(
+ activity -> {
+ assertThat(activity.getIntValue()).isEqualTo(100);
+ });
+ }
+ }
}
diff --git a/integration_tests/androidx_test/src/test/java/org/robolectric/integrationtests/axt/EspressoWithSwitchCompatTest.java b/integration_tests/androidx_test/src/test/java/org/robolectric/integrationtests/axt/EspressoWithSwitchCompatTest.java
index 13f8fc252..a7606ce51 100644
--- a/integration_tests/androidx_test/src/test/java/org/robolectric/integrationtests/axt/EspressoWithSwitchCompatTest.java
+++ b/integration_tests/androidx_test/src/test/java/org/robolectric/integrationtests/axt/EspressoWithSwitchCompatTest.java
@@ -19,7 +19,9 @@ import org.robolectric.integration.axt.R;
public class EspressoWithSwitchCompatTest {
@Test
public void switchCompatTest() {
- ActivityScenario.launch(ActivityWithSwitchCompat.class);
- onView(withId(R.id.switch_compat_2)).check(matches(isCompletelyDisplayed())).perform(click());
+ try (ActivityScenario<ActivityWithSwitchCompat> scenario =
+ ActivityScenario.launch(ActivityWithSwitchCompat.class)) {
+ onView(withId(R.id.switch_compat_2)).check(matches(isCompletelyDisplayed())).perform(click());
+ }
}
}
diff --git a/integration_tests/compat-target28/build.gradle b/integration_tests/compat-target28/build.gradle
index a124b5eaf..7e11b884e 100644
--- a/integration_tests/compat-target28/build.gradle
+++ b/integration_tests/compat-target28/build.gradle
@@ -38,7 +38,7 @@ android {
dependencies {
implementation libs.kotlin.stdlib
- testImplementation project(path: ':testapp')
+ testImplementation project(":testapp")
testImplementation project(":robolectric")
testImplementation libs.junit4
testImplementation libs.truth
diff --git a/integration_tests/compat-target28/src/main/AndroidManifest.xml b/integration_tests/compat-target28/src/main/AndroidManifest.xml
index a0c0db960..9419a324e 100644
--- a/integration_tests/compat-target28/src/main/AndroidManifest.xml
+++ b/integration_tests/compat-target28/src/main/AndroidManifest.xml
@@ -1,4 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
-<manifest package="org.robolectric.integrationtests.compattarget28">
- <application />
+<manifest xmlns:tools="http://schemas.android.com/tools"
+ package="org.robolectric.integrationtests.compattarget28"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <application
+ android:appComponentFactory="org.robolectric.integrationtests.compattarget28.TestAppComponentFactory"
+ tools:targetApi="p" />
</manifest>
diff --git a/integration_tests/compat-target28/src/main/java/org/robolectric/integrationtests/compattarget28/MainActivity.java b/integration_tests/compat-target28/src/main/java/org/robolectric/integrationtests/compattarget28/MainActivity.java
new file mode 100644
index 000000000..88722c541
--- /dev/null
+++ b/integration_tests/compat-target28/src/main/java/org/robolectric/integrationtests/compattarget28/MainActivity.java
@@ -0,0 +1,21 @@
+package org.robolectric.integrationtests.compattarget28;
+
+import android.app.Activity;
+
+public class MainActivity extends Activity {
+ public enum CreationSource {
+ DEFAULT_CONSTRUCTOR,
+ CUSTOM_CONSTRUCTOR
+ }
+
+ public final CreationSource creationSource;
+
+ @SuppressWarnings("unused")
+ public MainActivity() {
+ this(CreationSource.DEFAULT_CONSTRUCTOR);
+ }
+
+ public MainActivity(CreationSource creationSource) {
+ this.creationSource = creationSource;
+ }
+}
diff --git a/integration_tests/compat-target28/src/main/java/org/robolectric/integrationtests/compattarget28/TestAppComponentFactory.java b/integration_tests/compat-target28/src/main/java/org/robolectric/integrationtests/compattarget28/TestAppComponentFactory.java
new file mode 100644
index 000000000..8f16ee0e7
--- /dev/null
+++ b/integration_tests/compat-target28/src/main/java/org/robolectric/integrationtests/compattarget28/TestAppComponentFactory.java
@@ -0,0 +1,21 @@
+package org.robolectric.integrationtests.compattarget28;
+
+import android.app.Activity;
+import android.app.AppComponentFactory;
+import android.content.Intent;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class TestAppComponentFactory extends AppComponentFactory {
+
+ @NotNull
+ @Override
+ public Activity instantiateActivity(
+ @NotNull ClassLoader cl, @NotNull String className, @Nullable Intent intent)
+ throws ClassNotFoundException, IllegalAccessException, InstantiationException {
+ if (className.equals(MainActivity.class.getName())) {
+ return new MainActivity(MainActivity.CreationSource.CUSTOM_CONSTRUCTOR);
+ }
+ return super.instantiateActivity(cl, className, intent);
+ }
+}
diff --git a/integration_tests/compat-target28/src/test/java/org/robolectric/integration/compat/target28/NormalCompatibilityTest.kt b/integration_tests/compat-target28/src/test/java/org/robolectric/integration/compat/target28/NormalCompatibilityTest.kt
index 15e04799e..8521b5698 100644
--- a/integration_tests/compat-target28/src/test/java/org/robolectric/integration/compat/target28/NormalCompatibilityTest.kt
+++ b/integration_tests/compat-target28/src/test/java/org/robolectric/integration/compat/target28/NormalCompatibilityTest.kt
@@ -14,8 +14,13 @@ import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Robolectric
+import org.robolectric.Robolectric.buildActivity
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
+import org.robolectric.Shadows
+import org.robolectric.annotation.Config
+import org.robolectric.integrationtests.compattarget28.MainActivity
+import org.robolectric.integrationtests.compattarget28.MainActivity.CreationSource
import org.robolectric.testapp.TestActivity
@RunWith(RobolectricTestRunner::class)
@@ -40,8 +45,11 @@ class NormalCompatibilityTest {
}
@Test
- fun `Initialize Activity succeed`() {
- Robolectric.setupActivity(TestActivity::class.java)
+ fun `Initialize Activity and its shadow succeed`() {
+ buildActivity(TestActivity::class.java).use { controller ->
+ val activity = controller.setup().get()
+ Shadows.shadowOf(activity)
+ }
}
@Test
@@ -71,7 +79,20 @@ class NormalCompatibilityTest {
srcRect,
bitmap,
listener,
- Handler(Looper.getMainLooper())
+ Handler(Looper.getMainLooper()),
)
}
+
+ @Test
+ fun `MainActivity created correctly using AppComponentFactory`() {
+ val activity = Robolectric.setupActivity(MainActivity::class.java)
+ assertThat(activity.creationSource).isEqualTo(CreationSource.CUSTOM_CONSTRUCTOR)
+ }
+
+ @Test
+ @Config(minSdk = 19, maxSdk = 27)
+ fun `MainActivity created correctly using default constructor on api lower than 28`() {
+ val activity = Robolectric.setupActivity(MainActivity::class.java)
+ assertThat(activity.creationSource).isEqualTo(CreationSource.DEFAULT_CONSTRUCTOR)
+ }
}
diff --git a/integration_tests/ctesque/src/sharedTest/java/android/app/UiAutomationTest.kt b/integration_tests/ctesque/src/sharedTest/java/android/app/UiAutomationTest.kt
index dd2ef15d9..fc31e5a23 100644
--- a/integration_tests/ctesque/src/sharedTest/java/android/app/UiAutomationTest.kt
+++ b/integration_tests/ctesque/src/sharedTest/java/android/app/UiAutomationTest.kt
@@ -1,7 +1,6 @@
package android.app
import android.content.res.Configuration
-import android.os.Build.VERSION_CODES.JELLY_BEAN_MR2
import android.view.Surface
import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -9,13 +8,11 @@ import androidx.test.platform.app.InstrumentationRegistry
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
-import org.robolectric.annotation.Config
import org.robolectric.annotation.internal.DoNotInstrument
import org.robolectric.testapp.TestActivity
@Suppress("DEPRECATION")
@DoNotInstrument
-@Config(minSdk = JELLY_BEAN_MR2)
@RunWith(AndroidJUnit4::class)
class UiAutomationTest {
@Test
@@ -53,4 +50,4 @@ class UiAutomationTest {
}
}
}
-} \ No newline at end of file
+}
diff --git a/integration_tests/ctesque/src/sharedTest/java/android/graphics/BitmapTest.java b/integration_tests/ctesque/src/sharedTest/java/android/graphics/BitmapTest.java
index 75c328860..109194022 100644
--- a/integration_tests/ctesque/src/sharedTest/java/android/graphics/BitmapTest.java
+++ b/integration_tests/ctesque/src/sharedTest/java/android/graphics/BitmapTest.java
@@ -1,8 +1,5 @@
package android.graphics;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.O;
@@ -159,7 +156,6 @@ public class BitmapTest {
}
@Test
- @Config(minSdk = JELLY_BEAN)
public void checkBitmapNotRecycled() throws IOException {
InputStream inputStream = resources.getAssets().open("robolectric.png");
BitmapFactory.Options options = new BitmapFactory.Options();
@@ -301,8 +297,6 @@ public class BitmapTest {
}
@Test
- @Config(minSdk = KITKAT)
- @SdkSuppress(minSdkVersion = KITKAT)
public void reconfigure_drawPixel() {
Bitmap bitmap = Bitmap.createBitmap(100, 50, Bitmap.Config.ARGB_8888);
bitmap.reconfigure(50, 100, Bitmap.Config.ARGB_8888);
@@ -374,16 +368,11 @@ public class BitmapTest {
Bitmap.createBitmap(/* width= */ 1, /* height= */ 1, Bitmap.Config.ARGB_8888)
.isMutable())
.isTrue();
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
- assertThat(
- Bitmap.createBitmap(
- (DisplayMetrics) null,
- /* width= */ 1,
- /* height= */ 1,
- Bitmap.Config.ARGB_8888)
- .isMutable())
- .isTrue();
- }
+ assertThat(
+ Bitmap.createBitmap(
+ (DisplayMetrics) null, /* width= */ 1, /* height= */ 1, Bitmap.Config.ARGB_8888)
+ .isMutable())
+ .isTrue();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
assertThat(
Bitmap.createBitmap(
@@ -423,28 +412,26 @@ public class BitmapTest {
Bitmap.Config.ARGB_8888)
.isMutable())
.isFalse();
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
- assertThat(
- Bitmap.createBitmap(
- /* display= */ null,
- /* colors= */ new int[] {0},
- /* width= */ 1,
- /* height= */ 1,
- Bitmap.Config.ARGB_8888)
- .isMutable())
- .isFalse();
- assertThat(
- Bitmap.createBitmap(
- /* display= */ null,
- /* colors= */ new int[] {0},
- /* offset= */ 0,
- /* stride= */ 1,
- /* width= */ 1,
- /* height= */ 1,
- Bitmap.Config.ARGB_8888)
- .isMutable())
- .isFalse();
- }
+ assertThat(
+ Bitmap.createBitmap(
+ /* display= */ null,
+ /* colors= */ new int[] {0},
+ /* width= */ 1,
+ /* height= */ 1,
+ Bitmap.Config.ARGB_8888)
+ .isMutable())
+ .isFalse();
+ assertThat(
+ Bitmap.createBitmap(
+ /* display= */ null,
+ /* colors= */ new int[] {0},
+ /* offset= */ 0,
+ /* stride= */ 1,
+ /* width= */ 1,
+ /* height= */ 1,
+ Bitmap.Config.ARGB_8888)
+ .isMutable())
+ .isFalse();
}
@Test
@@ -507,8 +494,6 @@ public class BitmapTest {
}
}
- @Config(minSdk = JELLY_BEAN_MR1)
- @SdkSuppress(minSdkVersion = JELLY_BEAN_MR1)
@Test
public void createBitmap_premultiplied() {
// ARGB_8888 has alpha by default, is premultiplied.
diff --git a/integration_tests/ctesque/src/sharedTest/java/android/text/format/TimeTest.java b/integration_tests/ctesque/src/sharedTest/java/android/text/format/TimeTest.java
index 0e3f989f5..41d066dfd 100644
--- a/integration_tests/ctesque/src/sharedTest/java/android/text/format/TimeTest.java
+++ b/integration_tests/ctesque/src/sharedTest/java/android/text/format/TimeTest.java
@@ -1,6 +1,5 @@
package android.text.format;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -10,7 +9,6 @@ import static org.junit.Assert.assertTrue;
import android.util.TimeFormatException;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SdkSuppress;
import java.util.Arrays;
import java.util.TimeZone;
import org.junit.After;
@@ -240,7 +238,6 @@ public class TimeTest {
}
@Test
- @SdkSuppress(minSdkVersion = JELLY_BEAN_MR1)
public void shouldFormat() {
Time t = new Time(Time.TIMEZONE_UTC);
t.set(3600000L);
@@ -250,7 +247,6 @@ public class TimeTest {
}
@Test
- @SdkSuppress(minSdkVersion = JELLY_BEAN_MR1)
public void shouldFormatAndroidStrings() {
Time t = new Time("UTC");
// NOTE: month is zero-based.
diff --git a/integration_tests/ctesque/src/sharedTest/java/android/util/RationalTest.java b/integration_tests/ctesque/src/sharedTest/java/android/util/RationalTest.java
new file mode 100644
index 000000000..69a088b31
--- /dev/null
+++ b/integration_tests/ctesque/src/sharedTest/java/android/util/RationalTest.java
@@ -0,0 +1,534 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This test is created from Android CTS tests:
+ *
+ * https://cs.android.com/android/platform/superproject/main/+/main:cts/tests/tests/util/src/android/util/cts/RationalTest.java
+ */
+
+package android.util;
+
+import static android.util.Rational.NEGATIVE_INFINITY;
+import static android.util.Rational.NaN;
+import static android.util.Rational.POSITIVE_INFINITY;
+import static android.util.Rational.ZERO;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.os.Build;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InvalidObjectException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.lang.reflect.Field;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+@RunWith(AndroidJUnit4.class)
+public class RationalTest {
+
+ /** (1,1) */
+ private static final Rational UNIT = new Rational(1, 1);
+
+ @Test
+ public void testConstructor() {
+
+ // Simple case
+ Rational r = new Rational(1, 2);
+ assertEquals(1, r.getNumerator());
+ assertEquals(2, r.getDenominator());
+
+ // Denominator negative
+ r = new Rational(-1, 2);
+ assertEquals(-1, r.getNumerator());
+ assertEquals(2, r.getDenominator());
+
+ // Numerator negative
+ r = new Rational(1, -2);
+ assertEquals(-1, r.getNumerator());
+ assertEquals(2, r.getDenominator());
+
+ // Both negative
+ r = new Rational(-1, -2);
+ assertEquals(1, r.getNumerator());
+ assertEquals(2, r.getDenominator());
+
+ // Infinity.
+ r = new Rational(1, 0);
+ assertEquals(1, r.getNumerator());
+ assertEquals(0, r.getDenominator());
+
+ // Negative infinity.
+ r = new Rational(-1, 0);
+ assertEquals(-1, r.getNumerator());
+ assertEquals(0, r.getDenominator());
+
+ // NaN.
+ r = new Rational(0, 0);
+ assertEquals(0, r.getNumerator());
+ assertEquals(0, r.getDenominator());
+ }
+
+ @Test
+ public void testEquals() {
+ Rational r = new Rational(1, 2);
+ assertEquals(1, r.getNumerator());
+ assertEquals(2, r.getDenominator());
+
+ assertEquals(r, r);
+ assertFalse(r.equals(null));
+ assertFalse(r.equals(new Object()));
+
+ Rational twoThirds = new Rational(2, 3);
+ assertFalse(r.equals(twoThirds));
+ assertFalse(twoThirds.equals(r));
+
+ Rational fourSixths = new Rational(4, 6);
+ assertEquals(twoThirds, fourSixths);
+ assertEquals(fourSixths, twoThirds);
+
+ Rational moreComplicated = new Rational(5 * 6 * 7 * 8 * 9, 1 * 2 * 3 * 4 * 5);
+ Rational moreComplicated2 = new Rational(5 * 6 * 7 * 8 * 9 * 78, 1 * 2 * 3 * 4 * 5 * 78);
+ assertEquals(moreComplicated, moreComplicated2);
+ assertEquals(moreComplicated2, moreComplicated);
+
+ // Ensure negatives are fine
+ twoThirds = new Rational(-2, 3);
+ fourSixths = new Rational(-4, 6);
+ assertEquals(twoThirds, fourSixths);
+ assertEquals(fourSixths, twoThirds);
+
+ moreComplicated = new Rational(-5 * 6 * 7 * 8 * 9, 1 * 2 * 3 * 4 * 5);
+ moreComplicated2 = new Rational(-5 * 6 * 7 * 8 * 9 * 78, 1 * 2 * 3 * 4 * 5 * 78);
+ assertEquals(moreComplicated, moreComplicated2);
+ assertEquals(moreComplicated2, moreComplicated);
+
+ // Zero is always equal to itself
+ Rational zero2 = new Rational(0, 100);
+ assertEquals(ZERO, zero2);
+ assertEquals(zero2, ZERO);
+
+ // NaN is always equal to itself
+ Rational nan = NaN;
+ Rational nan2 = new Rational(0, 0);
+ assertTrue(nan.equals(nan));
+ assertTrue(nan.equals(nan2));
+ assertTrue(nan2.equals(nan));
+ assertFalse(nan.equals(r));
+ assertFalse(r.equals(nan));
+
+ // Infinities of the same sign are equal.
+ Rational posInf = POSITIVE_INFINITY;
+ Rational posInf2 = new Rational(2, 0);
+ Rational negInf = NEGATIVE_INFINITY;
+ Rational negInf2 = new Rational(-2, 0);
+ assertEquals(posInf, posInf);
+ assertEquals(negInf, negInf);
+ assertEquals(posInf, posInf2);
+ assertEquals(negInf, negInf2);
+
+ // Infinities aren't equal to anything else.
+ assertFalse(posInf.equals(negInf));
+ assertFalse(negInf.equals(posInf));
+ assertFalse(negInf.equals(r));
+ assertFalse(posInf.equals(r));
+ assertFalse(r.equals(negInf));
+ assertFalse(r.equals(posInf));
+ assertFalse(posInf.equals(nan));
+ assertFalse(negInf.equals(nan));
+ assertFalse(nan.equals(posInf));
+ assertFalse(nan.equals(negInf));
+ }
+
+ @Test
+ public void testReduction() {
+ Rational moreComplicated = new Rational(5 * 78, 7 * 78);
+ assertEquals(new Rational(5, 7), moreComplicated);
+ assertEquals(5, moreComplicated.getNumerator());
+ assertEquals(7, moreComplicated.getDenominator());
+
+ Rational posInf = new Rational(5, 0);
+ assertEquals(1, posInf.getNumerator());
+ assertEquals(0, posInf.getDenominator());
+ assertEquals(POSITIVE_INFINITY, posInf);
+
+ Rational negInf = new Rational(-100, 0);
+ assertEquals(-1, negInf.getNumerator());
+ assertEquals(0, negInf.getDenominator());
+ assertEquals(NEGATIVE_INFINITY, negInf);
+
+ Rational zero = new Rational(0, -100);
+ assertEquals(0, zero.getNumerator());
+ assertEquals(1, zero.getDenominator());
+ assertEquals(ZERO, zero);
+
+ Rational flipSigns = new Rational(1, -1);
+ assertEquals(-1, flipSigns.getNumerator());
+ assertEquals(1, flipSigns.getDenominator());
+
+ Rational flipAndReduce = new Rational(100, -200);
+ assertEquals(-1, flipAndReduce.getNumerator());
+ assertEquals(2, flipAndReduce.getDenominator());
+ }
+
+ @Test
+ public void testCompareTo() {
+ // unit is equal to itself
+ verifyCompareEquals(UNIT, new Rational(1, 1));
+
+ // NaN is greater than anything but NaN
+ verifyCompareEquals(NaN, new Rational(0, 0));
+ verifyGreaterThan(NaN, UNIT);
+ verifyGreaterThan(NaN, POSITIVE_INFINITY);
+ verifyGreaterThan(NaN, NEGATIVE_INFINITY);
+ verifyGreaterThan(NaN, ZERO);
+
+ // Positive infinity is greater than any other non-NaN
+ verifyCompareEquals(POSITIVE_INFINITY, new Rational(1, 0));
+ verifyGreaterThan(POSITIVE_INFINITY, UNIT);
+ verifyGreaterThan(POSITIVE_INFINITY, NEGATIVE_INFINITY);
+ verifyGreaterThan(POSITIVE_INFINITY, ZERO);
+
+ // Negative infinity is smaller than any other non-NaN
+ verifyCompareEquals(NEGATIVE_INFINITY, new Rational(-1, 0));
+ verifyLessThan(NEGATIVE_INFINITY, UNIT);
+ verifyLessThan(NEGATIVE_INFINITY, POSITIVE_INFINITY);
+ verifyLessThan(NEGATIVE_INFINITY, ZERO);
+
+ // A finite number with the same denominator is trivially comparable
+ verifyGreaterThan(new Rational(3, 100), new Rational(1, 100));
+ verifyGreaterThan(new Rational(3, 100), ZERO);
+
+ // Compare finite numbers with different divisors
+ verifyGreaterThan(new Rational(5, 25), new Rational(1, 10));
+ verifyGreaterThan(new Rational(5, 25), ZERO);
+
+ // Compare finite numbers with different signs
+ verifyGreaterThan(new Rational(5, 25), new Rational(-1, 10));
+ verifyLessThan(new Rational(-5, 25), ZERO);
+ }
+
+ @Test
+ public void testConvenienceMethods() {
+ // isFinite
+ verifyFinite(ZERO, true);
+ verifyFinite(NaN, false);
+ verifyFinite(NEGATIVE_INFINITY, false);
+ verifyFinite(POSITIVE_INFINITY, false);
+ verifyFinite(UNIT, true);
+
+ // isInfinite
+ verifyInfinite(ZERO, false);
+ verifyInfinite(NaN, false);
+ verifyInfinite(NEGATIVE_INFINITY, true);
+ verifyInfinite(POSITIVE_INFINITY, true);
+ verifyInfinite(UNIT, false);
+
+ // isNaN
+ verifyNaN(ZERO, false);
+ verifyNaN(NaN, true);
+ verifyNaN(NEGATIVE_INFINITY, false);
+ verifyNaN(POSITIVE_INFINITY, false);
+ verifyNaN(UNIT, false);
+
+ // isZero
+ verifyZero(ZERO, true);
+ verifyZero(NaN, false);
+ verifyZero(NEGATIVE_INFINITY, false);
+ verifyZero(POSITIVE_INFINITY, false);
+ verifyZero(UNIT, false);
+ }
+
+ @Test
+ public void testValueConversions() {
+ // Unit, simple case
+ verifyValueEquals(UNIT, 1.0f);
+ verifyValueEquals(UNIT, 1.0);
+ verifyValueEquals(UNIT, 1L);
+ verifyValueEquals(UNIT, 1);
+ verifyValueEquals(UNIT, (short) 1);
+
+ // Zero, simple case
+ verifyValueEquals(ZERO, 0.0f);
+ verifyValueEquals(ZERO, 0.0);
+ verifyValueEquals(ZERO, 0L);
+ verifyValueEquals(ZERO, 0);
+ verifyValueEquals(ZERO, (short) 0);
+
+ // NaN is 0 for integers, not-a-number for floating point
+ verifyValueEquals(NaN, Float.NaN);
+ verifyValueEquals(NaN, Double.NaN);
+ verifyValueEquals(NaN, 0L);
+ verifyValueEquals(NaN, 0);
+ verifyValueEquals(NaN, (short) 0);
+
+ // Positive infinity, saturates upwards for integers
+ verifyValueEquals(POSITIVE_INFINITY, Float.POSITIVE_INFINITY);
+ verifyValueEquals(POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
+ verifyValueEquals(POSITIVE_INFINITY, Long.MAX_VALUE);
+ verifyValueEquals(POSITIVE_INFINITY, Integer.MAX_VALUE);
+ verifyValueEquals(POSITIVE_INFINITY, (short) -1);
+
+ // Negative infinity, saturates downwards for integers
+ verifyValueEquals(NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY);
+ verifyValueEquals(NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
+ verifyValueEquals(NEGATIVE_INFINITY, Long.MIN_VALUE);
+ verifyValueEquals(NEGATIVE_INFINITY, Integer.MIN_VALUE);
+ verifyValueEquals(NEGATIVE_INFINITY, (short) 0);
+
+ // Normal finite values, round down for integers
+ final Rational oneQuarter = new Rational(1, 4);
+ verifyValueEquals(oneQuarter, 1.0f / 4.0f);
+ verifyValueEquals(oneQuarter, 1.0 / 4.0);
+ verifyValueEquals(oneQuarter, 0L);
+ verifyValueEquals(oneQuarter, 0);
+ verifyValueEquals(oneQuarter, (short) 0);
+
+ final Rational nineFifths = new Rational(9, 5);
+ verifyValueEquals(nineFifths, 9.0f / 5.0f);
+ verifyValueEquals(nineFifths, 9.0 / 5.0);
+ verifyValueEquals(nineFifths, 1L);
+ verifyValueEquals(nineFifths, 1);
+ verifyValueEquals(nineFifths, (short) 1);
+
+ final Rational negativeHundred = new Rational(-1000, 10);
+ verifyValueEquals(negativeHundred, -100.f / 1.f);
+ verifyValueEquals(negativeHundred, -100.0 / 1.0);
+ verifyValueEquals(negativeHundred, -100L);
+ verifyValueEquals(negativeHundred, -100);
+ verifyValueEquals(negativeHundred, (short) -100);
+
+ // Short truncates if the result is too large
+ verifyValueEquals(new Rational(Integer.MAX_VALUE, 1), (short) Integer.MAX_VALUE);
+ verifyValueEquals(new Rational(0x00FFFFFF, 1), (short) 0x00FFFFFF);
+ verifyValueEquals(new Rational(0x00FF00FF, 1), (short) 0x00FF00FF);
+ }
+
+ @Test
+ public void testSerialize() throws ClassNotFoundException, IOException, NoSuchFieldException {
+ /*
+ * Check correct [de]serialization
+ */
+ verifyEqualsAfterSerializing(ZERO);
+ verifyEqualsAfterSerializing(NaN);
+ verifyEqualsAfterSerializing(NEGATIVE_INFINITY);
+ verifyEqualsAfterSerializing(POSITIVE_INFINITY);
+ verifyEqualsAfterSerializing(UNIT);
+ verifyEqualsAfterSerializing(new Rational(100, 200));
+ verifyEqualsAfterSerializing(new Rational(-100, 200));
+ verifyEqualsAfterSerializing(new Rational(5, 1));
+ verifyEqualsAfterSerializing(new Rational(Integer.MAX_VALUE, Integer.MIN_VALUE));
+
+ /*
+ * Check bad deserialization fails
+ */
+ try {
+ Rational badZero = createIllegalRational(0, 100); // [0, 100] , should be [0, 1]
+ Rational results = serializeRoundTrip(badZero);
+ fail("Deserializing " + results + " should not have succeeded");
+ } catch (InvalidObjectException e) {
+ // OK
+ }
+
+ try {
+ Rational badPosInfinity = createIllegalRational(100, 0); // [100, 0] , should be [1, 0]
+ Rational results = serializeRoundTrip(badPosInfinity);
+ fail("Deserializing " + results + " should not have succeeded");
+ } catch (InvalidObjectException e) {
+ // OK
+ }
+
+ try {
+ Rational badNegInfinity = createIllegalRational(-100, 0); // [-100, 0] , should be [-1, 0]
+ Rational results = serializeRoundTrip(badNegInfinity);
+ fail("Deserializing " + results + " should not have succeeded");
+ } catch (InvalidObjectException e) {
+ // OK
+ }
+
+ try {
+ Rational badReduced = createIllegalRational(2, 4); // [2,4] , should be [1, 2]
+ Rational results = serializeRoundTrip(badReduced);
+ fail("Deserializing " + results + " should not have succeeded");
+ } catch (InvalidObjectException e) {
+ // OK
+ }
+
+ try {
+ Rational badReducedNeg = createIllegalRational(-2, 4); // [-2, 4] should be [-1, 2]
+ Rational results = serializeRoundTrip(badReducedNeg);
+ fail("Deserializing " + results + " should not have succeeded");
+ } catch (InvalidObjectException e) {
+ // OK
+ }
+ }
+
+ @Test
+ public void testParseRational() {
+ assertEquals(new Rational(1, 2), Rational.parseRational("3:+6"));
+ assertEquals(new Rational(1, 2), Rational.parseRational("-3:-6"));
+ assertEquals(Rational.NaN, Rational.parseRational("NaN"));
+ assertEquals(Rational.POSITIVE_INFINITY, Rational.parseRational("Infinity"));
+ assertEquals(Rational.NEGATIVE_INFINITY, Rational.parseRational("-Infinity"));
+ assertEquals(Rational.ZERO, Rational.parseRational("0/261"));
+ assertEquals(Rational.NaN, Rational.parseRational("0/-0"));
+ assertEquals(Rational.POSITIVE_INFINITY, Rational.parseRational("1000/+0"));
+ assertEquals(Rational.NEGATIVE_INFINITY, Rational.parseRational("-1000/-0"));
+
+ Rational r = new Rational(10, 15);
+ assertEquals(r, Rational.parseRational(r.toString()));
+ }
+
+ @Test(expected = NumberFormatException.class)
+ public void testParseRationalInvalid1() {
+ Rational.parseRational("1.5");
+ }
+
+ @Test(expected = NumberFormatException.class)
+ public void testParseRationalInvalid2() {
+ Rational.parseRational("239");
+ }
+
+ private static void verifyValueEquals(Rational object, float expected) {
+ assertEquals("Checking floatValue() for " + object + ";", expected, object.floatValue(), 0.0f);
+ }
+
+ private static void verifyValueEquals(Rational object, double expected) {
+ assertEquals(
+ "Checking doubleValue() for " + object + ";", expected, object.doubleValue(), 0.0f);
+ }
+
+ private static void verifyValueEquals(Rational object, long expected) {
+ assertEquals("Checking longValue() for " + object + ";", expected, object.longValue());
+ }
+
+ private static void verifyValueEquals(Rational object, int expected) {
+ assertEquals("Checking intValue() for " + object + ";", expected, object.intValue());
+ }
+
+ private static void verifyValueEquals(Rational object, short expected) {
+ assertEquals("Checking shortValue() for " + object + ";", expected, object.shortValue());
+ }
+
+ private static void verifyFinite(Rational object, boolean expected) {
+ verifyAction("finite", object, expected, object.isFinite());
+ }
+
+ private static void verifyInfinite(Rational object, boolean expected) {
+ verifyAction("infinite", object, expected, object.isInfinite());
+ }
+
+ private static void verifyNaN(Rational object, boolean expected) {
+ verifyAction("NaN", object, expected, object.isNaN());
+ }
+
+ private static void verifyZero(Rational object, boolean expected) {
+ verifyAction("zero", object, expected, object.isZero());
+ }
+
+ private static <T> void verifyAction(String action, T object, boolean expected, boolean actual) {
+ String expectedMessage = expected ? action : ("not " + action);
+ assertEquals("Expected " + object + " to be " + expectedMessage, expected, actual);
+ }
+
+ private static <T extends Comparable<? super T>> void verifyLessThan(T left, T right) {
+ assertTrue(
+ "Expected (LR) left " + left + " to be less than right " + right,
+ left.compareTo(right) < 0);
+ assertTrue(
+ "Expected (RL) left " + left + " to be less than right " + right,
+ right.compareTo(left) > 0);
+ }
+
+ private static <T extends Comparable<? super T>> void verifyGreaterThan(T left, T right) {
+ assertTrue(
+ "Expected (LR) left " + left + " to be greater than right " + right,
+ left.compareTo(right) > 0);
+ assertTrue(
+ "Expected (RL) left " + left + " to be greater than right " + right,
+ right.compareTo(left) < 0);
+ }
+
+ private static <T extends Comparable<? super T>> void verifyCompareEquals(T left, T right) {
+ assertTrue(
+ "Expected (LR) left " + left + " to be compareEquals to right " + right,
+ left.compareTo(right) == 0);
+ assertTrue(
+ "Expected (RL) left " + left + " to be compareEquals to right " + right,
+ right.compareTo(left) == 0);
+ }
+
+ private static <T extends Serializable> byte[] serialize(T obj) throws IOException {
+ ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
+ try (ObjectOutputStream objectStream = new ObjectOutputStream(byteStream)) {
+ objectStream.writeObject(obj);
+ }
+ return byteStream.toByteArray();
+ }
+
+ private static <T extends Serializable> T deserialize(byte[] array, Class<T> klass)
+ throws IOException, ClassNotFoundException {
+ ByteArrayInputStream bais = new ByteArrayInputStream(array);
+ ObjectInputStream ois = new ObjectInputStream(bais);
+ Object obj = ois.readObject();
+ return klass.cast(obj);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <T extends Serializable> T serializeRoundTrip(T obj)
+ throws IOException, ClassNotFoundException {
+ Class<T> klass = (Class<T>) obj.getClass();
+ byte[] arr = serialize(obj);
+ T serialized = deserialize(arr, klass);
+ return serialized;
+ }
+
+ private static <T extends Serializable> void verifyEqualsAfterSerializing(T obj)
+ throws ClassNotFoundException, IOException {
+ T serialized = serializeRoundTrip(obj);
+ assertEquals("Expected values to be equal after serialization round-trip", obj, serialized);
+ }
+
+ private static Rational createIllegalRational(int numerator, int denominator) {
+ Rational r = new Rational(numerator, denominator);
+ mutateField(r, "mNumerator", numerator);
+ mutateField(r, "mDenominator", denominator);
+ return r;
+ }
+
+ private static <T> void mutateField(T object, String name, int value) {
+ try {
+ Field f = object.getClass().getDeclaredField(name);
+ f.setAccessible(true);
+ f.set(object, value);
+ } catch (NoSuchFieldException e) {
+ throw new AssertionError(e);
+ } catch (IllegalAccessException e) {
+ throw new AssertionError(e);
+ } catch (IllegalArgumentException e) {
+ throw new AssertionError(e);
+ }
+ }
+}
diff --git a/integration_tests/ctesque/src/sharedTest/java/android/webkit/CookieManagerTest.java b/integration_tests/ctesque/src/sharedTest/java/android/webkit/CookieManagerTest.java
index ea784e73b..351cf82ac 100644
--- a/integration_tests/ctesque/src/sharedTest/java/android/webkit/CookieManagerTest.java
+++ b/integration_tests/ctesque/src/sharedTest/java/android/webkit/CookieManagerTest.java
@@ -2,12 +2,8 @@ package android.webkit;
import static com.google.common.truth.Truth.assertThat;
-import android.os.Build.VERSION;
-import android.os.Build.VERSION_CODES;
-import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.After;
-import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.internal.DoNotInstrument;
@@ -17,14 +13,6 @@ import org.robolectric.annotation.internal.DoNotInstrument;
@RunWith(AndroidJUnit4.class)
public class CookieManagerTest {
- @Before
- public void setUp() {
- // Required to initialize native CookieManager for emulators with SDK < 19.
- if (VERSION.SDK_INT < VERSION_CODES.KITKAT) {
- CookieSyncManager.createInstance(ApplicationProvider.getApplicationContext());
- }
- }
-
@After
public void tearDown() {
CookieManager.getInstance().removeAllCookie();
@@ -67,4 +55,22 @@ public class CookieManagerTest {
String cookie = cookieManager.getCookie(httpsUrl);
assertThat(cookie).isEqualTo("ID=test-id");
}
+
+ @Test
+ public void shouldSetAndGetCookieWithWhitespacesInUrlParameters() {
+ CookieManager cookieManager = CookieManager.getInstance();
+ String url = "http://www.google.com/?q=This is a test query";
+ String value = "my cookie";
+ cookieManager.setCookie(url, value);
+ assertThat(cookieManager.getCookie(url)).isEqualTo(value);
+ }
+
+ @Test
+ public void shouldSetAndGetCookieWithEncodedWhitespacesInUrlParameters() {
+ CookieManager cookieManager = CookieManager.getInstance();
+ String url = "http://www.google.com/?q=This%20is%20a%20test%20query";
+ String value = "my cookie";
+ cookieManager.setCookie(url, value);
+ assertThat(cookieManager.getCookie(url)).isEqualTo(value);
+ }
}
diff --git a/integration_tests/jacoco-offline/build.gradle b/integration_tests/jacoco-offline/build.gradle
index e0d34c9e0..ea668f70a 100644
--- a/integration_tests/jacoco-offline/build.gradle
+++ b/integration_tests/jacoco-offline/build.gradle
@@ -14,7 +14,6 @@ configurations {
jacocoRuntime
}
-
dependencies {
testCompileOnly AndroidSdk.MAX_SDK.coordinates
testRuntimeOnly AndroidSdk.MAX_SDK.coordinates
diff --git a/integration_tests/kotlin/build.gradle b/integration_tests/kotlin/build.gradle
index a8bf910c8..550f08a45 100644
--- a/integration_tests/kotlin/build.gradle
+++ b/integration_tests/kotlin/build.gradle
@@ -23,6 +23,7 @@ compileTestKotlin {
dependencies {
api project(":robolectric")
compileOnly AndroidSdk.MAX_SDK.coordinates
+ implementation libs.androidx.annotation
testCompileOnly AndroidSdk.MAX_SDK.coordinates
testRuntimeOnly AndroidSdk.MAX_SDK.coordinates
diff --git a/integration_tests/kotlin/src/test/kotlin/org/robolectric/integrationtests/kotlin/CustomShadowImageView.kt b/integration_tests/kotlin/src/test/kotlin/org/robolectric/integrationtests/kotlin/CustomShadowImageView.kt
index 9b2a83a4c..2affab380 100644
--- a/integration_tests/kotlin/src/test/kotlin/org/robolectric/integrationtests/kotlin/CustomShadowImageView.kt
+++ b/integration_tests/kotlin/src/test/kotlin/org/robolectric/integrationtests/kotlin/CustomShadowImageView.kt
@@ -1,21 +1,21 @@
package org.robolectric.integrationtests.kotlin
import android.widget.ImageView
-import androidx.annotation.DrawableRes
import org.robolectric.annotation.Implementation
import org.robolectric.annotation.Implements
import org.robolectric.annotation.RealObject
+import org.robolectric.shadows.ShadowView
@Implements(ImageView::class)
-open class CustomShadowImageView {
+open class CustomShadowImageView : ShadowView() {
@RealObject lateinit var realImageView: ImageView
- @DrawableRes
- var setImageResource: Int = 0
+ var longClickPerformed: Boolean = false
private set
@Implementation
- protected fun setImageResource(resId: Int) {
- setImageResource = resId
+ protected override fun performLongClick(): Boolean {
+ longClickPerformed = true
+ return super.performLongClick()
}
}
diff --git a/integration_tests/kotlin/src/test/kotlin/org/robolectric/integrationtests/kotlin/CustomShadowImageViewTest.kt b/integration_tests/kotlin/src/test/kotlin/org/robolectric/integrationtests/kotlin/CustomShadowImageViewTest.kt
index 52a5b8bde..e641233d8 100644
--- a/integration_tests/kotlin/src/test/kotlin/org/robolectric/integrationtests/kotlin/CustomShadowImageViewTest.kt
+++ b/integration_tests/kotlin/src/test/kotlin/org/robolectric/integrationtests/kotlin/CustomShadowImageViewTest.kt
@@ -1,10 +1,12 @@
package org.robolectric.integrationtests.kotlin
+import android.app.Activity
+import android.view.ViewGroup
import android.widget.ImageView
-import androidx.test.core.app.ApplicationProvider
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
+import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import org.robolectric.shadow.api.Shadow
@@ -14,12 +16,14 @@ import org.robolectric.shadow.api.Shadow
class CustomShadowImageViewTest {
@Test
fun `use custom ShadowImageView`() {
- val imageView = ImageView(ApplicationProvider.getApplicationContext())
+ val activity = Robolectric.setupActivity(Activity::class.java)
+ val imageView = ImageView(activity)
+ (activity.findViewById(android.R.id.content) as ViewGroup).addView(imageView)
val shadowImageView = Shadow.extract<CustomShadowImageView>(imageView)
assertThat(shadowImageView).isNotNull()
assertThat(shadowImageView.realImageView).isSameInstanceAs(imageView)
val resourceId = Int.MAX_VALUE
- imageView.setImageResource(resourceId)
- assertThat(shadowImageView.setImageResource).isEqualTo(resourceId)
+ imageView.performLongClick()
+ assertThat(shadowImageView.longClickPerformed).isTrue()
}
}
diff --git a/integration_tests/memoryleaks/build.gradle b/integration_tests/memoryleaks/build.gradle
index 183138101..1b1f3f4aa 100644
--- a/integration_tests/memoryleaks/build.gradle
+++ b/integration_tests/memoryleaks/build.gradle
@@ -22,15 +22,13 @@ android {
includeAndroidResources = true
}
}
-
}
dependencies {
// Testing dependencies
- testImplementation project(path: ':testapp')
+ testImplementation project(":testapp")
testImplementation project(":robolectric")
testImplementation libs.junit4
testImplementation libs.guava.testlib
- testImplementation libs.guava.testlib
testImplementation libs.androidx.fragment
}
diff --git a/integration_tests/nativegraphics/Android.bp b/integration_tests/nativegraphics/Android.bp
index b5a7ffe20..06d38add9 100644
--- a/integration_tests/nativegraphics/Android.bp
+++ b/integration_tests/nativegraphics/Android.bp
@@ -59,6 +59,7 @@ android_robolectric_test {
"android.test.base",
"android.test.mock",
"truth",
+ "guava-android-testlib",
],
upstream: true,
java_resource_dirs: ["config"],
diff --git a/integration_tests/nativegraphics/build.gradle b/integration_tests/nativegraphics/build.gradle
index a243ea85c..7abd82afd 100644
--- a/integration_tests/nativegraphics/build.gradle
+++ b/integration_tests/nativegraphics/build.gradle
@@ -38,4 +38,5 @@ dependencies {
testImplementation libs.truth
testImplementation libs.junit4
testImplementation libs.mockito
+ testImplementation libs.guava.testlib
}
diff --git a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeAllocationRegistryTest.java b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeAllocationRegistryTest.java
index 14f74de93..0fce5d5af 100644
--- a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeAllocationRegistryTest.java
+++ b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeAllocationRegistryTest.java
@@ -1,16 +1,45 @@
package org.robolectric.integrationtests.nativegraphics;
+import static android.os.Build.VERSION_CODES.O;
+import static android.os.Build.VERSION_CODES.S;
+import static com.google.common.truth.Truth.assertThat;
+import static org.robolectric.util.reflector.Reflector.reflector;
+
import android.graphics.Bitmap;
import android.graphics.Color;
+import android.graphics.Matrix;
+import com.google.common.testing.GcFinalization;
+import java.lang.ref.WeakReference;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
+import org.robolectric.util.reflector.Accessor;
+import org.robolectric.util.reflector.ForType;
+import org.robolectric.util.reflector.Static;
@RunWith(RobolectricTestRunner.class)
-@Config(sdk = 30)
+@Config(minSdk = O)
public final class ShadowNativeAllocationRegistryTest {
+ @Test
+ public void applyFreeFunction_matrix() throws Exception {
+ WeakReference<Matrix> weakMatrix = new WeakReference<>(newMatrix());
+ // Invokes 'applyFreeFunction' when the matrix is GC'd.
+ GcFinalization.awaitClear(weakMatrix);
+ }
+
+ // Creates a new Matrix as a local variable, which is eligible for GC when it goes out
+ // of scope.
+ private Matrix newMatrix() {
+ Matrix matrix = new Matrix();
+ long pointer = reflector(MatrixReflector.class, matrix).getNativeInstance();
+ long freeFunction = reflector(MatrixReflector.class).nGetNativeFinalizer();
+ assertThat(pointer).isNotEqualTo(0);
+ assertThat(freeFunction).isNotEqualTo(0);
+ return matrix;
+ }
+ @Config(sdk = S) // No need to re-run on multiple SDK levels
@Test
public void nativeAllocationRegistryStressTest() {
for (int i = 0; i < 10_000; i++) {
@@ -21,4 +50,13 @@ public final class ShadowNativeAllocationRegistryTest {
}
}
}
+
+ @ForType(Matrix.class)
+ interface MatrixReflector {
+ @Accessor("native_instance")
+ long getNativeInstance();
+
+ @Static
+ long nGetNativeFinalizer();
+ }
}
diff --git a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeBitmapFactoryTest.java b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeBitmapFactoryTest.java
index 31961b6f4..418cf93f6 100644
--- a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeBitmapFactoryTest.java
+++ b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeBitmapFactoryTest.java
@@ -43,9 +43,11 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileDescriptor;
+import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.RandomAccessFile;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -488,6 +490,78 @@ public class ShadowNativeBitmapFactoryTest {
assertNull(BitmapFactory.decodeFileDescriptor(input, r, opt2));
}
+ @Test
+ public void testDecodeFileDescriptor2() throws IOException {
+ ParcelFileDescriptor pfd = obtainParcelDescriptor(obtainPath());
+ FileDescriptor input = pfd.getFileDescriptor();
+ Bitmap b = BitmapFactory.decodeFileDescriptor(input);
+
+ assertNotNull(b);
+ // Test the bitmap size
+ assertEquals(START_HEIGHT, b.getHeight());
+ assertEquals(START_WIDTH, b.getWidth());
+ }
+
+ @Test
+ public void testDecodeFileDescriptor3() throws IOException {
+ for (TestImage testImage : testImages()) {
+ // Arbitrary offsets to use. If the offset of the FD matches the offset of the image,
+ // decoding should succeed, but if they do not match, decoding should fail.
+ final long[] actualOffsets = new long[] {0, 17};
+ for (int j = 0; j < actualOffsets.length; ++j) {
+ long actualOffset = actualOffsets[j];
+ String path = obtainPath(testImage.id, actualOffset);
+ RandomAccessFile file = new RandomAccessFile(path, "r");
+ FileDescriptor fd = file.getFD();
+ assertTrue(fd.valid());
+
+ // Set the offset to ACTUAL_OFFSET
+ file.seek(actualOffset);
+ assertEquals(file.getFilePointer(), actualOffset);
+
+ // Now decode. This should be successful and leave the offset
+ // unchanged.
+ Bitmap b = BitmapFactory.decodeFileDescriptor(fd);
+ assertNotNull(b);
+ assertEquals(file.getFilePointer(), actualOffset);
+
+ // Now use the other offset. It should fail to decode, and
+ // the offset should remain unchanged.
+ long otherOffset = actualOffsets[(j + 1) % actualOffsets.length];
+ assertFalse(otherOffset == actualOffset);
+ file.seek(otherOffset);
+ assertEquals(file.getFilePointer(), otherOffset);
+
+ b = BitmapFactory.decodeFileDescriptor(fd);
+ assertNull(b);
+ assertEquals(file.getFilePointer(), otherOffset);
+ }
+ }
+ }
+
+ @Test
+ public void testDecodeFileDescriptor_seekPositionUnchanged() throws IOException {
+ int numEmptyBytes = 25;
+ // Create a file that contains 25 empty bytes as well as the image contents of R.drawable.start
+ File imageFile = obtainFile(R.drawable.start, numEmptyBytes);
+ FileInputStream fis = new FileInputStream(imageFile.getAbsoluteFile());
+
+ // Set the seek position to the start of the image data
+ assertThat(fis.skip(numEmptyBytes)).isEqualTo(numEmptyBytes);
+ Rect r = new Rect(1, 1, 1, 1);
+ int bytesAvailable = fis.available();
+ Bitmap b = BitmapFactory.decodeFileDescriptor(fis.getFD(), r, opt1);
+ assertThat(b).isNotNull();
+
+ // Check that the seek position hasn't changed
+ assertThat(fis.available()).isEqualTo(bytesAvailable);
+
+ // Test the bitmap size
+ assertEquals(START_HEIGHT, b.getHeight());
+ assertEquals(START_WIDTH, b.getWidth());
+ fis.close();
+ }
+
private byte[] obtainArray() {
ByteArrayOutputStream stm = new ByteArrayOutputStream();
Options opt = new BitmapFactory.Options();
diff --git a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeBitmapTest.java b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeBitmapTest.java
index 714e12da7..fbbff298c 100644
--- a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeBitmapTest.java
+++ b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeBitmapTest.java
@@ -60,6 +60,7 @@ import org.robolectric.RuntimeEnvironment;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowBitmap;
import org.robolectric.shadows.ShadowNativeBitmap;
+import org.robolectric.versioning.AndroidVersions.U;
@org.robolectric.annotation.Config(minSdk = O)
@RunWith(RobolectricTestRunner.class)
@@ -1580,6 +1581,7 @@ public class ShadowNativeBitmapTest {
assertFalse(bitmap2.sameAs(bitmap1));
}
+ @org.robolectric.annotation.Config(maxSdk = U.SDK_INT) // TODO(hoisie): fix in V and above
@Test
public void testSameAs_hardware() {
Bitmap bitmap1 = BitmapFactory.decodeResource(res, R.drawable.robot, HARDWARE_OPTIONS);
@@ -1588,7 +1590,13 @@ public class ShadowNativeBitmapTest {
Bitmap bitmap4 = BitmapFactory.decodeResource(res, R.drawable.start, HARDWARE_OPTIONS);
assertTrue(bitmap1.sameAs(bitmap2));
assertTrue(bitmap2.sameAs(bitmap1));
- assertFalse(bitmap1.sameAs(bitmap3));
+
+ // Note: on an emulator or real device, the HARDWARE bitmap1 and the Software bitmap3 differ
+ // because the pixels cannot be read from the underlying hardware buffer. However, after the fix
+ // from r.android.com/2887086, Robolectric does actually properly fill the buffer content of a
+ // HARDWARE bitmap, which means it now is the same as its non-hardware counterpart.
+ assertTrue(bitmap1.sameAs(bitmap3));
+
assertFalse(bitmap1.sameAs(bitmap4));
}
@@ -1732,6 +1740,13 @@ public class ShadowNativeBitmapTest {
assertThat(bitmap.getColorSpace()).isEqualTo(ColorSpace.get(ColorSpace.Named.ADOBE_RGB));
}
+ @org.robolectric.annotation.Config(minSdk = U.SDK_INT)
+ @Test
+ public void noGainmap_returnsNull() {
+ Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
+ assertThat(bitmap.getGainmap()).isNull();
+ }
+
@Test
public void compress_thenDecodeStream_sameAs() {
Bitmap bitmap = Bitmap.createBitmap(/* width= */ 10, /* height= */ 10, Bitmap.Config.ARGB_8888);
diff --git a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativePaintTest.java b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativePaintTest.java
index 924da1a2c..1705f2b3f 100644
--- a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativePaintTest.java
+++ b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativePaintTest.java
@@ -48,6 +48,7 @@ import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.util.reflector.ForType;
+import org.robolectric.versioning.AndroidVersions.U;
@RunWith(RobolectricTestRunner.class)
@Config(minSdk = O)
@@ -1989,6 +1990,7 @@ public class ShadowNativePaintTest {
}
}
+ @Config(maxSdk = U.SDK_INT) // TODO(hoisie): fix in V and above
@Test
public void testElegantText() {
final Paint p = new Paint();
diff --git a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeRuntimeShaderTest.java b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeRuntimeShaderTest.java
index 9f06cf59a..4d373f9ab 100644
--- a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeRuntimeShaderTest.java
+++ b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeRuntimeShaderTest.java
@@ -15,6 +15,7 @@ import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.ReflectionHelpers.ClassParameter;
+import org.robolectric.versioning.AndroidVersions.U;
@Config(minSdk = S)
@RunWith(AndroidJUnit4.class)
@@ -68,7 +69,8 @@ public class ShadowNativeRuntimeShaderTest {
ClassParameter.from(boolean.class, false));
}
- @Config(minSdk = TIRAMISU)
+ /** {@link #SKSL} does not compile on V and above. */
+ @Config(minSdk = TIRAMISU, maxSdk = U.SDK_INT)
@Test
public void testConstructorT() {
var unused = new RuntimeShader(SKSL);
diff --git a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeSurfaceTest.java b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeSurfaceTest.java
new file mode 100644
index 000000000..c3326ccc6
--- /dev/null
+++ b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeSurfaceTest.java
@@ -0,0 +1,31 @@
+package org.robolectric.integrationtests.nativegraphics;
+
+import static android.os.Build.VERSION_CODES.O;
+
+import android.graphics.SurfaceTexture;
+import android.view.Surface;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+@RunWith(AndroidJUnit4.class)
+@Config(minSdk = O)
+public class ShadowNativeSurfaceTest {
+ @Test
+ public void surface_construction() {
+ // Invoke the public/hidden no-op constructor. Although it's public, it's not available in a
+ // Gradle environment, because integration_tests/nativegrapics is a com.android.library project,
+ // which uses the stubs jar, so only public signatures available during compile-time.
+ Surface s = Shadow.newInstanceOf(Surface.class);
+ s.release();
+ }
+
+ @Test
+ public void surface_construction_surfaceTexture() {
+ SurfaceTexture st = new SurfaceTexture(false);
+ Surface s = new Surface(st);
+ s.release();
+ }
+}
diff --git a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeVectorDrawableTest.java b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeVectorDrawableTest.java
index 7ffe54f13..9b4f6ee0a 100644
--- a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeVectorDrawableTest.java
+++ b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeVectorDrawableTest.java
@@ -50,6 +50,7 @@ import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowDrawable;
+import org.robolectric.versioning.AndroidVersions.U;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -217,26 +218,31 @@ public class ShadowNativeVectorDrawableTest {
resources = context.getResources();
}
+ @Config(maxSdk = U.SDK_INT) // TODO(hoisie): update this test for V
@Test
public void testBasicVectorDrawables() throws XmlPullParserException, IOException {
verifyVectorDrawables(BASIC_ICON_RES_IDS, BASIC_GOLDEN_IMAGES, null);
}
+ @Config(maxSdk = U.SDK_INT) // TODO(hoisie): update this test for V
@Test
public void testLMVectorDrawables() throws XmlPullParserException, IOException {
verifyVectorDrawables(L_M_ICON_RES_IDS, L_M_GOLDEN_IMAGES, null);
}
+ @Config(maxSdk = U.SDK_INT) // TODO(hoisie): update this test for V
@Test
public void testNVectorDrawables() throws XmlPullParserException, IOException {
verifyVectorDrawables(N_ICON_RES_IDS, N_GOLDEN_IMAGES, null);
}
+ @Config(maxSdk = U.SDK_INT) // TODO(hoisie): update this test for V
@Test
public void testVectorDrawableGradient() throws XmlPullParserException, IOException {
verifyVectorDrawables(GRADIENT_ICON_RES_IDS, GRADIENT_GOLDEN_IMAGES, null);
}
+ @Config(maxSdk = U.SDK_INT) // TODO(hoisie): update this test for V
@Test
public void testColorStateList() throws XmlPullParserException, IOException {
for (int i = 0; i < STATEFUL_STATE_SETS.length; i++) {
diff --git a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/SystemFontsQTest.java b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/SystemFontsQTest.java
new file mode 100644
index 000000000..5d0b745c0
--- /dev/null
+++ b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/SystemFontsQTest.java
@@ -0,0 +1,25 @@
+package org.robolectric.integrationtests.nativegraphics;
+
+import static android.os.Build.VERSION_CODES.Q;
+import static android.os.Build.VERSION_CODES.R;
+
+import android.graphics.fonts.SystemFonts;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+@RunWith(AndroidJUnit4.class)
+public class SystemFontsQTest {
+ /**
+ * A test to ensure that {@link SystemFonts#getAvailableFonts} will trigger RNG to load if it is
+ * called early in a test run.
+ *
+ * <p>This should be the first test that is run in this test class.
+ */
+ @Config(minSdk = Q, maxSdk = R)
+ @Test
+ public void getAvailableFonts() {
+ SystemFonts.getAvailableFonts();
+ }
+}
diff --git a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/testing/util/DrawCountDown.java b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/testing/util/DrawCountDown.java
deleted file mode 100644
index e1ad49593..000000000
--- a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/testing/util/DrawCountDown.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package org.robolectric.integrationtests.nativegraphics.testing.util;
-
-import android.view.View;
-import android.view.ViewTreeObserver.OnPreDrawListener;
-import java.util.HashSet;
-import java.util.Set;
-
-public class DrawCountDown implements OnPreDrawListener {
- private static Set<DrawCountDown> pendingCallbacks = new HashSet<>();
-
- private int drawCount;
- private View targetView;
- private Runnable runnable;
-
- private DrawCountDown(View targetView, int countFrames, Runnable countReachedListener) {
- this.targetView = targetView;
- drawCount = countFrames;
- runnable = countReachedListener;
- }
-
- @Override
- public boolean onPreDraw() {
- if (drawCount <= 0) {
- synchronized (pendingCallbacks) {
- pendingCallbacks.remove(this);
- }
- targetView.getViewTreeObserver().removeOnPreDrawListener(this);
- runnable.run();
- } else {
- drawCount--;
- targetView.postInvalidate();
- }
- return true;
- }
-
- public static void countDownDraws(
- View targetView, int countFrames, Runnable onDrawCountReachedListener) {
- DrawCountDown counter = new DrawCountDown(targetView, countFrames, onDrawCountReachedListener);
- synchronized (pendingCallbacks) {
- pendingCallbacks.add(counter);
- }
- targetView.getViewTreeObserver().addOnPreDrawListener(counter);
- }
-
- public static void cancelPending() {
- synchronized (pendingCallbacks) {
- for (DrawCountDown counter : pendingCallbacks) {
- counter.targetView.getViewTreeObserver().removeOnPreDrawListener(counter);
- }
- pendingCallbacks.clear();
- }
- }
-}
diff --git a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/testing/util/SneakyThrow.java b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/testing/util/SneakyThrow.java
deleted file mode 100644
index 6b4367a88..000000000
--- a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/testing/util/SneakyThrow.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.robolectric.integrationtests.nativegraphics.testing.util;
-
-/**
- * Provides a hacky method that always throws {@code t} even if {@code t} is a checked exception.
- * and is not declared to be thrown.
- *
- * <p>See http://www.mail-archive.com/javaposse@googlegroups.com/msg05984.html
- */
-public final class SneakyThrow {
- /**
- * A hacky method that always throws {@code t} even if {@code t} is a checked exception, and is
- * not declared to be thrown.
- */
- public static void sneakyThrow(Throwable t) {
- SneakyThrow.<RuntimeException>sneakyThrowInternal(t);
- }
-
- @SuppressWarnings({"unchecked"})
- private static <T extends Throwable> void sneakyThrowInternal(Throwable t) throws T {
- throw (T) t;
- }
-
- private SneakyThrow() {}
-}
diff --git a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/testing/util/WebViewReadyHelper.java b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/testing/util/WebViewReadyHelper.java
deleted file mode 100644
index 80e96c982..000000000
--- a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/testing/util/WebViewReadyHelper.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package org.robolectric.integrationtests.nativegraphics.testing.util;
-
-import android.view.ViewTreeObserver.OnDrawListener;
-import android.webkit.WebView;
-import android.webkit.WebView.VisualStateCallback;
-import android.webkit.WebViewClient;
-import java.util.concurrent.CountDownLatch;
-
-public final class WebViewReadyHelper {
- // Hacky quick-fix similar to DrawActivity's DrawCounterListener
- // TODO: De-dupe this against DrawCounterListener and fix this cruft
- private static final int DEBUG_REQUIRE_EXTRA_FRAMES = 1;
- private int drawCount = 0;
-
- private final CountDownLatch latch;
- private final WebView webView;
-
- public WebViewReadyHelper(WebView webView, CountDownLatch latch) {
- this.webView = webView;
- this.latch = latch;
- this.webView.setWebViewClient(client);
- }
-
- public void loadData(String data) {
- webView.loadData(data, null, null);
- }
-
- private WebViewClient client =
- new WebViewClient() {
- @Override
- public void onPageFinished(WebView view, String url) {
- webView.postVisualStateCallback(0, visualStateCallback);
- }
- };
-
- private VisualStateCallback visualStateCallback =
- new VisualStateCallback() {
- @Override
- public void onComplete(long requestId) {
- webView.getViewTreeObserver().addOnDrawListener(onDrawListener);
- webView.invalidate();
- }
- };
-
- private OnDrawListener onDrawListener =
- new OnDrawListener() {
- @Override
- public void onDraw() {
- if (++drawCount <= DEBUG_REQUIRE_EXTRA_FRAMES) {
- webView.postInvalidate();
- return;
- }
-
- webView.post(
- () -> {
- webView.getViewTreeObserver().removeOnDrawListener(onDrawListener);
- latch.countDown();
- });
- }
- };
-}
diff --git a/integration_tests/play_services/build.gradle b/integration_tests/play_services/build.gradle
index 0e05dd6b3..c7e522020 100644
--- a/integration_tests/play_services/build.gradle
+++ b/integration_tests/play_services/build.gradle
@@ -11,5 +11,5 @@ dependencies {
testRuntimeOnly AndroidSdk.MAX_SDK.coordinates
testImplementation libs.junit4
testImplementation libs.truth
- testImplementation "com.google.android.gms:play-services-basement:18.0.1"
+ testImplementation libs.play.services.basement
}
diff --git a/integration_tests/roborazzi/build.gradle b/integration_tests/roborazzi/build.gradle
new file mode 100644
index 000000000..7c7000c5b
--- /dev/null
+++ b/integration_tests/roborazzi/build.gradle
@@ -0,0 +1,65 @@
+import org.robolectric.gradle.AndroidProjectConfigPlugin
+
+apply plugin: 'com.android.library'
+apply plugin: AndroidProjectConfigPlugin
+apply plugin: 'kotlin-android'
+apply plugin: "com.diffplug.spotless"
+apply plugin: "io.github.takahirom.roborazzi"
+
+spotless {
+ kotlin {
+ target '**/*.kt'
+ ktfmt('0.42').googleStyle()
+ }
+}
+
+android {
+ compileSdk 34
+ namespace 'org.robolectric.integration.roborazzi'
+
+ defaultConfig {
+ minSdk 21
+ targetSdk 34
+ }
+
+ compileOptions {
+ sourceCompatibility = '1.8'
+ targetCompatibility = '1.8'
+ }
+
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ testOptions {
+ unitTests {
+ includeAndroidResources = true
+ all {
+ // For Roborazzi users, please use Roborazzi plugin and gradle.properties instead of this.
+ // https://takahirom.github.io/roborazzi/how-to-use.html#roborazzi-gradle-properties-options
+
+ // Change naming strategy of screenshots.
+ // org.robolectric.....RoborazziCaptureTest.checkDialogRendering.png -> RoborazziCaptureTest.checkDialogRendering.png
+ systemProperty 'roborazzi.record.namingStrategy', 'testClassAndMethod'
+
+ // Use RoborazziRule's base path when you use captureRoboImage(path).
+ systemProperty 'roborazzi.record.filePathStrategy', 'relativePathFromRoborazziContextOutputDirectory'
+ }
+ }
+ }
+ androidComponents {
+ beforeVariants(selector().all()) { variantBuilder ->
+ // Roborazzi does not support AndroidTest.
+ variantBuilder.enableAndroidTest = false
+ }
+ }
+}
+
+dependencies {
+ api project(":robolectric")
+ testImplementation libs.androidx.test.core
+ testImplementation libs.junit4
+ testImplementation libs.truth
+ testImplementation libs.roborazzi
+ testImplementation libs.roborazzi.rule
+}
diff --git a/integration_tests/roborazzi/src/screenshots/RoborazziCaptureTest.checkDialogRendering[31].png b/integration_tests/roborazzi/src/screenshots/RoborazziCaptureTest.checkDialogRendering[31].png
new file mode 100644
index 000000000..19bd098c3
--- /dev/null
+++ b/integration_tests/roborazzi/src/screenshots/RoborazziCaptureTest.checkDialogRendering[31].png
Binary files differ
diff --git a/integration_tests/roborazzi/src/screenshots/RoborazziCaptureTest.checkViewWithElevationRendering[31].png b/integration_tests/roborazzi/src/screenshots/RoborazziCaptureTest.checkViewWithElevationRendering[31].png
new file mode 100644
index 000000000..5780ff86c
--- /dev/null
+++ b/integration_tests/roborazzi/src/screenshots/RoborazziCaptureTest.checkViewWithElevationRendering[31].png
Binary files differ
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
new file mode 100644
index 000000000..9adbe305e
--- /dev/null
+++ b/integration_tests/roborazzi/src/test/java/org/robolectric/integration/roborazzi/RoborazziCaptureTest.kt
@@ -0,0 +1,196 @@
+package org.robolectric.integration.roborazzi
+
+import android.app.Activity
+import android.app.AlertDialog
+import android.app.Application
+import android.content.ComponentName
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.os.Build.VERSION_CODES.S
+import android.os.Bundle
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.test.core.app.ActivityScenario
+import androidx.test.platform.app.InstrumentationRegistry
+import com.github.takahirom.roborazzi.ExperimentalRoborazziApi
+import com.github.takahirom.roborazzi.RoborazziOptions
+import com.github.takahirom.roborazzi.RoborazziRule
+import com.github.takahirom.roborazzi.captureScreenRoboImage
+import kotlin.reflect.KClass
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.Shadows
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.GraphicsMode
+import org.robolectric.integration.roborazzi.RoborazziDialogTestActivity.Companion.OUTPUT_DIRECTORY_PATH
+
+/**
+ * Integration Test for Roborazzi
+ *
+ * This test is not intended to obstruct the release of Robolectric. In the event that issues are
+ * detected which do not stem from Robolectric, the test can be temporarily disabled, and an issue
+ * can be reported on the Roborazzi repository.
+ *
+ * Run ./gradlew integration_tests:roborazzi:recordRoborazziDebug
+ * -Drobolectric.alwaysIncludeVariantMarkersInTestName=true to record the reference
+ * screenshots(golden images). Run ./gradlew integration_tests:roborazzi:verifyRoborazziDebug
+ * -Drobolectric.alwaysIncludeVariantMarkersInTestName=true to check the screenshots.
+ */
+@RunWith(RobolectricTestRunner::class)
+@Config(sdk = [S])
+@GraphicsMode(GraphicsMode.Mode.NATIVE)
+@OptIn(ExperimentalRoborazziApi::class)
+class RoborazziCaptureTest {
+ @get:Rule
+ val roborazziRule =
+ RoborazziRule(
+ options =
+ RoborazziRule.Options(
+ outputDirectoryPath = OUTPUT_DIRECTORY_PATH,
+ roborazziOptions =
+ RoborazziOptions(
+ recordOptions =
+ RoborazziOptions.RecordOptions(
+ resizeScale = 0.5,
+ )
+ )
+ )
+ )
+
+ @Test
+ // For reducing repository size, we use small size
+ @Config(qualifiers = "w50dp-h40dp")
+ fun checkViewWithElevationRendering() {
+ hardwareRendererEnvironment {
+ setupActivity(RoborazziViewWithElevationTestActivity::class)
+
+ captureScreenWithRoborazzi()
+ }
+ }
+
+ @Test
+ // For reducing repository size, we use small size
+ @Config(qualifiers = "w110dp-h120dp")
+ fun checkDialogRendering() {
+ hardwareRendererEnvironment {
+ setupActivity(RoborazziDialogTestActivity::class)
+
+ captureScreenWithRoborazzi()
+ }
+ }
+
+ private fun setupActivity(activityClass: KClass<out Activity>) {
+ registerActivityToPackageManager(checkNotNull(activityClass.java.canonicalName))
+ ActivityScenario.launch(activityClass.java)
+ }
+
+ private fun captureScreenWithRoborazzi() {
+ try {
+ captureScreenRoboImage()
+ } catch (e: AssertionError) {
+ throw AssertionError(
+ """
+ |${e.message}
+ |Please check the screenshot in $OUTPUT_DIRECTORY_PATH
+ |If you want to update the screenshot,
+ |run `./gradlew integration_tests:roborazzi:recordRoborazziDebug -Drobolectric.alwaysIncludeVariantMarkersInTestName=true` and commit the changes.
+ |"""
+ .trimMargin(),
+ e
+ )
+ }
+ }
+
+ companion object {
+ const val USE_HARDWARE_RENDERER_NATIVE_ENV = "robolectric.screenshot.hwrdr.native"
+ }
+}
+
+private fun registerActivityToPackageManager(activity: String) {
+ val appContext: Application =
+ InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as Application
+ Shadows.shadowOf(appContext.packageManager)
+ .addActivityIfNotPresent(
+ ComponentName(
+ appContext.packageName,
+ activity,
+ )
+ )
+}
+
+private fun hardwareRendererEnvironment(block: () -> Unit) {
+ val originalHwrdrOption =
+ System.getProperty(RoborazziCaptureTest.USE_HARDWARE_RENDERER_NATIVE_ENV, null)
+ // This cause ClassNotFoundException: java.nio.NioUtils
+ // TODO: Remove comment out after fix this issue
+ // https://github.com/robolectric/robolectric/issues/8081#issuecomment-1858726896
+ // System.setProperty(USE_HARDWARE_RENDERER_NATIVE_ENV, "true")
+ try {
+ block()
+ } finally {
+ if (originalHwrdrOption == null) {
+ System.clearProperty(RoborazziCaptureTest.USE_HARDWARE_RENDERER_NATIVE_ENV)
+ } else {
+ System.setProperty(RoborazziCaptureTest.USE_HARDWARE_RENDERER_NATIVE_ENV, originalHwrdrOption)
+ }
+ }
+}
+
+private class RoborazziViewWithElevationTestActivity : Activity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ setTheme(android.R.style.Theme_Light_NoTitleBar)
+ super.onCreate(savedInstanceState)
+ setContentView(
+ LinearLayout(this).apply {
+ orientation = LinearLayout.VERTICAL
+ fun Int.toDp(): Int = (this * resources.displayMetrics.density).toInt()
+
+ // View with elevation
+ addView(
+ FrameLayout(this@RoborazziViewWithElevationTestActivity).apply {
+ background = ColorDrawable(Color.MAGENTA)
+ elevation = 10f
+ addView(TextView(this.context).apply { text = "Txt" })
+ },
+ LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT,
+ LinearLayout.LayoutParams.MATCH_PARENT
+ )
+ .apply { setMargins(10.toDp(), 10.toDp(), 10.toDp(), 10.toDp()) }
+ )
+ }
+ )
+ }
+}
+
+private class RoborazziDialogTestActivity : Activity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ setTheme(android.R.style.Theme_DeviceDefault_Light_NoActionBar)
+ super.onCreate(savedInstanceState)
+ setContentView(
+ LinearLayout(this).apply {
+ orientation = LinearLayout.VERTICAL
+ fun Int.toDp(): Int = (this * resources.displayMetrics.density).toInt()
+
+ // View with elevation
+ addView(
+ TextView(this.context).apply { text = "Under the dialog" },
+ LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.WRAP_CONTENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT
+ )
+ .apply { setMargins(10.toDp(), 10.toDp(), 10.toDp(), 10.toDp()) }
+ )
+ }
+ )
+ AlertDialog.Builder(this).setTitle("Dlg").setPositiveButton("OK") { _, _ -> }.show()
+ }
+
+ companion object {
+ const val OUTPUT_DIRECTORY_PATH = "src/screenshots"
+ }
+}
diff --git a/integration_tests/room/build.gradle b/integration_tests/room/build.gradle
index 1798d40ba..4a12143be 100644
--- a/integration_tests/room/build.gradle
+++ b/integration_tests/room/build.gradle
@@ -22,17 +22,16 @@ android {
includeAndroidResources = true
}
}
-
}
dependencies {
// Testing dependencies
- testImplementation project(path: ':testapp')
+ testImplementation project(":testapp")
testImplementation project(":robolectric")
testImplementation libs.junit4
testImplementation libs.guava.testlib
testImplementation libs.guava.testlib
testImplementation libs.truth
- implementation 'androidx.room:room-runtime:2.6.0'
- annotationProcessor 'androidx.room:room-compiler:2.6.0'
+ implementation libs.androidx.room.runtime
+ annotationProcessor libs.androidx.room.compiler
}
diff --git a/nativeruntime/build.gradle b/nativeruntime/build.gradle
index e784984ab..65f1e60a9 100644
--- a/nativeruntime/build.gradle
+++ b/nativeruntime/build.gradle
@@ -1,5 +1,3 @@
-import groovy.json.JsonSlurper
-import java.nio.charset.StandardCharsets
import org.robolectric.gradle.DeployedRoboJavaModulePlugin
import org.robolectric.gradle.RoboJavaModulePlugin
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/BitmapNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/BitmapNatives.java
index b273771c4..8bc2e3656 100644
--- a/nativeruntime/src/main/java/org/robolectric/nativeruntime/BitmapNatives.java
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/BitmapNatives.java
@@ -130,5 +130,7 @@ public final class BitmapNatives {
public static native boolean nativeIsBackedByAshmem(long nativePtr);
+ public static native void nativeCopyColorSpaceP(long srcBitmap, long dstBitmap);
+
private BitmapNatives() {}
}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLoader.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLoader.java
index 3892453dd..78c9b20a8 100644
--- a/nativeruntime/src/main/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLoader.java
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLoader.java
@@ -75,8 +75,7 @@ public class DefaultNativeRuntimeLoader implements NativeRuntimeLoader {
"loadNativeRuntime",
() -> {
extractDirectory = new TempDirectory("nativeruntime");
- System.setProperty(
- "robolectric.nativeruntime.languageTag", Locale.getDefault().toLanguageTag());
+ System.setProperty("icu.locale.default", Locale.getDefault().toLanguageTag());
if (Build.VERSION.SDK_INT >= O) {
maybeCopyFonts(extractDirectory);
}
@@ -97,9 +96,9 @@ public class DefaultNativeRuntimeLoader implements NativeRuntimeLoader {
return;
}
Path icuPath = tempDirectory.create("icu");
- Path icuDatPath = tempDirectory.getBasePath().resolve("icu/icudt68l.dat");
+ Path icuDatPath = icuPath.resolve("icudt68l.dat");
Resources.asByteSource(icuDatUrl).copyTo(Files.asByteSink(icuDatPath.toFile()));
- System.setProperty("icu.dir", icuPath.toAbsolutePath().toString());
+ System.setProperty("icu.data.path", icuDatPath.toAbsolutePath().toString());
}
/**
@@ -148,8 +147,6 @@ public class DefaultNativeRuntimeLoader implements NativeRuntimeLoader {
private void loadLibrary(TempDirectory tempDirectory) throws IOException {
String libraryName = System.mapLibraryName("robolectric-nativeruntime");
- System.setProperty(
- "robolectric.nativeruntime.languageTag", Locale.getDefault().toLanguageTag());
Path libraryPath = tempDirectory.getBasePath().resolve(libraryName);
URL libraryResource = Resources.getResource(nativeLibraryPath());
Resources.asByteSource(libraryResource).copyTo(Files.asByteSink(libraryPath.toFile()));
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/ImageReaderNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/ImageReaderNatives.java
index 2b7035b86..5ea61ac9f 100644
--- a/nativeruntime/src/main/java/org/robolectric/nativeruntime/ImageReaderNatives.java
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/ImageReaderNatives.java
@@ -63,6 +63,8 @@ public final class ImageReaderNatives {
public synchronized native void nativeDiscardFreeBuffers(); // Q+
/**
+ * Setup image in native level.
+ *
* @return A return code {@code ACQUIRE_*}
* @see #ACQUIRE_SUCCESS
* @see #ACQUIRE_NO_BUFS
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/RenderNodeNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/RenderNodeNatives.java
index adda69e61..52c6b7203 100644
--- a/nativeruntime/src/main/java/org/robolectric/nativeruntime/RenderNodeNatives.java
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/RenderNodeNatives.java
@@ -43,6 +43,8 @@ public final class RenderNodeNatives {
public static native void nEndAllAnimators(long renderNode);
+ public static native void nForceEndAnimators(long renderNode);
+
public static native void nDiscardDisplayList(long renderNode);
public static native boolean nIsValid(long renderNode);
diff --git a/nativeruntime/src/main/resources/icu/icudt.dat b/nativeruntime/src/main/resources/icu/icudt.dat
new file mode 100644
index 000000000..e25a15ab9
--- /dev/null
+++ b/nativeruntime/src/main/resources/icu/icudt.dat
Binary files differ
diff --git a/nativeruntime/src/test/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLazyLoadTest.java b/nativeruntime/src/test/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLazyLoadTest.java
index 5fa5ad364..d432b9497 100644
--- a/nativeruntime/src/test/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLazyLoadTest.java
+++ b/nativeruntime/src/test/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLazyLoadTest.java
@@ -1,7 +1,7 @@
package org.robolectric.nativeruntime;
+import static android.os.Build.VERSION_CODES.KITKAT;
import static com.google.common.truth.Truth.assertThat;
-import static org.robolectric.annotation.Config.ALL_SDKS;
import android.app.Application;
import android.database.CursorWindow;
@@ -10,15 +10,18 @@ import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
+import org.robolectric.versioning.AndroidVersions.U;
@RunWith(RobolectricTestRunner.class)
-@Config(sdk = ALL_SDKS)
+@Config(minSdk = KITKAT, maxSdk = U.SDK_INT)
public final class DefaultNativeRuntimeLazyLoadTest {
/**
* Checks to see that RNR is not loaded by default when an empty application is created. RNR load
* times are typically 0.5-1s, so it is desirable to have it lazy loaded when native code is
* called.
+ *
+ * <p>Note that lazy loading is disabled for V and above.
*/
@SuppressWarnings("UnusedVariable")
@Test
diff --git a/preinstrumented/build.gradle b/preinstrumented/build.gradle
index dc1e5d0d5..0de49e0bf 100644
--- a/preinstrumented/build.gradle
+++ b/preinstrumented/build.gradle
@@ -46,11 +46,11 @@ tasks.register('instrumentAll') {
}
}
-tasks.register('sourcesJar', Jar) {
+tasks.register('emptySourcesJar', Jar) {
archiveClassifier = "sources"
}
-tasks.register('javadocJar', Jar) {
+tasks.register('emptyJavadocJar', Jar) {
archiveClassifier = "javadoc"
}
@@ -68,8 +68,8 @@ if (System.getenv('PUBLISH_PREINSTRUMENTED_JARS') == "true") {
"sdk${androidSdk.apiLevel}"(MavenPublication) {
artifact "${buildDir}/${androidSdk.preinstrumentedJarFileName}"
artifactId 'android-all-instrumented'
- artifact sourcesJar
- artifact javadocJar
+ artifact emptySourcesJar
+ artifact emptyJavadocJar
version androidSdk.preinstrumentedVersion
pom {
@@ -118,6 +118,20 @@ if (System.getenv('PUBLISH_PREINSTRUMENTED_JARS') == "true") {
sign publishing.publications."sdk${androidSdk.apiLevel}"
}
}
+
+
+ // Workaround for https://github.com/gradle/gradle/issues/26132
+ // For some reason, Gradle has inferred that all publishing tasks depend on all signing tasks,
+ // so we must explicitly declare this here.
+ afterEvaluate {
+ tasks.all {
+ if (name.startsWith("publishSdk")) {
+ sdksToInstrument().each { androidSdk ->
+ dependsOn(tasks.named("signSdk${androidSdk.apiLevel}Publication"))
+ }
+ }
+ }
+ }
}
static def sdksToInstrument() {
diff --git a/preinstrumented/src/main/java/org/robolectric/preinstrumented/JarInstrumentor.java b/preinstrumented/src/main/java/org/robolectric/preinstrumented/JarInstrumentor.java
index 1410c33d5..d0665562b 100644
--- a/preinstrumented/src/main/java/org/robolectric/preinstrumented/JarInstrumentor.java
+++ b/preinstrumented/src/main/java/org/robolectric/preinstrumented/JarInstrumentor.java
@@ -20,7 +20,6 @@ import org.robolectric.internal.bytecode.ClassInstrumentor;
import org.robolectric.internal.bytecode.ClassNodeProvider;
import org.robolectric.internal.bytecode.InstrumentationConfiguration;
import org.robolectric.internal.bytecode.Interceptors;
-import org.robolectric.internal.bytecode.NativeCallHandler;
import org.robolectric.util.inject.Injector;
import org.robolectric.versioning.AndroidVersionInitTools;
import org.robolectric.versioning.AndroidVersions.AndroidRelease;
@@ -51,35 +50,15 @@ public class JarInstrumentor {
@VisibleForTesting
void processCommandLine(String[] args) throws IOException, ClassNotFoundException {
- if (args.length >= 2) {
+ if (args.length == 2) {
File sourceFile = new File(args[0]);
File destJarFile = new File(args[1]);
- File destNativesFile = null;
- boolean throwOnNatives = false;
- boolean parseError = false;
- for (int i = 2; i < args.length; i++) {
- if (args[i].startsWith("--write-natives=")) {
- destNativesFile = new File(args[i].substring("--write-natives=".length()));
- } else if (args[i].equals("--throw-on-natives")) {
- throwOnNatives = true;
- } else {
- System.err.println("Unknown argument: " + args[i]);
- parseError = true;
- break;
- }
- }
-
- if (!parseError) {
- instrumentJar(sourceFile, destJarFile, destNativesFile, throwOnNatives);
- return;
- }
+ instrumentJar(sourceFile, destJarFile);
+ return;
}
- System.err.println(
- "Usage: JarInstrumentor <source jar> <dest jar> "
- + "[--write-natives=<file>] "
- + "[--throw-on-natives]");
+ System.err.println("Usage: JarInstrumentor <source jar> <dest jar> ");
exit(1);
}
@@ -94,13 +73,9 @@ public class JarInstrumentor {
*
* @param sourceJarFile The source JAR to process.
* @param destJarFile The destination JAR with the instrumented method calls.
- * @param destNativesFile Optional file to write native calls signature. Null to disable.
- * @param throwOnNatives Whether native calls should be instrumented as throwing a dedicated
- * exception (true) or no-op (false).
*/
@VisibleForTesting
- protected void instrumentJar(
- File sourceJarFile, File destJarFile, File destNativesFile, boolean throwOnNatives)
+ protected void instrumentJar(File sourceJarFile, File destJarFile)
throws IOException, ClassNotFoundException {
long startNs = System.nanoTime();
JarFile jarFile = new JarFile(sourceJarFile);
@@ -112,23 +87,6 @@ public class JarInstrumentor {
}
};
- NativeCallHandler nativeCallHandler;
- final boolean writeNativesFile = destNativesFile != null;
-
- if (destNativesFile == null) {
- destNativesFile =
- new File(
- sourceJarFile.getParentFile(),
- sourceJarFile.getName().replace(".jar", "-natives.txt"));
- }
-
- try {
- nativeCallHandler = new NativeCallHandler(destNativesFile, writeNativesFile, throwOnNatives);
- classInstrumentor.setNativeCallHandler(nativeCallHandler);
- } catch (IOException e) {
- throw new AssertionError("Unable to load native exemptions file", e);
- }
-
int nonClassCount = 0;
int classCount = 0;
@@ -176,10 +134,6 @@ public class JarInstrumentor {
}
}
- if (writeNativesFile) {
- nativeCallHandler.writeExemptionsList();
- }
-
long elapsedNs = System.nanoTime() - startNs;
System.out.println(
String.format(
diff --git a/preinstrumented/src/test/java/org/robolectric/preinstrumented/JarInstrumentorTest.java b/preinstrumented/src/test/java/org/robolectric/preinstrumented/JarInstrumentorTest.java
index 6b6d169e1..3e401f37d 100644
--- a/preinstrumented/src/test/java/org/robolectric/preinstrumented/JarInstrumentorTest.java
+++ b/preinstrumented/src/test/java/org/robolectric/preinstrumented/JarInstrumentorTest.java
@@ -1,7 +1,6 @@
package org.robolectric.preinstrumented;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -23,8 +22,7 @@ public class JarInstrumentorTest {
JarInstrumentor dummyInstrumentor =
new JarInstrumentor() {
@Override
- protected void instrumentJar(
- File sourceJarFile, File destJarFile, File destNativesFile, boolean throwOnNatives) {
+ protected void instrumentJar(File sourceJarFile, File destJarFile) {
// No-op. We only want to test the command line processing. Stub the actual
// instrumention.
}
@@ -40,32 +38,13 @@ public class JarInstrumentorTest {
@Test
public void processCommandLine_legacyUsage() throws Exception {
spyDummyInstrumentor.processCommandLine(new String[] {"source.jar", "dest.jar"});
- verify(spyDummyInstrumentor)
- .instrumentJar(new File("source.jar"), new File("dest.jar"), null, false);
- }
-
- @Test
- public void processCommandLine_throwOnNatives() throws Exception {
- spyDummyInstrumentor.processCommandLine(
- new String[] {"source.jar", "dest.jar", "--throw-on-natives"});
- verify(spyDummyInstrumentor)
- .instrumentJar(new File("source.jar"), new File("dest.jar"), null, true);
- }
-
- @Test
- public void processCommandLine_writeNativesExemptionFile() throws Exception {
- spyDummyInstrumentor.processCommandLine(
- new String[] {"source.jar", "dest.jar", "--write-natives=natives.txt"});
- verify(spyDummyInstrumentor)
- .instrumentJar(
- new File("source.jar"), new File("dest.jar"), new File("natives.txt"), false);
+ verify(spyDummyInstrumentor).instrumentJar(new File("source.jar"), new File("dest.jar"));
}
@Test
public void processCommandLine_unknownArguments() throws Exception {
spyDummyInstrumentor.processCommandLine(new String[] {"source.jar", "dest.jar", "--some-flag"});
- verify(spyDummyInstrumentor, never())
- .instrumentJar(any(File.class), any(File.class), any(File.class), anyBoolean());
+ verify(spyDummyInstrumentor, never()).instrumentJar(any(File.class), any(File.class));
verify(spyDummyInstrumentor).exit(1);
}
}
diff --git a/processor/src/main/java/org/robolectric/annotation/processing/validator/ImplementsValidator.java b/processor/src/main/java/org/robolectric/annotation/processing/validator/ImplementsValidator.java
index e1186b9c4..ee4de7ae5 100644
--- a/processor/src/main/java/org/robolectric/annotation/processing/validator/ImplementsValidator.java
+++ b/processor/src/main/java/org/robolectric/annotation/processing/validator/ImplementsValidator.java
@@ -173,19 +173,23 @@ public class ImplementsValidator extends Validator {
AnnotationValue classNameAttr,
TypeElement shadowPickerTypeElement) {
- String sdkClassName;
+ String sdkClassNameFq;
if (valueAttr == null) {
- sdkClassName = Helpers.getAnnotationStringValue(classNameAttr).replace('$', '.');
+ sdkClassNameFq = Helpers.getAnnotationStringValue(classNameAttr);
} else {
- sdkClassName = Helpers.getAnnotationTypeMirrorValue(valueAttr).toString();
+ TypeMirror typeMirror = Helpers.getAnnotationTypeMirrorValue(valueAttr);
+ TypeElement typeElement = MoreElements.asType(types.asElement(typeMirror));
+ sdkClassNameFq = elements.getBinaryName(typeElement).toString();
}
// there's no such type at the current SDK level, so just use strings...
// getQualifiedName() uses Outer.Inner and we want Outer$Inner, so:
String name = getClassFQName(shadowType);
- modelBuilder.addExtraShadow(sdkClassName, name);
+ // SHADOW_MAP currently uses class dot syntax for keys, but SHADOW_PICKER_MAP uses
+ // FQ syntax for keys.
+ modelBuilder.addExtraShadow(sdkClassNameFq.replace('$', '.'), name);
if (shadowPickerTypeElement != null) {
- modelBuilder.addExtraShadowPicker(sdkClassName, shadowPickerTypeElement);
+ modelBuilder.addExtraShadowPicker(sdkClassNameFq, shadowPickerTypeElement);
}
}
diff --git a/processor/src/test/java/org/robolectric/annotation/processing/RobolectricProcessorTest.java b/processor/src/test/java/org/robolectric/annotation/processing/RobolectricProcessorTest.java
index 46f5bc4ea..4f5a57dc9 100644
--- a/processor/src/test/java/org/robolectric/annotation/processing/RobolectricProcessorTest.java
+++ b/processor/src/test/java/org/robolectric/annotation/processing/RobolectricProcessorTest.java
@@ -235,4 +235,22 @@ public class RobolectricProcessorTest {
.and()
.generatesSources(forResource("org/robolectric/Robolectric_MinimalPackages.java"));
}
+
+ @Test
+ public void shouldEmitShadowPickerMapForShadowedInnerClasses() {
+ Map<String, String> options = new HashMap<>(DEFAULT_OPTS);
+ options.put(SHOULD_INSTRUMENT_PKG_OPT, "true");
+
+ assertAbout(javaSources())
+ .that(
+ ImmutableList.of(
+ SHADOW_PROVIDER_SOURCE,
+ SHADOW_EXTRACTOR_SOURCE,
+ forResource(
+ "org/robolectric/annotation/processing/shadows/ShadowInnerDummyWithPicker.java")))
+ .processedWith(new RobolectricProcessor(options))
+ .compilesWithoutError()
+ .and()
+ .generatesSources(forResource("org/robolectric/Robolectric_ShadowPickers.java"));
+ }
}
diff --git a/processor/src/test/resources/mock-source/org/robolectric/internal/ShadowProvider.java b/processor/src/test/resources/mock-source/org/robolectric/internal/ShadowProvider.java
index 22b858a46..1a4e0a39b 100644
--- a/processor/src/test/resources/mock-source/org/robolectric/internal/ShadowProvider.java
+++ b/processor/src/test/resources/mock-source/org/robolectric/internal/ShadowProvider.java
@@ -1,6 +1,7 @@
package org.robolectric.internal;
import java.util.Collection;
+import java.util.Collections;
import java.util.Map;
public interface ShadowProvider {
@@ -10,4 +11,8 @@ public interface ShadowProvider {
String[] getProvidedPackageNames();
Collection<Map.Entry<String, String>> getShadows();
+
+ default Map<String, String> getShadowPickerMap() {
+ return Collections.emptyMap();
+ }
}
diff --git a/processor/src/test/resources/org/robolectric/Robolectric_ShadowPickers.java b/processor/src/test/resources/org/robolectric/Robolectric_ShadowPickers.java
new file mode 100644
index 000000000..e08db1f34
--- /dev/null
+++ b/processor/src/test/resources/org/robolectric/Robolectric_ShadowPickers.java
@@ -0,0 +1,68 @@
+package org.robolectric;
+import com.example.objects.OuterDummy.InnerDummy;
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.Generated;
+import org.robolectric.annotation.processing.shadows.ShadowInnerDummyWithPicker;
+import org.robolectric.internal.ShadowProvider;
+import org.robolectric.shadow.api.Shadow;
+
+/** Shadow mapper. Automatically generated by the Robolectric Annotation Processor. */
+@Generated("org.robolectric.annotation.processing.RobolectricProcessor")
+@SuppressWarnings({"unchecked", "deprecation"})
+public class Shadows implements ShadowProvider {
+ private static final List<Map.Entry<String, String>> SHADOWS = new ArrayList<>(3);
+
+ static {
+ SHADOWS.add(
+ new AbstractMap.SimpleImmutableEntry<>(
+ "com.example.objects.OuterDummy.InnerDummy",
+ "org.robolectric.annotation.processing.shadows.ShadowInnerDummyWithPicker$ShadowInnerDummyWithPicker2"));
+ SHADOWS.add(
+ new AbstractMap.SimpleImmutableEntry<>(
+ "com.example.objects.OuterDummy.InnerDummy2",
+ "org.robolectric.annotation.processing.shadows.ShadowInnerDummyWithPicker$ShadowInnerDummyWithPicker3"));
+ }
+
+ public static org.robolectric.annotation.processing.shadows.ShadowInnerDummyWithPicker shadowOf(
+ InnerDummy actual) {
+ return (org.robolectric.annotation.processing.shadows.ShadowInnerDummyWithPicker)
+ Shadow.extract(actual);
+ }
+
+ @Override
+ public void reset() {}
+
+ @Override
+ public Collection<Map.Entry<String, String>> getShadows() {
+ return SHADOWS;
+ }
+
+ @Override
+ public String[] getProvidedPackageNames() {
+ return new String[] {"com.example.objects"};
+ }
+
+ private static final Map<String, String> SHADOW_PICKER_MAP = new HashMap<>(12);
+
+ static {
+ SHADOW_PICKER_MAP.put(
+ "com.example.objects.OuterDummy$InnerDummy",
+ "org.robolectric.annotation.processing.shadows.ShadowInnerDummyWithPicker$Picker");
+ SHADOW_PICKER_MAP.put(
+ "com.example.objects.OuterDummy$InnerDummy",
+ "org.robolectric.annotation.processing.shadows.ShadowInnerDummyWithPicker$Picker");
+ SHADOW_PICKER_MAP.put(
+ "com.example.objects.OuterDummy$InnerDummy2",
+ "org.robolectric.annotation.processing.shadows.ShadowInnerDummyWithPicker$Picker");
+ }
+
+ @Override
+ public Map<String, String> getShadowPickerMap() {
+ return SHADOW_PICKER_MAP;
+ }
+} \ No newline at end of file
diff --git a/processor/src/test/resources/org/robolectric/annotation/processing/shadows/ShadowInnerDummyWithPicker.java b/processor/src/test/resources/org/robolectric/annotation/processing/shadows/ShadowInnerDummyWithPicker.java
new file mode 100644
index 000000000..e9d3a53b6
--- /dev/null
+++ b/processor/src/test/resources/org/robolectric/annotation/processing/shadows/ShadowInnerDummyWithPicker.java
@@ -0,0 +1,26 @@
+package org.robolectric.annotation.processing.shadows;
+
+import com.example.objects.OuterDummy;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.processing.shadows.ShadowInnerDummyWithPicker.Picker;
+import org.robolectric.shadow.api.ShadowPicker;
+
+@Implements(value = OuterDummy.InnerDummy.class, shadowPicker = Picker.class)
+public class ShadowInnerDummyWithPicker {
+
+ @Implements(value = OuterDummy.InnerDummy.class, maxSdk = 21, shadowPicker = Picker.class)
+ public static class ShadowInnerDummyWithPicker2 extends ShadowInnerDummyWithPicker {}
+
+ @Implements(
+ className = "com.example.objects.OuterDummy$InnerDummy2",
+ maxSdk = 21,
+ shadowPicker = Picker.class)
+ public static class ShadowInnerDummyWithPicker3 extends ShadowInnerDummyWithPicker {}
+
+ public static class Picker implements ShadowPicker<ShadowInnerDummyWithPicker> {
+ @Override
+ public Class<? extends ShadowInnerDummyWithPicker> pickShadowClass() {
+ return ShadowInnerDummyWithPicker.class;
+ }
+ }
+}
diff --git a/resources/src/main/java/org/robolectric/RoboSettings.java b/resources/src/main/java/org/robolectric/RoboSettings.java
index a76d90d2c..80dbe21bb 100644
--- a/resources/src/main/java/org/robolectric/RoboSettings.java
+++ b/resources/src/main/java/org/robolectric/RoboSettings.java
@@ -14,10 +14,18 @@ public class RoboSettings {
useGlobalScheduler = Boolean.getBoolean("robolectric.scheduling.global");
}
+ /**
+ * @deprecated Use PAUSED looper mode.
+ */
+ @Deprecated
public static boolean isUseGlobalScheduler() {
return useGlobalScheduler;
}
+ /**
+ * @deprecated Use PAUSED looper mode.
+ */
+ @Deprecated
public static void setUseGlobalScheduler(boolean useGlobalScheduler) {
RoboSettings.useGlobalScheduler = useGlobalScheduler;
}
diff --git a/resources/src/main/java/org/robolectric/manifest/AndroidManifest.java b/resources/src/main/java/org/robolectric/manifest/AndroidManifest.java
index 00a11f5ff..d5c0850a7 100644
--- a/resources/src/main/java/org/robolectric/manifest/AndroidManifest.java
+++ b/resources/src/main/java/org/robolectric/manifest/AndroidManifest.java
@@ -620,7 +620,7 @@ public class AndroidManifest implements UsesSdk {
@Override
public int getMinSdkVersion() {
parseAndroidManifest();
- return minSdkVersion == null ? 16 : minSdkVersion;
+ return minSdkVersion == null ? 19 : minSdkVersion;
}
/**
diff --git a/resources/src/main/java/org/robolectric/res/android/Asset.java b/resources/src/main/java/org/robolectric/res/android/Asset.java
index a659a6cd9..85e3629a0 100644
--- a/resources/src/main/java/org/robolectric/res/android/Asset.java
+++ b/resources/src/main/java/org/robolectric/res/android/Asset.java
@@ -555,14 +555,13 @@ static Asset createFromCompressedMap(FileMap dataMap,
default:
ALOGW("unexpected whence %d\n", whence);
// this was happening due to an long size mismatch
- assert(false);
- return (long) -1;
+ assert false;
+ return -1;
}
if (newOffset < 0 || newOffset > maxPosn) {
- ALOGW("seek out of range: want %d, end=%d\n",
- (long) newOffset, (long) maxPosn);
- return (long) -1;
+ ALOGW("seek out of range: want %d, end=%d\n", newOffset, maxPosn);
+ return -1;
}
return newOffset;
@@ -786,7 +785,7 @@ static Asset createFromCompressedMap(FileMap dataMap,
if (mFp.getFilePointer() != mStart + mOffset) {
ALOGE("Hosed: %d != %d+%d\n",
mFp.getFilePointer(), (long) mStart, (long) mOffset);
- assert(false);
+ assert false;
}
/*
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 656661d03..58d436b69 100644
--- a/resources/src/main/java/org/robolectric/res/android/CppApkAssets.java
+++ b/resources/src/main/java/org/robolectric/res/android/CppApkAssets.java
@@ -9,6 +9,10 @@ import static org.robolectric.res.android.Util.CHECK;
import static org.robolectric.res.android.ZipFileRO.OpenArchive;
import static org.robolectric.res.android.ZipFileRO.kCompressDeflated;
+import com.google.common.io.ByteStreams;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Enumeration;
@@ -41,17 +45,21 @@ import org.robolectric.util.PerfStatsCollector;
@SuppressWarnings("NewApi")
public class CppApkAssets {
private static final String kResourcesArsc = "resources.arsc";
-// public:
-// static std::unique_ptr<const ApkAssets> Load(const String& path, bool system = false);
-// static std::unique_ptr<const ApkAssets> LoadAsSharedLibrary(const String& path,
-// bool system = false);
-//
-// std::unique_ptr<Asset> Open(const String& path,
-// Asset::AccessMode mode = Asset::AccessMode::ACCESS_RANDOM) const;
-//
-// bool ForEachFile(const String& path,
-// const std::function<void(const StringPiece&, FileType)>& f) const;
+ // public:
+ // static std::unique_ptr<const ApkAssets> Load(const String& path, bool system = false);
+ // static std::unique_ptr<const ApkAssets> LoadAsSharedLibrary(const String& path,
+ // bool system = false);
+ //
+ // std::unique_ptr<Asset> Open(const String& path,
+ // Asset::AccessMode mode = Asset::AccessMode::ACCESS_RANDOM) const;
+ //
+ // bool ForEachFile(const String& path,
+ // const std::function<void(const StringPiece&, FileType)>& f) const;
+
+ private CppApkAssets() {
+ this.zipFileRO = null;
+ }
public CppApkAssets(ZipArchiveHandle zip_handle_, String path_) {
this.zip_handle_ = zip_handle_;
@@ -169,6 +177,23 @@ public class CppApkAssets {
// system, force_shared_lib);
// }
+ // Creates an ApkAssets of the format ARSC from the given file descriptor, and takes ownership of
+ // the file descriptor.
+ public static CppApkAssets loadArscFromFd(FileDescriptor fd) {
+ CppApkAssets loadedApk = new CppApkAssets();
+ try {
+ byte[] bytes = ByteStreams.toByteArray(new FileInputStream(fd));
+
+ StringPiece data = new StringPiece(ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN), 0);
+ loadedApk.loaded_arsc_ = LoadedArsc.Load(data, null, false, false);
+
+ } catch (IOException e) {
+ // logError("Error loading assets from fd: " + e.getLocalizedMessage());
+ return null;
+ }
+ return loadedApk;
+ }
+
// std::unique_ptr<Asset> ApkAssets::CreateAssetFromFile(const std::string& path) {
@SuppressWarnings("DoNotCallSuggester")
static Asset CreateAssetFromFile(String path) {
@@ -275,7 +300,10 @@ public class CppApkAssets {
}
public Asset Open(String path, AccessMode mode) {
- 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 null;
+ }
String name = path;
ZipEntryRO entry;
diff --git a/resources/src/main/java/org/robolectric/res/android/CppAssetManager.java b/resources/src/main/java/org/robolectric/res/android/CppAssetManager.java
index b796f230d..4f95df813 100644
--- a/resources/src/main/java/org/robolectric/res/android/CppAssetManager.java
+++ b/resources/src/main/java/org/robolectric/res/android/CppAssetManager.java
@@ -1431,8 +1431,8 @@ public class CppAssetManager {
pNewSorted.add(pMergedInfo.itemAt(mergeIdx));
mergeIdx++;
} else {
- /* "cont" is lower, add that one */
- assert (pContents.itemAt(contIdx).isLessThan(pMergedInfo.itemAt(mergeIdx)));
+ /* "cont" is lower, add that one */
+ assert pContents.itemAt(contIdx).isLessThan(pMergedInfo.itemAt(mergeIdx));
pNewSorted.add(pContents.itemAt(contIdx));
contIdx++;
}
diff --git a/resources/src/main/java/org/robolectric/res/android/CppAssetManager2.java b/resources/src/main/java/org/robolectric/res/android/CppAssetManager2.java
index 4bb34f41b..803f82363 100644
--- a/resources/src/main/java/org/robolectric/res/android/CppAssetManager2.java
+++ b/resources/src/main/java/org/robolectric/res/android/CppAssetManager2.java
@@ -39,6 +39,7 @@ import org.robolectric.res.android.ResourceTypes.ResTable_map;
import org.robolectric.res.android.ResourceTypes.ResTable_map_entry;
import org.robolectric.res.android.ResourceTypes.ResTable_type;
import org.robolectric.res.android.ResourceTypes.Res_value;
+import org.robolectric.util.PerfStatsCollector;
// transliterated from https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/include/androidfw/AssetManager2.h
// and https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/AssetManager2.cpp
@@ -667,7 +668,9 @@ public class CppAssetManager2 {
// and we don't need to match the configurations, since they already matched.
boolean use_fast_path = desired_config == configuration_;
- for (int pi = 0; pi < package_count; pi++) {
+ // Search the entry in reverse order. This favors the newly added package in case neither
+ // configuration is considered "better than" the other.
+ for (int pi = package_count - 1; pi >= 0; pi--) {
ConfiguredPackage loaded_package_impl = package_group.packages_.get(pi);
LoadedPackage loaded_package = loaded_package_impl.loaded_package_;
ApkAssetsCookie cookie = package_group.cookies_.get(pi);
@@ -1293,36 +1296,43 @@ public class CppAssetManager2 {
// Triggers the re-construction of lists of types that match the set configuration.
// This should always be called when mutating the AssetManager's configuration or ApkAssets set.
void RebuildFilterList() {
- for (PackageGroup group : package_groups_) {
- for (ConfiguredPackage impl : group.packages_) {
- // // Destroy it.
- // impl.filtered_configs_.~ByteBucketArray();
- //
- // // Re-create it.
- // new (impl.filtered_configs_) ByteBucketArray<FilteredConfigGroup>();
- impl.filtered_configs_ =
- new ByteBucketArray<FilteredConfigGroup>(new FilteredConfigGroup()) {
- @Override
- FilteredConfigGroup newInstance() {
- return new FilteredConfigGroup();
+ PerfStatsCollector.getInstance()
+ .measure(
+ "RebuildFilterList",
+ () -> {
+ for (PackageGroup group : package_groups_) {
+ for (ConfiguredPackage impl : group.packages_) {
+ // // Destroy it.
+ // impl.filtered_configs_.~ByteBucketArray();
+ //
+ // // Re-create it.
+ // new (impl.filtered_configs_) ByteBucketArray<FilteredConfigGroup>();
+ impl.filtered_configs_ =
+ new ByteBucketArray<FilteredConfigGroup>(new FilteredConfigGroup()) {
+ @Override
+ FilteredConfigGroup newInstance() {
+ return new FilteredConfigGroup();
+ }
+ };
+
+ // Create the filters here.
+ impl.loaded_package_.ForEachTypeSpec(
+ (TypeSpec spec, byte type_index) -> {
+ FilteredConfigGroup configGroup =
+ impl.filtered_configs_.editItemAt(type_index);
+ // const auto iter_end = spec->types + spec->type_count;
+ // for (auto iter = spec->types; iter != iter_end; ++iter) {
+ for (ResTable_type iter : spec.types) {
+ if (iter.config.match(configuration_)) {
+ ResTable_config this_config = ResTable_config.fromDtoH(iter.config);
+ configGroup.configurations.add(this_config);
+ configGroup.types.add(iter);
+ }
+ }
+ });
+ }
}
- };
-
- // Create the filters here.
- impl.loaded_package_.ForEachTypeSpec((TypeSpec spec, byte type_index) -> {
- FilteredConfigGroup configGroup = impl.filtered_configs_.editItemAt(type_index);
- // const auto iter_end = spec->types + spec->type_count;
- // for (auto iter = spec->types; iter != iter_end; ++iter) {
- for (ResTable_type iter : spec.types) {
- ResTable_config this_config = ResTable_config.fromDtoH(iter.config);
- if (this_config.match(configuration_)) {
- configGroup.configurations.add(this_config);
- configGroup.types.add(iter);
- }
- }
- });
- }
- }
+ });
}
// Purge all resources that are cached and vary by the configuration axis denoted by the
diff --git a/resources/src/main/java/org/robolectric/res/android/LocaleData.java b/resources/src/main/java/org/robolectric/res/android/LocaleData.java
index 0fe00f5da..359b6434a 100644
--- a/resources/src/main/java/org/robolectric/res/android/LocaleData.java
+++ b/resources/src/main/java/org/robolectric/res/android/LocaleData.java
@@ -103,7 +103,7 @@ public class LocaleData {
(((long) script.charAt(1) & 0xff) << 16) |
(((long) script.charAt(2) & 0xff) << 8) |
((long) script.charAt(3) & 0xff));
- return (REPRESENTATIVE_LOCALES.contains(packed_locale));
+ return REPRESENTATIVE_LOCALES.contains(packed_locale);
}
private static final int US_SPANISH = 0x65735553; // es-US
diff --git a/resources/src/main/java/org/robolectric/res/android/ResTable_config.java b/resources/src/main/java/org/robolectric/res/android/ResTable_config.java
index ba164bd1e..3c4a8402d 100644
--- a/resources/src/main/java/org/robolectric/res/android/ResTable_config.java
+++ b/resources/src/main/java/org/robolectric/res/android/ResTable_config.java
@@ -1210,11 +1210,11 @@ public class ResTable_config {
if (isTruthy(requested)) {
if (isTruthy(imsi()) || isTruthy(o.imsi())) {
if ((mcc != o.mcc) && isTruthy(requested.mcc)) {
- return (isTruthy(mcc));
+ return isTruthy(mcc);
}
if ((mnc != o.mnc) && isTruthy(requested.mnc)) {
- return (isTruthy(mnc));
+ return isTruthy(mnc);
}
}
diff --git a/resources/src/main/java/org/robolectric/res/android/ZipFileRO.java b/resources/src/main/java/org/robolectric/res/android/ZipFileRO.java
index c1d21e070..8f884e5fd 100644
--- a/resources/src/main/java/org/robolectric/res/android/ZipFileRO.java
+++ b/resources/src/main/java/org/robolectric/res/android/ZipFileRO.java
@@ -145,7 +145,7 @@ public class ZipFileRO {
final Ref<Long> pUncompLen, Ref<Long> pCompLen, Ref<Long> pOffset,
final Ref<Long> pModWhen, Ref<Long> pCrc32)
{
- final ZipEntryRO zipEntry = /*reinterpret_cast<ZipEntryRO*>*/(entry);
+ final ZipEntryRO zipEntry = /*reinterpret_cast<ZipEntryRO*>*/ entry;
final ZipEntry ze = zipEntry.entry;
if (pMethod != null) {
diff --git a/robolectric/Android.bp b/robolectric/Android.bp
index b6720e96f..954a9e99f 100644
--- a/robolectric/Android.bp
+++ b/robolectric/Android.bp
@@ -17,6 +17,7 @@ java_library_host {
"Robolectric_shadows_framework_upstream",
"Robolectric_shadows_versioning_upstream",
"Robolectric_annotations_upstream",
+ "Robolectric_nativeruntime_upstream",
"Robolectric_shadowapi_upstream",
"Robolectric_resources_upstream",
"Robolectric_sandbox_upstream",
@@ -86,8 +87,7 @@ java_test_host {
"asm-tree-9.2",
"junit",
"icu4j",
- "truth",
- "truth-java8-extension",
+ "truth-1.4.0-prebuilt",
"robolectric-ant-1.8.0",
"asm-9.2",
"jsr305",
diff --git a/robolectric/build.gradle b/robolectric/build.gradle
index b826e9232..859504e6d 100644
--- a/robolectric/build.gradle
+++ b/robolectric/build.gradle
@@ -30,10 +30,12 @@ dependencies {
compileOnly AndroidSdk.MAX_SDK.coordinates
compileOnly libs.junit4
+ compileOnly libs.androidx.annotation
api "androidx.test:monitor:$axtMonitorVersion@aar"
implementation "androidx.test.espresso:espresso-idling-resource:$espressoVersion@aar"
+ testImplementation libs.androidx.annotation
testImplementation libs.junit4
testImplementation libs.truth
testImplementation libs.truth.java8.extension
@@ -46,40 +48,4 @@ dependencies {
testImplementation libs.guava
testCompileOnly AndroidSdk.MAX_SDK.coordinates // compile against latest Android SDK
testRuntimeOnly AndroidSdk.MAX_SDK.coordinates // run against whatever this JDK supports
-}
-
-project.apply plugin: CheckApiChangesPlugin
-
-checkApiChanges {
- from = [
- "org.robolectric:robolectric:${apiCompatVersion}@jar",
- "org.robolectric:annotations:${apiCompatVersion}@jar",
- "org.robolectric:junit:${apiCompatVersion}@jar",
- "org.robolectric:resources:${apiCompatVersion}@jar",
- "org.robolectric:sandbox:${apiCompatVersion}@jar",
- "org.robolectric:utils:${apiCompatVersion}@jar",
- "org.robolectric:shadowapi:${apiCompatVersion}@jar",
- "org.robolectric:shadows-framework:${apiCompatVersion}@jar",
- ]
-
- to = [
- project(":robolectric"),
- project(":annotations"),
- project(":junit"),
- project(":resources"),
- project(":sandbox"),
- project(":shadows:framework"),
- project(":utils"),
- project(":shadowapi"),
- ]
-
- entryPoints += "org.robolectric.RobolectricTestRunner"
- expectedChanges = [
- "^org.robolectric.util.ActivityController#",
- "^org.robolectric.util.ComponentController#",
- "^org.robolectric.util.ContentProviderController#",
- "^org.robolectric.util.FragmentController#",
- "^org.robolectric.util.IntentServiceController#",
- "^org.robolectric.util.ServiceController#",
- ]
-}
+} \ No newline at end of file
diff --git a/robolectric/src/main/java/org/robolectric/Robolectric.java b/robolectric/src/main/java/org/robolectric/Robolectric.java
index 3ce637e76..7b694ec82 100644
--- a/robolectric/src/main/java/org/robolectric/Robolectric.java
+++ b/robolectric/src/main/java/org/robolectric/Robolectric.java
@@ -1,15 +1,21 @@
package org.robolectric;
+import static android.os.Build.VERSION_CODES.P;
import static com.google.common.base.Preconditions.checkState;
import static org.robolectric.shadows.ShadowAssetManager.useLegacy;
import android.annotation.IdRes;
+import android.annotation.RequiresApi;
import android.app.Activity;
+import android.app.ActivityThread;
+import android.app.AppComponentFactory;
import android.app.Fragment;
import android.app.IntentService;
+import android.app.LoadedApk;
import android.app.Service;
import android.app.backup.BackupAgent;
import android.content.ContentProvider;
+import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Looper;
@@ -26,6 +32,7 @@ import org.robolectric.android.controller.FragmentController;
import org.robolectric.android.controller.IntentServiceController;
import org.robolectric.android.controller.ServiceController;
import org.robolectric.shadows.ShadowApplication;
+import org.robolectric.util.Logger;
import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.Scheduler;
@@ -36,7 +43,7 @@ public class Robolectric {
}
public static <T extends Service> ServiceController<T> buildService(Class<T> serviceClass, Intent intent) {
- return ServiceController.of(ReflectionHelpers.callConstructor(serviceClass), intent);
+ return ServiceController.of(instantiateService(serviceClass, intent), intent);
}
public static <T extends Service> T setupService(Class<T> serviceClass) {
@@ -48,7 +55,7 @@ public class Robolectric {
}
public static <T extends IntentService> IntentServiceController<T> buildIntentService(Class<T> serviceClass, Intent intent) {
- return IntentServiceController.of(ReflectionHelpers.callConstructor(serviceClass), intent);
+ return IntentServiceController.of(instantiateService(serviceClass, intent), intent);
}
public static <T extends IntentService> T setupIntentService(Class<T> serviceClass) {
@@ -56,7 +63,7 @@ public class Robolectric {
}
public static <T extends ContentProvider> ContentProviderController<T> buildContentProvider(Class<T> contentProviderClass) {
- return ContentProviderController.of(ReflectionHelpers.callConstructor(contentProviderClass));
+ return ContentProviderController.of(instantiateContentProvider(contentProviderClass));
}
public static <T extends ContentProvider> T setupContentProvider(Class<T> contentProviderClass) {
@@ -110,7 +117,7 @@ public class Robolectric {
Thread.currentThread() == Looper.getMainLooper().getThread(),
"buildActivity must be called on main Looper thread");
return ActivityController.of(
- ReflectionHelpers.callConstructor(activityClass), intent, activityOptions);
+ instantiateActivity(activityClass, intent), intent, activityOptions);
}
/**
@@ -378,4 +385,84 @@ public class Robolectric {
public static void flushBackgroundThreadScheduler() {
getBackgroundThreadScheduler().advanceToLastPostedRunnable();
}
+
+ @SuppressWarnings({"NewApi", "unchecked"})
+ private static <T extends Service> T instantiateService(Class<T> serviceClass, Intent intent) {
+ if (RuntimeEnvironment.getApiLevel() >= P) {
+ final LoadedApk loadedApk = getLoadedApk();
+ AppComponentFactory factory = getAppComponentFactory(loadedApk);
+ if (factory != null) {
+ try {
+ Service instance =
+ factory.instantiateService(
+ loadedApk.getClassLoader(), serviceClass.getName(), intent);
+ if (instance != null && serviceClass.isAssignableFrom(instance.getClass())) {
+ return (T) instance;
+ }
+ } catch (ReflectiveOperationException e) {
+ Logger.debug("Failed to instantiate Service using AppComponentFactory", e);
+ }
+ }
+ }
+ return ReflectionHelpers.callConstructor(serviceClass);
+ }
+
+ @SuppressWarnings({"NewApi", "unchecked"})
+ private static <T extends ContentProvider> T instantiateContentProvider(Class<T> providerClass) {
+ if (RuntimeEnvironment.getApiLevel() >= P) {
+ final LoadedApk loadedApk = getLoadedApk();
+ AppComponentFactory factory = getAppComponentFactory(loadedApk);
+ if (factory != null) {
+ try {
+ ContentProvider instance =
+ factory.instantiateProvider(loadedApk.getClassLoader(), providerClass.getName());
+ if (instance != null && providerClass.isAssignableFrom(instance.getClass())) {
+ return (T) instance;
+ }
+ } catch (ReflectiveOperationException e) {
+ Logger.debug("Failed to instantiate ContentProvider using AppComponentFactory", e);
+ }
+ }
+ }
+ return ReflectionHelpers.callConstructor(providerClass);
+ }
+
+ @SuppressWarnings({"NewApi", "unchecked"})
+ private static <T extends Activity> T instantiateActivity(Class<T> activityClass, Intent intent) {
+ if (RuntimeEnvironment.getApiLevel() >= P) {
+ final LoadedApk loadedApk = getLoadedApk();
+ AppComponentFactory factory = getAppComponentFactory(loadedApk);
+ if (factory != null) {
+ try {
+ Activity instance =
+ factory.instantiateActivity(
+ loadedApk.getClassLoader(), activityClass.getName(), intent);
+ if (instance != null && activityClass.isAssignableFrom(instance.getClass())) {
+ return (T) instance;
+ }
+ } catch (ReflectiveOperationException e) {
+ Logger.debug("Failed to instantiate Activity using AppComponentFactory", e);
+ }
+ }
+ }
+ return ReflectionHelpers.callConstructor(activityClass);
+ }
+
+ @Nullable
+ @RequiresApi(api = P)
+ private static AppComponentFactory getAppComponentFactory(final LoadedApk loadedApk) {
+ if (RuntimeEnvironment.getApiLevel() < P) {
+ return null;
+ }
+ if (loadedApk == null || loadedApk.getApplicationInfo().appComponentFactory == null) {
+ return null;
+ }
+ return loadedApk.getAppFactory();
+ }
+
+ private static LoadedApk getLoadedApk() {
+ final ActivityThread activityThread = (ActivityThread) RuntimeEnvironment.getActivityThread();
+ return activityThread.getPackageInfo(
+ activityThread.getApplication().getApplicationInfo(), null, Context.CONTEXT_INCLUDE_CODE);
+ }
}
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 8776bc660..976edc52c 100644
--- a/robolectric/src/main/java/org/robolectric/android/internal/AndroidTestEnvironment.java
+++ b/robolectric/src/main/java/org/robolectric/android/internal/AndroidTestEnvironment.java
@@ -39,20 +39,29 @@ import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.Security;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
import java.util.Locale;
import java.util.Objects;
import javax.annotation.Nonnull;
import javax.inject.Named;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSession;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.conscrypt.OkHostnameVerifier;
import org.conscrypt.OpenSSLProvider;
import org.robolectric.ApkLoader;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.android.Bootstrap;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.ConscryptMode;
-import org.robolectric.annotation.LooperMode;
import org.robolectric.annotation.GraphicsMode;
import org.robolectric.annotation.GraphicsMode.Mode;
+import org.robolectric.annotation.LooperMode;
+import org.robolectric.annotation.SQLiteMode;
import org.robolectric.annotation.experimental.LazyApplication.LazyLoad;
import org.robolectric.config.ConfigurationRegistry;
import org.robolectric.internal.ResourcesMode;
@@ -61,6 +70,7 @@ import org.robolectric.internal.TestEnvironment;
import org.robolectric.manifest.AndroidManifest;
import org.robolectric.manifest.BroadcastReceiverData;
import org.robolectric.manifest.RoboNotFoundException;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.pluginapi.Sdk;
import org.robolectric.pluginapi.TestEnvironmentLifecyclePlugin;
import org.robolectric.pluginapi.config.ConfigurationStrategy.Configuration;
@@ -147,6 +157,13 @@ public class AndroidTestEnvironment implements TestEnvironment {
}
clearEnvironment();
+
+ // Starting in Android V and above, the native runtime does not support begin lazy-loaded, it
+ // must be loaded upfront.
+ if (shouldLoadNativeRuntime() && RuntimeEnvironment.getApiLevel() >= AndroidVersions.V.SDK_INT) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ }
+
RuntimeEnvironment.setTempDirectory(new TempDirectory(createTestDataDirRootPath(method)));
if (ShadowLooper.looperMode() == LooperMode.Mode.LEGACY) {
RuntimeEnvironment.setMasterScheduler(new Scheduler());
@@ -169,6 +186,23 @@ public class AndroidTestEnvironment implements TestEnvironment {
if (Security.getProvider(CONSCRYPT_PROVIDER) == null) {
Security.insertProviderAt(new OpenSSLProvider(), 1);
}
+
+ HttpsURLConnection.setDefaultHostnameVerifier(
+ new HostnameVerifier() {
+ private final OkHostnameVerifier conscryptVerifier = OkHostnameVerifier.INSTANCE;
+
+ @Override
+ public boolean verify(String hostname, SSLSession session) {
+ try {
+ Certificate[] certificates = session.getPeerCertificates();
+ X509Certificate[] x509Certificates =
+ Arrays.copyOf(certificates, certificates.length, X509Certificate[].class);
+ return conscryptVerifier.verify(x509Certificates, hostname, session);
+ } catch (SSLException e) {
+ return false;
+ }
+ }
+ });
}
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
@@ -209,8 +243,7 @@ public class AndroidTestEnvironment implements TestEnvironment {
Instrumentation instrumentation = createInstrumentation();
InstrumentationRegistry.registerInstance(instrumentation, new Bundle());
- Supplier<Application> applicationSupplier =
- createApplicationSupplier(appManifest, config, androidConfiguration, displayMetrics);
+ Supplier<Application> applicationSupplier = createApplicationSupplier(appManifest, config);
RuntimeEnvironment.setApplicationSupplier(applicationSupplier);
if (configuration.get(LazyLoad.class) == LazyLoad.ON) {
@@ -220,6 +253,7 @@ public class AndroidTestEnvironment implements TestEnvironment {
// force eager load of the application
RuntimeEnvironment.getApplication();
}
+
}
// If certain Android classes are required to be loaded in a particular order, do so here.
@@ -239,10 +273,7 @@ public class AndroidTestEnvironment implements TestEnvironment {
// TODO Move synchronization logic into its own class for better readability
private Supplier<Application> createApplicationSupplier(
- AndroidManifest appManifest,
- Config config,
- android.content.res.Configuration androidConfiguration,
- DisplayMetrics displayMetrics) {
+ AndroidManifest appManifest, Config config) {
final ActivityThread activityThread = (ActivityThread) RuntimeEnvironment.getActivityThread();
final _ActivityThread_ _activityThread_ = reflector(_ActivityThread_.class, activityThread);
final ShadowActivityThread shadowActivityThread = Shadow.extract(activityThread);
@@ -256,8 +287,6 @@ public class AndroidTestEnvironment implements TestEnvironment {
installAndCreateApplication(
appManifest,
config,
- androidConfiguration,
- displayMetrics,
shadowActivityThread,
_activityThread_,
activityThread.getInstrumentation())));
@@ -266,8 +295,6 @@ public class AndroidTestEnvironment implements TestEnvironment {
private Application installAndCreateApplication(
AndroidManifest appManifest,
Config config,
- android.content.res.Configuration androidConfiguration,
- DisplayMetrics displayMetrics,
ShadowActivityThread shadowActivityThread,
_ActivityThread_ activityThreadReflector,
Instrumentation androidInstrumentation) {
@@ -309,6 +336,9 @@ public class AndroidTestEnvironment implements TestEnvironment {
// code in there that can be reusable, e.g: the XxxxIntentResolver code.
ShadowActivityThread.setApplicationInfo(applicationInfo);
+ // Bootstrap.getConfiguration gets any potential updates to configuration via
+ // RuntimeEnvironment.setQualifiers.
+ android.content.res.Configuration androidConfiguration = Bootstrap.getConfiguration();
shadowActivityThread.setCompatConfiguration(androidConfiguration);
Bootstrap.setUpDisplay();
@@ -368,9 +398,7 @@ public class AndroidTestEnvironment implements TestEnvironment {
}
registerBroadcastReceivers(application, appManifest, loadedApk);
- appResources.updateConfiguration(androidConfiguration, displayMetrics);
- // propagate any updates to configuration via RuntimeEnvironment.setQualifiers
- Bootstrap.updateConfiguration(appResources);
+ appResources.updateConfiguration(androidConfiguration, Bootstrap.getDisplayMetrics());
if (ShadowAssetManager.useLegacy()) {
populateAssetPaths(appResources.getAssets(), appManifest);
@@ -585,25 +613,14 @@ public class AndroidTestEnvironment implements TestEnvironment {
Application dummyInitialApplication = new Application();
final ComponentName dummyInitialComponent =
new ComponentName("", androidInstrumentation.getClass().getSimpleName());
- // TODO Move the API check into a helper method inside ShadowInstrumentation
- if (RuntimeEnvironment.getApiLevel() <= VERSION_CODES.JELLY_BEAN_MR1) {
- reflector(_Instrumentation_.class, androidInstrumentation)
- .init(
- activityThread,
- dummyInitialApplication,
- dummyInitialApplication,
- dummyInitialComponent,
- null);
- } else {
- reflector(_Instrumentation_.class, androidInstrumentation)
- .init(
- activityThread,
- dummyInitialApplication,
- dummyInitialApplication,
- dummyInitialComponent,
- null,
- null);
- }
+ reflector(_Instrumentation_.class, androidInstrumentation)
+ .init(
+ activityThread,
+ dummyInitialApplication,
+ dummyInitialApplication,
+ dummyInitialComponent,
+ null,
+ null);
});
androidInstrumentation.onCreate(new Bundle());
@@ -697,7 +714,7 @@ public class AndroidTestEnvironment implements TestEnvironment {
applicationInfo.publicSourceDir =
createTempDir(applicationInfo.packageName + "-publicSourceDir");
} else {
- if (apiLevel <= VERSION_CODES.KITKAT) {
+ if (apiLevel == VERSION_CODES.KITKAT) {
String sourcePath = reflector(_Package_.class, parsedPackage).getPath();
if (sourcePath == null) {
sourcePath = createTempDir("sourceDir");
@@ -742,6 +759,12 @@ public class AndroidTestEnvironment implements TestEnvironment {
return null;
}
+ private static boolean shouldLoadNativeRuntime() {
+ GraphicsMode.Mode graphicsMode = ConfigurationRegistry.get(GraphicsMode.Mode.class);
+ SQLiteMode.Mode sqliteMode = ConfigurationRegistry.get(SQLiteMode.Mode.class);
+ return graphicsMode == GraphicsMode.Mode.NATIVE || sqliteMode == SQLiteMode.Mode.NATIVE;
+ }
+
// TODO move/replace this with packageManager
@VisibleForTesting
static void registerBroadcastReceivers(
diff --git a/robolectric/src/main/java/org/robolectric/android/internal/RoboMonitoringInstrumentation.java b/robolectric/src/main/java/org/robolectric/android/internal/RoboMonitoringInstrumentation.java
index bfd3104af..a9665b706 100644
--- a/robolectric/src/main/java/org/robolectric/android/internal/RoboMonitoringInstrumentation.java
+++ b/robolectric/src/main/java/org/robolectric/android/internal/RoboMonitoringInstrumentation.java
@@ -35,6 +35,7 @@ import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Nullable;
import org.robolectric.Robolectric;
@@ -62,6 +63,8 @@ public class RoboMonitoringInstrumentation extends Instrumentation {
private final IntentMonitorImpl intentMonitor = new IntentMonitorImpl();
private final List<ActivityController<?>> createdActivities = new ArrayList<>();
+ private final AtomicBoolean attachedConfigListener = new AtomicBoolean();
+
/**
* Sets up lifecycle monitoring, and argument registry.
*
@@ -74,13 +77,6 @@ public class RoboMonitoringInstrumentation extends Instrumentation {
ActivityLifecycleMonitorRegistry.registerInstance(lifecycleMonitor);
ApplicationLifecycleMonitorRegistry.registerInstance(applicationMonitor);
IntentMonitorRegistry.registerInstance(intentMonitor);
- if (!Boolean.getBoolean("robolectric.createActivityContexts")) {
- // To avoid infinite recursion listen to the system resources, this will be updated before
- // the application resources but because activities use the application resources they will
- // get updated by the first activity (via updateConfiguration).
- shadowOf(Resources.getSystem()).addConfigurationChangeListener(this::updateConfiguration);
- }
-
super.onCreate(arguments);
}
@@ -115,6 +111,15 @@ public class RoboMonitoringInstrumentation extends Instrumentation {
} catch (ClassNotFoundException e) {
throw new RuntimeException("Could not load activity " + ai.name, e);
}
+
+ if (attachedConfigListener.compareAndSet(false, true)
+ && !Boolean.getBoolean("robolectric.createActivityContexts")) {
+ // To avoid infinite recursion listen to the system resources, this will be updated before
+ // the application resources but because activities use the application resources they will
+ // get updated by the first activity (via updateConfiguration).
+ shadowOf(Resources.getSystem()).addConfigurationChangeListener(this::updateConfiguration);
+ }
+
AtomicReference<ActivityController<? extends Activity>> activityControllerReference =
new AtomicReference<>();
ShadowInstrumentation.runOnMainSyncNoIdle(
diff --git a/robolectric/src/main/java/org/robolectric/internal/BuckManifestFactory.java b/robolectric/src/main/java/org/robolectric/internal/BuckManifestFactory.java
index 0178ac8ea..9efb53c51 100644
--- a/robolectric/src/main/java/org/robolectric/internal/BuckManifestFactory.java
+++ b/robolectric/src/main/java/org/robolectric/internal/BuckManifestFactory.java
@@ -20,7 +20,8 @@ import org.robolectric.util.Util;
public class BuckManifestFactory implements ManifestFactory {
private static final String BUCK_ROBOLECTRIC_RES_DIRECTORIES = "buck.robolectric_res_directories";
- private static final String BUCK_ROBOLECTRIC_ASSETS_DIRECTORIES = "buck.robolectric_assets_directories";
+ private static final String BUCK_ROBOLECTRIC_ASSETS_DIRECTORIES =
+ "buck.robolectric_assets_directories";
private static final String BUCK_ROBOLECTRIC_MANIFEST = "buck.robolectric_manifest";
@Override
@@ -83,6 +84,6 @@ public class BuckManifestFactory implements ManifestFactory {
for (String dir : dirs) {
files.add(Fs.fromUrl(dir));
}
- return files;
+ return Collections.unmodifiableList(files);
}
}
diff --git a/robolectric/src/main/java/org/robolectric/plugins/DefaultSdkProvider.java b/robolectric/src/main/java/org/robolectric/plugins/DefaultSdkProvider.java
index f4cf49f26..3b6f76da9 100644
--- a/robolectric/src/main/java/org/robolectric/plugins/DefaultSdkProvider.java
+++ b/robolectric/src/main/java/org/robolectric/plugins/DefaultSdkProvider.java
@@ -46,7 +46,7 @@ public class DefaultSdkProvider implements SdkProvider {
private static final int RUNNING_JAVA_VERSION = Util.getJavaVersion();
- private static final int PREINSTRUMENTED_VERSION = 4;
+ private static final int PREINSTRUMENTED_VERSION = 5;
private final DependencyResolver dependencyResolver;
diff --git a/robolectric/src/test/java/org/robolectric/CustomAppComponentFactory.java b/robolectric/src/test/java/org/robolectric/CustomAppComponentFactory.java
index 22df750a9..0cf46eae2 100644
--- a/robolectric/src/test/java/org/robolectric/CustomAppComponentFactory.java
+++ b/robolectric/src/test/java/org/robolectric/CustomAppComponentFactory.java
@@ -1,10 +1,16 @@
package org.robolectric;
+import android.app.Activity;
import android.app.AppComponentFactory;
+import android.app.Service;
import android.content.BroadcastReceiver;
+import android.content.ContentProvider;
import android.content.Intent;
import org.robolectric.CustomConstructorReceiverWrapper.CustomConstructorWithEmptyActionReceiver;
import org.robolectric.CustomConstructorReceiverWrapper.CustomConstructorWithOneActionReceiver;
+import org.robolectric.CustomConstructorServices.CustomConstructorIntentService;
+import org.robolectric.CustomConstructorServices.CustomConstructorJobService;
+import org.robolectric.CustomConstructorServices.CustomConstructorService;
public final class CustomAppComponentFactory extends AppComponentFactory {
@Override
@@ -19,4 +25,41 @@ public final class CustomAppComponentFactory extends AppComponentFactory {
}
return super.instantiateReceiver(cl, className, intent);
}
+
+ @Override
+ public Service instantiateService(ClassLoader cl, String className, Intent intent)
+ throws InstantiationException, IllegalAccessException, ClassNotFoundException {
+ if (className != null) {
+ if (className.contains(CustomConstructorService.class.getName())) {
+ return new CustomConstructorService(100);
+ } else if (className.contains(CustomConstructorIntentService.class.getName())) {
+ return new CustomConstructorIntentService(100);
+ } else if (className.contains(CustomConstructorJobService.class.getName())) {
+ return new CustomConstructorJobService(100);
+ }
+ }
+ return super.instantiateService(cl, className, intent);
+ }
+
+ @Override
+ public ContentProvider instantiateProvider(ClassLoader cl, String className)
+ throws InstantiationException, IllegalAccessException, ClassNotFoundException {
+ if (className != null) {
+ if (className.contains(CustomConstructorContentProvider.class.getName())) {
+ return new CustomConstructorContentProvider(100);
+ }
+ }
+ return super.instantiateProvider(cl, className);
+ }
+
+ @Override
+ public Activity instantiateActivity(ClassLoader cl, String className, Intent intent)
+ throws InstantiationException, IllegalAccessException, ClassNotFoundException {
+ if (className != null) {
+ if (className.contains(CustomConstructorActivity.class.getName())) {
+ return new CustomConstructorActivity(100);
+ }
+ }
+ return super.instantiateActivity(cl, className, intent);
+ }
}
diff --git a/robolectric/src/test/java/org/robolectric/CustomAppComponentFactoryTest.java b/robolectric/src/test/java/org/robolectric/CustomAppComponentFactoryTest.java
new file mode 100644
index 000000000..da7f4eb6c
--- /dev/null
+++ b/robolectric/src/test/java/org/robolectric/CustomAppComponentFactoryTest.java
@@ -0,0 +1,161 @@
+package org.robolectric;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Activity;
+import android.app.Service;
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.IBinder;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.CustomConstructorServices.CustomConstructorIntentService;
+import org.robolectric.CustomConstructorServices.CustomConstructorJobService;
+import org.robolectric.CustomConstructorServices.CustomConstructorService;
+import org.robolectric.android.controller.ActivityController;
+import org.robolectric.annotation.Config;
+
+@RunWith(AndroidJUnit4.class)
+@Config(manifest = "TestAndroidManifestWithAppComponentFactory.xml", minSdk = Build.VERSION_CODES.P)
+public class CustomAppComponentFactoryTest {
+
+ @Test
+ public void instantiateServiceWithCustomConstructor() {
+ CustomConstructorService service = Robolectric.setupService(CustomConstructorService.class);
+ assertThat(service.getIntValue()).isEqualTo(100);
+ }
+
+ @Test
+ public void instantiateIntentServiceWithCustomConstructor() {
+ CustomConstructorIntentService service =
+ Robolectric.setupService(CustomConstructorIntentService.class);
+ assertThat(service.getIntValue()).isEqualTo(100);
+ }
+
+ @Test
+ public void instantiateJobServiceWithCustomConstructor() {
+ CustomConstructorJobService service =
+ Robolectric.setupService(CustomConstructorJobService.class);
+ assertThat(service.getIntValue()).isEqualTo(100);
+ }
+
+ @Test
+ public void instantiatePrivateServiceClass() {
+ PrivateService service = Robolectric.setupService(PrivateService.class);
+ assertThat(service.isCreated).isTrue();
+ }
+
+ @Test
+ public void instantiateContentProviderWithCustomConstructor() {
+ CustomConstructorContentProvider provider =
+ Robolectric.setupContentProvider(CustomConstructorContentProvider.class);
+ assertThat(provider.getIntValue()).isEqualTo(100);
+ }
+
+ @Test
+ public void instantiateContentProviderWithCustomConstructorAndAuthority() {
+ CustomConstructorContentProvider provider =
+ Robolectric.setupContentProvider(
+ CustomConstructorContentProvider.class, "org.robolectric.authority");
+ assertThat(provider.getIntValue()).isEqualTo(100);
+ }
+
+ @Test
+ public void instantiatePrivateContentProviderClass() {
+ PrivateContentProvider provider =
+ Robolectric.setupContentProvider(PrivateContentProvider.class);
+ assertThat(provider.isCreated).isTrue();
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ public void instantiateActivityWithCustomConstructor() {
+ CustomConstructorActivity activity = Robolectric.setupActivity(CustomConstructorActivity.class);
+ assertThat(activity.getIntValue()).isEqualTo(100);
+ }
+
+ @Test
+ public void instantiateActivityWithCustomConstructorAndIntent() {
+ Uri intentUri = Uri.parse("https://robolectric.org");
+ Intent intent = new Intent(Intent.ACTION_VIEW, intentUri);
+ try (ActivityController<CustomConstructorActivity> activity =
+ Robolectric.buildActivity(CustomConstructorActivity.class, intent)) {
+ assertThat(activity.get().getIntValue()).isEqualTo(100);
+ assertThat(activity.get().getIntent().getData()).isEqualTo(intentUri);
+ assertThat(activity.get().getIntent().getAction()).isEqualTo(Intent.ACTION_VIEW);
+ }
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ public void instantiatePrivateActivityClass() {
+ PrivateActivity activity = Robolectric.setupActivity(PrivateActivity.class);
+ assertThat(activity.isCreated).isTrue();
+ }
+
+ private static class PrivateService extends Service {
+ public boolean isCreated = false;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ isCreated = true;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+ }
+
+ private static class PrivateContentProvider extends ContentProvider {
+ public boolean isCreated = false;
+
+ @Override
+ public boolean onCreate() {
+ isCreated = true;
+ return false;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] strings, String s, String[] strings1, String s1) {
+ return null;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ return null;
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues contentValues) {
+ return null;
+ }
+
+ @Override
+ public int delete(Uri uri, String s, String[] strings) {
+ return 0;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues contentValues, String s, String[] strings) {
+ return 0;
+ }
+ }
+
+ private static class PrivateActivity extends Activity {
+ public boolean isCreated = false;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ isCreated = true;
+ }
+ }
+}
diff --git a/robolectric/src/test/java/org/robolectric/CustomConstructorActivity.java b/robolectric/src/test/java/org/robolectric/CustomConstructorActivity.java
new file mode 100644
index 000000000..2dae3b1f2
--- /dev/null
+++ b/robolectric/src/test/java/org/robolectric/CustomConstructorActivity.java
@@ -0,0 +1,15 @@
+package org.robolectric;
+
+import android.app.Activity;
+
+public class CustomConstructorActivity extends Activity {
+ private final int intValue;
+
+ public CustomConstructorActivity(int intValue) {
+ this.intValue = intValue;
+ }
+
+ public int getIntValue() {
+ return intValue;
+ }
+}
diff --git a/robolectric/src/test/java/org/robolectric/CustomConstructorContentProvider.java b/robolectric/src/test/java/org/robolectric/CustomConstructorContentProvider.java
new file mode 100644
index 000000000..8848f7d9f
--- /dev/null
+++ b/robolectric/src/test/java/org/robolectric/CustomConstructorContentProvider.java
@@ -0,0 +1,48 @@
+package org.robolectric;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+public class CustomConstructorContentProvider extends ContentProvider {
+ private final int intValue;
+
+ public CustomConstructorContentProvider(int intValue) {
+ this.intValue = intValue;
+ }
+
+ public int getIntValue() {
+ return intValue;
+ }
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] strings, String s, String[] strings1, String s1) {
+ return null;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ return null;
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues contentValues) {
+ return null;
+ }
+
+ @Override
+ public int delete(Uri uri, String s, String[] strings) {
+ return 0;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues contentValues, String s, String[] strings) {
+ return 0;
+ }
+}
diff --git a/robolectric/src/test/java/org/robolectric/CustomConstructorServices.java b/robolectric/src/test/java/org/robolectric/CustomConstructorServices.java
new file mode 100644
index 000000000..31d1eb20f
--- /dev/null
+++ b/robolectric/src/test/java/org/robolectric/CustomConstructorServices.java
@@ -0,0 +1,67 @@
+package org.robolectric;
+
+import android.app.IntentService;
+import android.app.Service;
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+import android.content.Intent;
+import android.os.IBinder;
+
+public class CustomConstructorServices {
+
+ public static class CustomConstructorService extends Service {
+ private final int intValue;
+
+ public CustomConstructorService(int intValue) {
+ this.intValue = intValue;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ public int getIntValue() {
+ return intValue;
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ public static class CustomConstructorIntentService extends IntentService {
+ private final int intValue;
+
+ public CustomConstructorIntentService(int intValue) {
+ super("test");
+ this.intValue = intValue;
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {}
+
+ public int getIntValue() {
+ return intValue;
+ }
+ }
+
+ public static class CustomConstructorJobService extends JobService {
+ private final int intValue;
+
+ public CustomConstructorJobService(int intValue) {
+ this.intValue = intValue;
+ }
+
+ @Override
+ public boolean onStartJob(JobParameters jobParameters) {
+ return true;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters jobParameters) {
+ return true;
+ }
+
+ public int getIntValue() {
+ return intValue;
+ }
+ }
+}
diff --git a/robolectric/src/test/java/org/robolectric/ManifestFactoryTest.java b/robolectric/src/test/java/org/robolectric/ManifestFactoryTest.java
index 107252b47..35b9a9694 100644
--- a/robolectric/src/test/java/org/robolectric/ManifestFactoryTest.java
+++ b/robolectric/src/test/java/org/robolectric/ManifestFactoryTest.java
@@ -1,7 +1,6 @@
package org.robolectric;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth8.assertThat;
import java.net.URL;
import java.nio.file.Paths;
@@ -20,7 +19,8 @@ import org.robolectric.res.Fs;
public class ManifestFactoryTest {
@Test
- public void whenBuildSystemApiPropertiesFileIsPresent_shouldUseDefaultManifestFactory() throws Exception {
+ public void whenBuildSystemApiPropertiesFileIsPresent_shouldUseDefaultManifestFactory()
+ throws Exception {
final Properties properties = new Properties();
properties.setProperty("android_sdk_home", "");
properties.setProperty("android_merged_manifest", "/path/to/MergedManifest.xml");
@@ -46,8 +46,8 @@ public class ManifestFactoryTest {
assertThat(manifestIdentifier.getLibraries()).isEmpty();
assertThat(manifestIdentifier.getPackageName()).isNull();
- AndroidManifest androidManifest = RobolectricTestRunner
- .createAndroidManifest(manifestIdentifier);
+ AndroidManifest androidManifest =
+ RobolectricTestRunner.createAndroidManifest(manifestIdentifier);
assertThat(androidManifest.getAndroidManifestFile())
.isEqualTo(Paths.get("/path/to/MergedManifest.xml"));
assertThat(androidManifest.getResDirectory()).isEqualTo(Paths.get("/path/to/merged-resources"));
@@ -70,10 +70,11 @@ public class ManifestFactoryTest {
}
};
- Config.Implementation config = Config.Builder.defaults()
- .setManifest("TestAndroidManifest.xml")
- .setPackageName("another.package")
- .build();
+ Config.Implementation config =
+ Config.Builder.defaults()
+ .setManifest("TestAndroidManifest.xml")
+ .setPackageName("another.package")
+ .build();
ManifestFactory manifestFactory = testRunner.getManifestFactory(config);
assertThat(manifestFactory).isInstanceOf(DefaultManifestFactory.class);
ManifestIdentifier manifestIdentifier = manifestFactory.identify(config);
diff --git a/robolectric/src/test/java/org/robolectric/RuntimeEnvironmentTest.java b/robolectric/src/test/java/org/robolectric/RuntimeEnvironmentTest.java
index 26a197003..bdbf97278 100644
--- a/robolectric/src/test/java/org/robolectric/RuntimeEnvironmentTest.java
+++ b/robolectric/src/test/java/org/robolectric/RuntimeEnvironmentTest.java
@@ -1,7 +1,5 @@
package org.robolectric;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
@@ -19,7 +17,6 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.annotation.Config;
import org.robolectric.annotation.LooperMode;
import org.robolectric.shadows.ShadowDisplay;
import org.robolectric.util.Scheduler;
@@ -133,7 +130,6 @@ public class RuntimeEnvironmentTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void testGetRotation() {
RuntimeEnvironment.setQualifiers("+land");
int screenRotation = ShadowDisplay.getDefaultDisplay().getRotation();
@@ -141,7 +137,6 @@ public class RuntimeEnvironmentTest {
}
@Test
- @Config(minSdk = KITKAT)
public void setQualifiers_resetsDateUtilsFormatCache() {
RuntimeEnvironment.setQualifiers("ar-rXB");
// Populate the DateUtils static format cache.
diff --git a/robolectric/src/test/java/org/robolectric/android/BootstrapTest.java b/robolectric/src/test/java/org/robolectric/android/BootstrapTest.java
index 2164ac6e1..3e9eb6a93 100644
--- a/robolectric/src/test/java/org/robolectric/android/BootstrapTest.java
+++ b/robolectric/src/test/java/org/robolectric/android/BootstrapTest.java
@@ -15,7 +15,6 @@ import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.content.res.Configuration.SCREENLAYOUT_LAYOUTDIR_LTR;
import static android.content.res.Configuration.SCREENLAYOUT_LAYOUTDIR_MASK;
-import static android.content.res.Configuration.SCREENLAYOUT_LAYOUTDIR_RTL;
import static android.content.res.Configuration.SCREENLAYOUT_LONG_MASK;
import static android.content.res.Configuration.SCREENLAYOUT_LONG_NO;
import static android.content.res.Configuration.SCREENLAYOUT_LONG_YES;
@@ -33,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.JELLY_BEAN;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.N;
import static android.os.Build.VERSION_CODES.O;
@@ -81,30 +79,28 @@ public class BootstrapTest {
@Test
@Config(qualifiers = "w480dp-h640dp")
public void shouldSetUpRealisticDisplay() throws Exception {
- if (Build.VERSION.SDK_INT > JELLY_BEAN) {
- DisplayManager displayManager =
- (DisplayManager)
- ApplicationProvider.getApplicationContext().getSystemService(Context.DISPLAY_SERVICE);
- DisplayInfo displayInfo = new DisplayInfo();
- Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
- display.getDisplayInfo(displayInfo);
-
- assertThat(displayInfo.name).isEqualTo("Built-in screen");
- assertThat(displayInfo.appWidth).isEqualTo(480);
- assertThat(displayInfo.appHeight).isEqualTo(640);
- assertThat(displayInfo.smallestNominalAppWidth).isEqualTo(480);
- assertThat(displayInfo.smallestNominalAppHeight).isEqualTo(480);
- assertThat(displayInfo.largestNominalAppWidth).isEqualTo(640);
- assertThat(displayInfo.largestNominalAppHeight).isEqualTo(640);
- assertThat(displayInfo.logicalWidth).isEqualTo(480);
- assertThat(displayInfo.logicalHeight).isEqualTo(640);
- assertThat(displayInfo.rotation).isEqualTo(ROTATION_0);
- assertThat(displayInfo.logicalDensityDpi).isEqualTo(160);
- assertThat(displayInfo.physicalXDpi).isEqualTo(160f);
- assertThat(displayInfo.physicalYDpi).isEqualTo(160f);
- if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
- assertThat(displayInfo.state).isEqualTo(Display.STATE_ON);
- }
+ DisplayManager displayManager =
+ (DisplayManager)
+ ApplicationProvider.getApplicationContext().getSystemService(Context.DISPLAY_SERVICE);
+ DisplayInfo displayInfo = new DisplayInfo();
+ Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
+ display.getDisplayInfo(displayInfo);
+
+ assertThat(displayInfo.name).isEqualTo("Built-in screen");
+ assertThat(displayInfo.appWidth).isEqualTo(480);
+ assertThat(displayInfo.appHeight).isEqualTo(640);
+ assertThat(displayInfo.smallestNominalAppWidth).isEqualTo(480);
+ assertThat(displayInfo.smallestNominalAppHeight).isEqualTo(480);
+ assertThat(displayInfo.largestNominalAppWidth).isEqualTo(640);
+ assertThat(displayInfo.largestNominalAppHeight).isEqualTo(640);
+ assertThat(displayInfo.logicalWidth).isEqualTo(480);
+ assertThat(displayInfo.logicalHeight).isEqualTo(640);
+ assertThat(displayInfo.rotation).isEqualTo(ROTATION_0);
+ assertThat(displayInfo.logicalDensityDpi).isEqualTo(160);
+ assertThat(displayInfo.physicalXDpi).isEqualTo(160f);
+ assertThat(displayInfo.physicalYDpi).isEqualTo(160f);
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
+ assertThat(displayInfo.state).isEqualTo(Display.STATE_ON);
}
DisplayMetrics displayMetrics =
@@ -116,30 +112,28 @@ public class BootstrapTest {
@Test
@Config(qualifiers = "w480dp-h640dp-land-hdpi")
public void shouldSetUpRealisticDisplay_landscapeHighDensity() throws Exception {
- if (Build.VERSION.SDK_INT > JELLY_BEAN) {
- DisplayManager displayManager =
- (DisplayManager)
- ApplicationProvider.getApplicationContext().getSystemService(Context.DISPLAY_SERVICE);
- DisplayInfo displayInfo = new DisplayInfo();
- Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
- display.getDisplayInfo(displayInfo);
-
- assertThat(displayInfo.name).isEqualTo("Built-in screen");
- assertThat(displayInfo.appWidth).isEqualTo(960);
- assertThat(displayInfo.appHeight).isEqualTo(720);
- assertThat(displayInfo.smallestNominalAppWidth).isEqualTo(720);
- assertThat(displayInfo.smallestNominalAppHeight).isEqualTo(720);
- assertThat(displayInfo.largestNominalAppWidth).isEqualTo(960);
- assertThat(displayInfo.largestNominalAppHeight).isEqualTo(960);
- assertThat(displayInfo.logicalWidth).isEqualTo(960);
- assertThat(displayInfo.logicalHeight).isEqualTo(720);
- assertThat(displayInfo.rotation).isEqualTo(ROTATION_90);
- assertThat(displayInfo.logicalDensityDpi).isEqualTo(240);
- assertThat(displayInfo.physicalXDpi).isEqualTo(240f);
- assertThat(displayInfo.physicalYDpi).isEqualTo(240f);
- if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
- assertThat(displayInfo.state).isEqualTo(Display.STATE_ON);
- }
+ DisplayManager displayManager =
+ (DisplayManager)
+ ApplicationProvider.getApplicationContext().getSystemService(Context.DISPLAY_SERVICE);
+ DisplayInfo displayInfo = new DisplayInfo();
+ Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
+ display.getDisplayInfo(displayInfo);
+
+ assertThat(displayInfo.name).isEqualTo("Built-in screen");
+ assertThat(displayInfo.appWidth).isEqualTo(960);
+ assertThat(displayInfo.appHeight).isEqualTo(720);
+ assertThat(displayInfo.smallestNominalAppWidth).isEqualTo(720);
+ assertThat(displayInfo.smallestNominalAppHeight).isEqualTo(720);
+ assertThat(displayInfo.largestNominalAppWidth).isEqualTo(960);
+ assertThat(displayInfo.largestNominalAppHeight).isEqualTo(960);
+ assertThat(displayInfo.logicalWidth).isEqualTo(960);
+ assertThat(displayInfo.logicalHeight).isEqualTo(720);
+ assertThat(displayInfo.rotation).isEqualTo(ROTATION_90);
+ assertThat(displayInfo.logicalDensityDpi).isEqualTo(240);
+ assertThat(displayInfo.physicalXDpi).isEqualTo(240f);
+ assertThat(displayInfo.physicalYDpi).isEqualTo(240f);
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {
+ assertThat(displayInfo.state).isEqualTo(Display.STATE_ON);
}
DisplayMetrics displayMetrics =
@@ -174,13 +168,7 @@ public class BootstrapTest {
assertThat(configuration.orientation).isEqualTo(ORIENTATION_PORTRAIT);
assertThat(configuration.uiMode & UI_MODE_TYPE_MASK).isEqualTo(UI_MODE_TYPE_NORMAL);
assertThat(configuration.uiMode & UI_MODE_NIGHT_MASK).isEqualTo(UI_MODE_NIGHT_NO);
-
- if (RuntimeEnvironment.getApiLevel() > JELLY_BEAN) {
- assertThat(configuration.densityDpi).isEqualTo(DisplayMetrics.DENSITY_DEFAULT);
- } else {
- assertThat(displayMetrics.densityDpi).isEqualTo(DisplayMetrics.DENSITY_DEFAULT);
- assertThat(displayMetrics.density).isEqualTo(1.0f);
- }
+ assertThat(configuration.densityDpi).isEqualTo(DisplayMetrics.DENSITY_DEFAULT);
assertThat(configuration.touchscreen).isEqualTo(TOUCHSCREEN_FINGER);
assertThat(configuration.keyboardHidden).isEqualTo(KEYBOARDHIDDEN_SOFT);
@@ -211,29 +199,21 @@ public class BootstrapTest {
displayMetrics);
String outQualifiers = ConfigurationV25.resourceQualifierString(configuration, displayMetrics);
- if (RuntimeEnvironment.getApiLevel() > JELLY_BEAN) {
- // Setting Locale in > JB results in forcing layout direction to match locale
- assertThat(outQualifiers).isEqualTo("mcc310-mnc4-fr-rFR-ldltr-sw400dp-w480dp-h456dp"
- + "-xlarge-long-round" + altOptsForO + "-land-appliance-night-hdpi-notouch-"
- + "keyshidden-12key-navhidden-dpad-v" + Build.VERSION.RESOURCES_SDK_INT);
- } else {
- assertThat(outQualifiers).isEqualTo("mcc310-mnc4-fr-rFR-ldrtl-sw400dp-w480dp-h456dp"
- + "-xlarge-long-round-land-appliance-night-hdpi-notouch-"
- + "keyshidden-12key-navhidden-dpad-v" + Build.VERSION.RESOURCES_SDK_INT);
- }
+ // Setting Locale results in forcing layout direction to match locale
+ assertThat(outQualifiers)
+ .isEqualTo(
+ "mcc310-mnc4-fr-rFR-ldltr-sw400dp-w480dp-h456dp"
+ + "-xlarge-long-round"
+ + altOptsForO
+ + "-land-appliance-night-hdpi-notouch-"
+ + "keyshidden-12key-navhidden-dpad-v"
+ + Build.VERSION.RESOURCES_SDK_INT);
assertThat(configuration.mcc).isEqualTo(310);
assertThat(configuration.mnc).isEqualTo(4);
assertThat(configuration.locale).isEqualTo(new Locale("fr", "FR"));
- if (RuntimeEnvironment.getApiLevel() > JELLY_BEAN) {
- // note that locale overrides ltr/rtl
- assertThat(configuration.screenLayout & SCREENLAYOUT_LAYOUTDIR_MASK)
- .isEqualTo(SCREENLAYOUT_LAYOUTDIR_LTR);
- } else {
- // but not on Jelly Bean...
- assertThat(configuration.screenLayout & SCREENLAYOUT_LAYOUTDIR_MASK)
- .isEqualTo(SCREENLAYOUT_LAYOUTDIR_RTL);
- }
+ assertThat(configuration.screenLayout & SCREENLAYOUT_LAYOUTDIR_MASK)
+ .isEqualTo(SCREENLAYOUT_LAYOUTDIR_LTR);
assertThat(configuration.smallestScreenWidthDp).isEqualTo(400);
assertThat(configuration.screenWidthDp).isEqualTo(480);
assertThat(configuration.screenHeightDp).isEqualTo(456);
@@ -243,12 +223,7 @@ public class BootstrapTest {
assertThat(configuration.orientation).isEqualTo(ORIENTATION_LANDSCAPE);
assertThat(configuration.uiMode & UI_MODE_TYPE_MASK).isEqualTo(UI_MODE_TYPE_APPLIANCE);
assertThat(configuration.uiMode & UI_MODE_NIGHT_MASK).isEqualTo(UI_MODE_NIGHT_YES);
- if (RuntimeEnvironment.getApiLevel() > JELLY_BEAN) {
- assertThat(configuration.densityDpi).isEqualTo(DisplayMetrics.DENSITY_HIGH);
- } else {
- assertThat(displayMetrics.densityDpi).isEqualTo(DisplayMetrics.DENSITY_HIGH);
- assertThat(displayMetrics.density).isEqualTo(1.5f);
- }
+ assertThat(configuration.densityDpi).isEqualTo(DisplayMetrics.DENSITY_HIGH);
assertThat(configuration.touchscreen).isEqualTo(TOUCHSCREEN_NOTOUCH);
assertThat(configuration.keyboardHidden).isEqualTo(KEYBOARDHIDDEN_YES);
assertThat(configuration.keyboard).isEqualTo(KEYBOARD_12KEY);
diff --git a/robolectric/src/test/java/org/robolectric/android/DeviceConfigTest.java b/robolectric/src/test/java/org/robolectric/android/DeviceConfigTest.java
index f2395b788..d71101765 100644
--- a/robolectric/src/test/java/org/robolectric/android/DeviceConfigTest.java
+++ b/robolectric/src/test/java/org/robolectric/android/DeviceConfigTest.java
@@ -36,7 +36,6 @@ public class DeviceConfigTest {
}
@Test
- @Config(minSdk = VERSION_CODES.JELLY_BEAN_MR1)
public void applyToConfiguration() {
applyQualifiers("en-rUS-w400dp-h800dp-notround");
assertThat(asQualifierString())
@@ -95,7 +94,7 @@ public class DeviceConfigTest {
+ "port-notnight-mdpi-finger-keyssoft-nokeys-navhidden-nonav");
}
- // todo: this fails on JELLY_BEAN and LOLLIPOP through M... why?
+ // todo: this fails on LOLLIPOP through M... why?
@Test
@Config(minSdk = VERSION_CODES.N)
public void applyRules_rtlScript() {
diff --git a/robolectric/src/test/java/org/robolectric/android/ResourceLoaderTest.java b/robolectric/src/test/java/org/robolectric/android/ResourceLoaderTest.java
index 15ca52c68..b2c1edf64 100644
--- a/robolectric/src/test/java/org/robolectric/android/ResourceLoaderTest.java
+++ b/robolectric/src/test/java/org/robolectric/android/ResourceLoaderTest.java
@@ -8,7 +8,6 @@ import static org.robolectric.shadows.ShadowAssetManager.useLegacy;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
-import android.os.Build.VERSION_CODES;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
@@ -84,11 +83,7 @@ public class ResourceLoaderTest {
assertThat(textView.getText().toString()).isEqualTo("default");
RuntimeEnvironment.setQualifiers("fr-land"); // testing if this pollutes the other test
Configuration configuration = Resources.getSystem().getConfiguration();
- if (RuntimeEnvironment.getApiLevel() <= VERSION_CODES.JELLY_BEAN) {
- configuration.locale = new Locale("fr", "FR");
- } else {
- configuration.setLocale(new Locale("fr", "FR"));
- }
+ configuration.setLocale(new Locale("fr", "FR"));
configuration.orientation = Configuration.ORIENTATION_LANDSCAPE;
Resources.getSystem().updateConfiguration(configuration, null);
}
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 181a8013a..09aec11c9 100644
--- a/robolectric/src/test/java/org/robolectric/android/controller/ActivityControllerTest.java
+++ b/robolectric/src/test/java/org/robolectric/android/controller/ActivityControllerTest.java
@@ -185,7 +185,6 @@ public class ActivityControllerTest {
}
@Test
- @Config(minSdk = VERSION_CODES.JELLY_BEAN_MR1)
public void destroy_cleansUpWindowManagerState() {
WindowManager windowManager = controller.get().getWindowManager();
ShadowWindowManagerImpl shadowWindowManager =
diff --git a/robolectric/src/test/java/org/robolectric/internal/BuckManifestFactoryTest.java b/robolectric/src/test/java/org/robolectric/internal/BuckManifestFactoryTest.java
index f7cd3edf3..10ac36359 100644
--- a/robolectric/src/test/java/org/robolectric/internal/BuckManifestFactoryTest.java
+++ b/robolectric/src/test/java/org/robolectric/internal/BuckManifestFactoryTest.java
@@ -1,7 +1,6 @@
package org.robolectric.internal;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth8.assertThat;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.io.Files;
@@ -23,8 +22,7 @@ import org.robolectric.res.ResourcePath;
@RunWith(JUnit4.class)
public class BuckManifestFactoryTest {
- @Rule
- public TemporaryFolder tempFolder = new TemporaryFolder();
+ @Rule public TemporaryFolder tempFolder = new TemporaryFolder();
private Config.Builder configBuilder;
private BuckManifestFactory buckManifestFactory;
@@ -43,18 +41,20 @@ public class BuckManifestFactoryTest {
System.clearProperty("buck.robolectric_assets_directories");
}
- @Test public void identify() throws Exception {
+ @Test
+ public void identify() throws Exception {
ManifestIdentifier manifestIdentifier = buckManifestFactory.identify(configBuilder.build());
assertThat(manifestIdentifier.getManifestFile())
.isEqualTo(Paths.get("buck/AndroidManifest.xml"));
- assertThat(manifestIdentifier.getPackageName())
- .isEqualTo("com.robolectric.buck");
+ assertThat(manifestIdentifier.getPackageName()).isEqualTo("com.robolectric.buck");
}
- @Test public void multiple_res_dirs() throws Exception {
- System.setProperty("buck.robolectric_res_directories",
- "buck/res1" + File.pathSeparator + "buck/res2");
- System.setProperty("buck.robolectric_assets_directories",
+ @Test
+ public void multiple_res_dirs() throws Exception {
+ System.setProperty(
+ "buck.robolectric_res_directories", "buck/res1" + File.pathSeparator + "buck/res2");
+ System.setProperty(
+ "buck.robolectric_assets_directories",
"buck/assets1" + File.pathSeparator + "buck/assets2");
ManifestIdentifier manifestIdentifier = buckManifestFactory.identify(configBuilder.build());
@@ -72,7 +72,8 @@ public class BuckManifestFactoryTest {
new ResourcePath(manifest.getRClass(), null, Paths.get("buck/assets1")));
}
- @Test public void pass_multiple_res_dirs_in_file() throws Exception {
+ @Test
+ public void pass_multiple_res_dirs_in_file() throws Exception {
String resDirectoriesFileName = "res-directories";
File resDirectoriesFile = tempFolder.newFile(resDirectoriesFileName);
Files.asCharSink(resDirectoriesFile, UTF_8).write("buck/res1\nbuck/res2");
diff --git a/robolectric/src/test/java/org/robolectric/internal/DefaultManifestFactoryTest.java b/robolectric/src/test/java/org/robolectric/internal/DefaultManifestFactoryTest.java
index c32dc19cd..026d675b4 100644
--- a/robolectric/src/test/java/org/robolectric/internal/DefaultManifestFactoryTest.java
+++ b/robolectric/src/test/java/org/robolectric/internal/DefaultManifestFactoryTest.java
@@ -1,7 +1,6 @@
package org.robolectric.internal;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth8.assertThat;
import java.nio.file.Paths;
import java.util.Properties;
@@ -100,7 +99,6 @@ public class DefaultManifestFactoryTest {
.isEqualTo(Paths.get("gradle/AndroidManifest.xml"));
assertThat(manifest.getResDirectory()).isEqualTo(Paths.get("gradle/res"));
assertThat(manifest.getAssetsDirectory()).isEqualTo(Paths.get("gradle/assets"));
- assertThat(manifest.getRClassName())
- .isEqualTo("overridden.package.R");
+ assertThat(manifest.getRClassName()).isEqualTo("overridden.package.R");
}
}
diff --git a/robolectric/src/test/java/org/robolectric/internal/MavenManifestFactoryTest.java b/robolectric/src/test/java/org/robolectric/internal/MavenManifestFactoryTest.java
index 414fdb09d..60b064894 100644
--- a/robolectric/src/test/java/org/robolectric/internal/MavenManifestFactoryTest.java
+++ b/robolectric/src/test/java/org/robolectric/internal/MavenManifestFactoryTest.java
@@ -1,7 +1,6 @@
package org.robolectric.internal;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth8.assertThat;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -23,14 +22,16 @@ public class MavenManifestFactoryTest {
myMavenManifestFactory = new MyMavenManifestFactory();
}
- @Test public void identify() throws Exception {
+ @Test
+ public void identify() throws Exception {
ManifestIdentifier manifestIdentifier = myMavenManifestFactory.identify(configBuilder.build());
assertThat(manifestIdentifier.getManifestFile())
.isEqualTo(Paths.get("_fakefs_path").resolve("to").resolve("DifferentManifest.xml"));
assertThat(manifestIdentifier.getResDir()).isEqualTo(Paths.get("_fakefs_path/to/res"));
}
- @Test public void withDotSlashManifest_identify() throws Exception {
+ @Test
+ public void withDotSlashManifest_identify() throws Exception {
configBuilder.setManifest("./DifferentManifest.xml");
ManifestIdentifier manifestIdentifier = myMavenManifestFactory.identify(configBuilder.build());
@@ -40,7 +41,8 @@ public class MavenManifestFactoryTest {
.isEqualTo(Paths.get("_fakefs_path/to/res"));
}
- @Test public void withDotDotSlashManifest_identify() throws Exception {
+ @Test
+ public void withDotDotSlashManifest_identify() throws Exception {
configBuilder.setManifest("../DifferentManifest.xml");
ManifestIdentifier manifestIdentifier = myMavenManifestFactory.identify(configBuilder.build());
@@ -55,4 +57,4 @@ public class MavenManifestFactoryTest {
return Paths.get("_fakefs_path").resolve("to");
}
}
-} \ No newline at end of file
+}
diff --git a/robolectric/src/test/java/org/robolectric/internal/bytecode/ShadowMapTest.java b/robolectric/src/test/java/org/robolectric/internal/bytecode/ShadowMapTest.java
index 2e201197f..653d40564 100644
--- a/robolectric/src/test/java/org/robolectric/internal/bytecode/ShadowMapTest.java
+++ b/robolectric/src/test/java/org/robolectric/internal/bytecode/ShadowMapTest.java
@@ -69,8 +69,10 @@ public class ShadowMapTest {
}
@Test public void getInvalidatedClasses_disjoin() {
- ShadowMap current = baseShadowMap.newBuilder().addShadowClass(A1, A2, true, false).build();
- ShadowMap previous = baseShadowMap.newBuilder().addShadowClass(B1, B2, true, false).build();
+ ShadowMap current =
+ baseShadowMap.newBuilder().addShadowClass(A1, A2, true, false, false).build();
+ ShadowMap previous =
+ baseShadowMap.newBuilder().addShadowClass(B1, B2, true, false, false).build();
assertThat(current.getInvalidatedClasses(previous)).containsExactly(A1, B1);
}
@@ -79,22 +81,22 @@ public class ShadowMapTest {
ShadowMap current =
baseShadowMap
.newBuilder()
- .addShadowClass(A1, A2, true, false)
- .addShadowClass(C1, C2, true, false)
+ .addShadowClass(A1, A2, true, false, false)
+ .addShadowClass(C1, C2, true, false, false)
.build();
ShadowMap previous =
baseShadowMap
.newBuilder()
- .addShadowClass(A1, A2, true, false)
- .addShadowClass(C1, C3, true, false)
+ .addShadowClass(A1, A2, true, false, false)
+ .addShadowClass(C1, C3, true, false, false)
.build();
assertThat(current.getInvalidatedClasses(previous)).containsExactly(C1);
}
@Test public void equalsHashCode() throws Exception {
- ShadowMap a = baseShadowMap.newBuilder().addShadowClass(A, B, true, false).build();
- ShadowMap b = baseShadowMap.newBuilder().addShadowClass(A, B, true, false).build();
+ ShadowMap a = baseShadowMap.newBuilder().addShadowClass(A, B, true, false, false).build();
+ ShadowMap b = baseShadowMap.newBuilder().addShadowClass(A, B, true, false, false).build();
assertThat(a).isEqualTo(b);
assertThat(a.hashCode()).isEqualTo(b.hashCode());
@@ -102,7 +104,7 @@ public class ShadowMapTest {
assertThat(c).isEqualTo(b);
assertThat(c.hashCode()).isEqualTo(b.hashCode());
- ShadowMap d = baseShadowMap.newBuilder().addShadowClass(A, X, true, false).build();
+ ShadowMap d = baseShadowMap.newBuilder().addShadowClass(A, X, true, false, false).build();
assertThat(d).isNotEqualTo(a);
assertThat(d.hashCode()).isNotEqualTo(b.hashCode());
}
diff --git a/robolectric/src/test/java/org/robolectric/manifest/AndroidManifestTest.java b/robolectric/src/test/java/org/robolectric/manifest/AndroidManifestTest.java
index 51b809b69..e64cacd04 100644
--- a/robolectric/src/test/java/org/robolectric/manifest/AndroidManifestTest.java
+++ b/robolectric/src/test/java/org/robolectric/manifest/AndroidManifestTest.java
@@ -28,7 +28,7 @@ public class AndroidManifestTest {
@Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();
@Test
- public void parseManifest_shouldReadContentProviders() throws Exception {
+ public void parseManifest_shouldReadContentProviders() {
AndroidManifest config = newConfig("TestAndroidManifestWithContentProviders.xml");
assertThat(config.getContentProviders().get(0).getName())
@@ -45,7 +45,7 @@ public class AndroidManifestTest {
}
@Test
- public void parseManifest_shouldReadPermissions() throws Exception {
+ public void parseManifest_shouldReadPermissions() {
AndroidManifest config = newConfig("TestAndroidManifestWithPermissions.xml");
assertThat(config.getPermissions().keySet())
@@ -63,7 +63,7 @@ public class AndroidManifestTest {
}
@Test
- public void parseManifest_shouldReadPermissionGroups() throws Exception {
+ public void parseManifest_shouldReadPermissionGroups() {
AndroidManifest config = newConfig("TestAndroidManifestWithPermissions.xml");
assertThat(config.getPermissionGroups().keySet())
@@ -76,7 +76,7 @@ public class AndroidManifestTest {
}
@Test
- public void parseManifest_shouldReadBroadcastReceivers() throws Exception {
+ public void parseManifest_shouldReadBroadcastReceivers() {
AndroidManifest config = newConfig("TestAndroidManifestWithReceivers.xml");
assertThat(config.getBroadcastReceivers()).hasSize(8);
@@ -124,7 +124,7 @@ public class AndroidManifestTest {
}
@Test
- public void parseManifest_shouldReadServices() throws Exception {
+ public void parseManifest_shouldReadServices() {
AndroidManifest config = newConfig("TestAndroidManifestWithServices.xml");
assertThat(config.getServices()).hasSize(2);
@@ -152,13 +152,13 @@ public class AndroidManifestTest {
}
@Test
- public void testManifestWithNoApplicationElement() throws Exception {
+ public void testManifestWithNoApplicationElement() {
AndroidManifest config = newConfig("TestAndroidManifestNoApplicationElement.xml");
assertThat(config.getPackageName()).isEqualTo("org.robolectric");
}
@Test
- public void parseManifest_shouldReadBroadcastReceiversWithMetaData() throws Exception {
+ public void parseManifest_shouldReadBroadcastReceiversWithMetaData() {
AndroidManifest config = newConfig("TestAndroidManifestWithReceivers.xml");
assertThat(config.getBroadcastReceivers().get(4).getName())
@@ -208,7 +208,7 @@ public class AndroidManifestTest {
}
@Test
- public void shouldReadBroadcastReceiverPermissions() throws Exception {
+ public void shouldReadBroadcastReceiverPermissions() {
AndroidManifest config = newConfig("TestAndroidManifestWithReceivers.xml");
assertThat(config.getBroadcastReceivers().get(7).getName())
@@ -231,19 +231,19 @@ public class AndroidManifestTest {
assertThat(newConfigWith("minsdk7.xml", "android:minSdkVersion=\"7\"").getTargetSdkVersion())
.isEqualTo(7);
assertThat(newConfigWith("noattributes.xml", "").getTargetSdkVersion())
- .isEqualTo(VERSION_CODES.JELLY_BEAN);
+ .isEqualTo(VERSION_CODES.KITKAT);
}
@Test
- public void shouldReadMinSdkVersionFromAndroidManifestOrDefaultToJellyBean() throws Exception {
+ public void shouldReadMinSdkVersionFromAndroidManifestOrDefaultToKitKat() throws Exception {
assertThat(newConfigWith("minsdk17.xml", "android:minSdkVersion=\"17\"").getMinSdkVersion())
.isEqualTo(17);
assertThat(newConfigWith("noattributes.xml", "").getMinSdkVersion())
- .isEqualTo(VERSION_CODES.JELLY_BEAN);
+ .isEqualTo(VERSION_CODES.KITKAT);
}
@Test
- public void shouldReadProcessFromAndroidManifest() throws Exception {
+ public void shouldReadProcessFromAndroidManifest() {
assertThat(newConfig("TestAndroidManifestWithProcess.xml").getProcessName())
.isEqualTo("robolectricprocess");
}
@@ -256,7 +256,7 @@ public class AndroidManifestTest {
@Test
@Config(manifest = "TestAndroidManifestWithAppMetaData.xml")
- public void shouldReturnApplicationMetaData() throws Exception {
+ public void shouldReturnApplicationMetaData() {
Map<String, Object> meta =
newConfig("TestAndroidManifestWithAppMetaData.xml").getApplicationMetaData();
@@ -301,7 +301,7 @@ public class AndroidManifestTest {
}
@Test
- public void shouldTolerateMissingRFile() throws Exception {
+ public void shouldTolerateMissingRFile() {
AndroidManifest appManifest =
new AndroidManifest(
resourceFile("TestAndroidManifestWithNoRFile.xml"),
@@ -312,7 +312,7 @@ public class AndroidManifestTest {
}
@Test
- public void whenNullManifestFile_getRClass_shouldComeFromPackageName() throws Exception {
+ public void whenNullManifestFile_getRClass_shouldComeFromPackageName() {
AndroidManifest appManifest =
new AndroidManifest(null, resourceFile("res"), resourceFile("assets"), "org.robolectric");
assertThat(appManifest.getRClass()).isEqualTo(org.robolectric.R.class);
@@ -320,7 +320,7 @@ public class AndroidManifestTest {
}
@Test
- public void whenMissingManifestFile_getRClass_shouldComeFromPackageName() throws Exception {
+ public void whenMissingManifestFile_getRClass_shouldComeFromPackageName() {
AndroidManifest appManifest =
new AndroidManifest(
resourceFile("none.xml"),
@@ -332,7 +332,7 @@ public class AndroidManifestTest {
}
@Test
- public void whenMissingManifestFile_getPackageName_shouldBeDefault() throws Exception {
+ public void whenMissingManifestFile_getPackageName_shouldBeDefault() {
AndroidManifest appManifest =
new AndroidManifest(null, resourceFile("res"), resourceFile("assets"), null);
assertThat(appManifest.getPackageName()).isEqualTo("org.robolectric.default");
@@ -398,7 +398,7 @@ public class AndroidManifestTest {
}
@Test
- public void shouldReadPermissions() throws Exception {
+ public void shouldReadPermissions() {
AndroidManifest config = newConfig("TestAndroidManifestWithPermissions.xml");
assertThat(config.getUsedPermissions()).hasSize(3);
@@ -408,7 +408,7 @@ public class AndroidManifestTest {
}
@Test
- public void shouldReadPartiallyQualifiedActivities() throws Exception {
+ public void shouldReadPartiallyQualifiedActivities() {
AndroidManifest config = newConfig("TestAndroidManifestForActivities.xml");
assertThat(config.getActivityDatas()).hasSize(2);
assertThat(config.getActivityDatas()).containsKey("org.robolectric.shadows.TestActivity");
@@ -416,7 +416,7 @@ public class AndroidManifestTest {
}
@Test
- public void shouldReadActivityAliases() throws Exception {
+ public void shouldReadActivityAliases() {
AndroidManifest config = newConfig("TestAndroidManifestForActivityAliases.xml");
assertThat(config.getActivityDatas()).hasSize(2);
assertThat(config.getActivityDatas()).containsKey("org.robolectric.shadows.TestActivity");
@@ -468,7 +468,7 @@ public class AndroidManifestTest {
}
@Test
- public void shouldHaveStableHashCode() throws Exception {
+ public void shouldHaveStableHashCode() {
AndroidManifest manifest = newConfig("TestAndroidManifestWithContentProviders.xml");
int hashCode1 = manifest.hashCode();
manifest.getServices();
@@ -477,13 +477,13 @@ public class AndroidManifestTest {
}
@Test
- public void shouldReadApplicationAttrsFromAndroidManifest() throws Exception {
+ public void shouldReadApplicationAttrsFromAndroidManifest() {
AndroidManifest config = newConfig("TestAndroidManifestWithFlags.xml");
assertThat(config.getApplicationAttributes().get("android:allowBackup")).isEqualTo("true");
}
@Test
- public void allFieldsShouldBePrimitivesOrJavaLangOrRobolectric() throws Exception {
+ public void allFieldsShouldBePrimitivesOrJavaLangOrRobolectric() {
List<Field> wrongFields = new ArrayList<>();
for (Field field : AndroidManifest.class.getDeclaredFields()) {
Class<?> type = field.getType();
@@ -502,35 +502,35 @@ public class AndroidManifestTest {
}
@Test
- public void activitiesWithoutIntentFiltersNotExportedByDefault() throws Exception {
+ public void activitiesWithoutIntentFiltersNotExportedByDefault() {
AndroidManifest config = newConfig("TestAndroidManifestForActivities.xml");
ActivityData activityData = config.getActivityData("org.robolectric.shadows.TestActivity");
assertThat(activityData.isExported()).isFalse();
}
@Test
- public void activitiesWithIntentFiltersExportedByDefault() throws Exception {
+ public void activitiesWithIntentFiltersExportedByDefault() {
AndroidManifest config = newConfig("TestAndroidManifestForActivitiesWithIntentFilter.xml");
ActivityData activityData = config.getActivityData("org.robolectric.shadows.TestActivity");
assertThat(activityData.isExported()).isTrue();
}
@Test
- public void servicesWithoutIntentFiltersNotExportedByDefault() throws Exception {
+ public void servicesWithoutIntentFiltersNotExportedByDefault() {
AndroidManifest config = newConfig("TestAndroidManifestWithServices.xml");
ServiceData serviceData = config.getServiceData("com.bar.ServiceWithoutIntentFilter");
assertThat(serviceData.isExported()).isFalse();
}
@Test
- public void servicesWithIntentFiltersExportedByDefault() throws Exception {
+ public void servicesWithIntentFiltersExportedByDefault() {
AndroidManifest config = newConfig("TestAndroidManifestWithServices.xml");
ServiceData serviceData = config.getServiceData("com.foo.Service");
assertThat(serviceData.isExported()).isTrue();
}
@Test
- public void receiversWithoutIntentFiltersNotExportedByDefault() throws Exception {
+ public void receiversWithoutIntentFiltersNotExportedByDefault() {
AndroidManifest config = newConfig("TestAndroidManifestWithReceivers.xml");
BroadcastReceiverData receiverData =
config.getBroadcastReceiver("com.bar.ReceiverWithoutIntentFilter");
@@ -539,7 +539,7 @@ public class AndroidManifestTest {
}
@Test
- public void receiversWithIntentFiltersExportedByDefault() throws Exception {
+ public void receiversWithIntentFiltersExportedByDefault() {
AndroidManifest config = newConfig("TestAndroidManifestWithReceivers.xml");
BroadcastReceiverData receiverData = config.getBroadcastReceiver("com.foo.Receiver");
assertThat(receiverData).isNotNull();
@@ -547,7 +547,7 @@ public class AndroidManifestTest {
}
@Test
- public void getTransitiveManifests() throws Exception {
+ public void getTransitiveManifests() {
AndroidManifest lib1 =
new AndroidManifest(resourceFile("lib1/AndroidManifest.xml"), null, null);
AndroidManifest lib2 = new AndroidManifest(resourceFile("lib2/AndroidManifest.xml"), null, null,
diff --git a/robolectric/src/test/java/org/robolectric/plugins/LegacyDependencyResolverTest.java b/robolectric/src/test/java/org/robolectric/plugins/LegacyDependencyResolverTest.java
index 4cb13a0c8..e628be5cd 100644
--- a/robolectric/src/test/java/org/robolectric/plugins/LegacyDependencyResolverTest.java
+++ b/robolectric/src/test/java/org/robolectric/plugins/LegacyDependencyResolverTest.java
@@ -1,7 +1,6 @@
package org.robolectric.plugins;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth8.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -46,9 +45,9 @@ public class LegacyDependencyResolverTest {
@Test
public void whenRobolectricDepsPropertiesProperty() throws Exception {
- Path depsPath = tempDirectory
- .createFile("deps.properties",
- "org.robolectric\\:android-all\\:" + VERSION + ": file-123.jar");
+ Path depsPath =
+ tempDirectory.createFile(
+ "deps.properties", "org.robolectric\\:android-all\\:" + VERSION + ": file-123.jar");
Path jarPath = tempDirectory.createFile("file-123.jar", "...");
properties.setProperty("robolectric-deps.properties", depsPath.toString());
@@ -61,9 +60,9 @@ public class LegacyDependencyResolverTest {
@Test
public void whenRobolectricDepsPropertiesPropertyAndOfflineProperty() throws Exception {
- Path depsPath = tempDirectory
- .createFile("deps.properties",
- "org.robolectric\\:android-all\\:" + VERSION + ": file-123.jar");
+ Path depsPath =
+ tempDirectory.createFile(
+ "deps.properties", "org.robolectric\\:android-all\\:" + VERSION + ": file-123.jar");
Path jarPath = tempDirectory.createFile("file-123.jar", "...");
properties.setProperty("robolectric-deps.properties", depsPath.toString());
@@ -77,9 +76,9 @@ public class LegacyDependencyResolverTest {
@Test
public void whenRobolectricDepsPropertiesResource() throws Exception {
- Path depsPath = tempDirectory
- .createFile("deps.properties",
- "org.robolectric\\:android-all\\:" + VERSION + ": file-123.jar");
+ Path depsPath =
+ tempDirectory.createFile(
+ "deps.properties", "org.robolectric\\:android-all\\:" + VERSION + ": file-123.jar");
when(mockClassLoader.getResource("robolectric-deps.properties")).thenReturn(meh(depsPath));
DependencyResolver resolver = new LegacyDependencyResolver(properties, mockClassLoader);
@@ -110,8 +109,7 @@ public class LegacyDependencyResolverTest {
DependencyResolver resolver = new LegacyDependencyResolver(properties, mockClassLoader);
URL jarUrl = resolver.getLocalArtifactUrl(DEPENDENCY_COORDS);
- assertThat(Fs.fromUrl(jarUrl))
- .isEqualTo(Paths.get("/some/fake/file.jar").toAbsolutePath());
+ assertThat(Fs.fromUrl(jarUrl)).isEqualTo(Paths.get("/some/fake/file.jar").toAbsolutePath());
}
public static class FakeMavenDependencyResolver implements DependencyResolver {
diff --git a/robolectric/src/test/java/org/robolectric/shadows/AppWidgetProviderInfoBuilderTest.java b/robolectric/src/test/java/org/robolectric/shadows/AppWidgetProviderInfoBuilderTest.java
index dfb47e40a..0496529f0 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/AppWidgetProviderInfoBuilderTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/AppWidgetProviderInfoBuilderTest.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static android.os.Build.VERSION_CODES.L;
import static com.google.common.truth.Truth.assertThat;
@@ -21,7 +20,6 @@ import org.robolectric.annotation.Config;
/** Tests for {@link AppWidgetProviderInfoBuilder} */
@RunWith(AndroidJUnit4.class)
-@Config(minSdk = JELLY_BEAN)
public class AppWidgetProviderInfoBuilderTest {
private Context context;
private PackageManager packageManager;
diff --git a/robolectric/src/test/java/org/robolectric/shadows/AssetManagerCachingTest.java b/robolectric/src/test/java/org/robolectric/shadows/AssetManagerCachingTest.java
new file mode 100644
index 000000000..d0171913f
--- /dev/null
+++ b/robolectric/src/test/java/org/robolectric/shadows/AssetManagerCachingTest.java
@@ -0,0 +1,53 @@
+package org.robolectric.shadows;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Application;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import java.util.concurrent.atomic.AtomicLong;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.versioning.AndroidVersions.P;
+
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@RunWith(AndroidJUnit4.class)
+@Config(minSdk = P.SDK_INT)
+public class AssetManagerCachingTest {
+ private static final AtomicLong systemNativePtr = new AtomicLong();
+
+ @Test
+ public void test1_getAssetManagerPtr() {
+ AssetManager systemAssetManager = Resources.getSystem().getAssets();
+ systemNativePtr.set(getNativePtr(systemAssetManager));
+ assertThat(systemNativePtr.get()).isNotEqualTo(0);
+ }
+
+ @Test
+ public void test2_verifySamePtr() {
+ AssetManager systemAssetManager = Resources.getSystem().getAssets();
+ long nativePtr = getNativePtr(systemAssetManager);
+ assertThat(nativePtr).isEqualTo(systemNativePtr.get());
+ }
+
+ @Test
+ public void test3_createApplicationAssets() {
+ AssetManager systemAssetManager = Resources.getSystem().getAssets();
+ Application application = RuntimeEnvironment.getApplication();
+ AssetManager assetManager = application.getAssets();
+ long nativePtr = getNativePtr(systemAssetManager);
+ long appNativePtr = getNativePtr(assetManager);
+ assertThat(nativePtr).isEqualTo(systemNativePtr.get());
+ assertThat(nativePtr).isNotEqualTo(appNativePtr);
+ }
+
+ private static long getNativePtr(AssetManager assetManager) {
+ return ((ShadowAssetManager) Shadow.extract(assetManager)).getNativePtr();
+ }
+}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/AudioDeviceInfoBuilderTest.java b/robolectric/src/test/java/org/robolectric/shadows/AudioDeviceInfoBuilderTest.java
index e1961877a..6e05e7d15 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/AudioDeviceInfoBuilderTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/AudioDeviceInfoBuilderTest.java
@@ -1,11 +1,15 @@
package org.robolectric.shadows;
-import static android.media.AudioDeviceInfo.TYPE_BLUETOOTH_A2DP;
import static android.os.Build.VERSION_CODES.M;
+import static android.os.Build.VERSION_CODES.R;
+import static android.os.Build.VERSION_CODES.S;
import static com.google.common.truth.Truth.assertThat;
import android.media.AudioDeviceInfo;
+import android.media.AudioFormat;
+import android.media.AudioProfile;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.common.collect.ImmutableList;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
@@ -15,11 +19,45 @@ import org.robolectric.annotation.Config;
@Config(minSdk = M)
public class AudioDeviceInfoBuilderTest {
+ @Config(minSdk = M, maxSdk = R)
@Test
- public void canCreateAudioDeviceInfoWithDesiredType() {
+ public void buildAudioDeviceInfo_apiM_withDefaultValues_buildsExpectedObject() {
+ AudioDeviceInfo audioDeviceInfo = AudioDeviceInfoBuilder.newBuilder().build();
+
+ assertThat(audioDeviceInfo.getType()).isEqualTo(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
+ }
+
+ @Config(minSdk = S)
+ @Test
+ public void buildAudioDeviceInfo_apiS_withDefaultValues_buildsExpectedObject() {
+ AudioDeviceInfo audioDeviceInfo = AudioDeviceInfoBuilder.newBuilder().build();
+
+ assertThat(audioDeviceInfo.getType()).isEqualTo(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
+ assertThat(audioDeviceInfo.getAudioProfiles()).isEmpty();
+ }
+
+ @Config(minSdk = M, maxSdk = R)
+ @Test
+ public void buildAudioDeviceInfo_apiM_witSetValues_buildsExpectedObject() {
+ AudioDeviceInfo audioDeviceInfo =
+ AudioDeviceInfoBuilder.newBuilder().setType(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP).build();
+
+ assertThat(audioDeviceInfo.getType()).isEqualTo(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP);
+ }
+
+ @Test
+ @Config(minSdk = S)
+ public void buildAudioDeviceInfo_apiS_witSetValues_buildsExpectedObject() {
+ ImmutableList<AudioProfile> audioProfiles =
+ ImmutableList.of(
+ AudioProfileBuilder.newBuilder().setFormat(AudioFormat.ENCODING_PCM_32BIT).build());
AudioDeviceInfo audioDeviceInfo =
- AudioDeviceInfoBuilder.newBuilder().setType(TYPE_BLUETOOTH_A2DP).build();
+ AudioDeviceInfoBuilder.newBuilder()
+ .setType(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP)
+ .setProfiles(audioProfiles)
+ .build();
- assertThat(audioDeviceInfo.getType()).isEqualTo(TYPE_BLUETOOTH_A2DP);
+ assertThat(audioDeviceInfo.getType()).isEqualTo(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP);
+ assertThat(audioDeviceInfo.getAudioProfiles()).isEqualTo(audioProfiles);
}
}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/AudioProfileBuilderTest.java b/robolectric/src/test/java/org/robolectric/shadows/AudioProfileBuilderTest.java
new file mode 100644
index 000000000..30980a532
--- /dev/null
+++ b/robolectric/src/test/java/org/robolectric/shadows/AudioProfileBuilderTest.java
@@ -0,0 +1,77 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.S;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.media.AudioFormat;
+import android.media.AudioProfile;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+/** Tests for {@link AudioProfileBuilder}. */
+@RunWith(AndroidJUnit4.class)
+@Config(minSdk = S)
+public class AudioProfileBuilderTest {
+
+ @Test
+ public void canCreateAudioProfile() {
+ AudioProfile audioProfile =
+ AudioProfileBuilder.newBuilder()
+ .setFormat(AudioFormat.ENCODING_AC3)
+ .setSamplingRates(new int[] {48_000})
+ .setChannelMasks(new int[] {AudioFormat.CHANNEL_OUT_5POINT1})
+ // The canonical channel index masks by channel count are given by the formula
+ // (1 << channelCount) - 1. See:
+ // https://developer.android.com/reference/android/media/AudioFormat#channelMask
+ .setChannelIndexMasks(new int[] {0x3F})
+ .setEncapsulationType(AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE)
+ .build();
+
+ assertThat(audioProfile.getFormat()).isEqualTo(AudioFormat.ENCODING_AC3);
+ int[] sampleRates = audioProfile.getSampleRates();
+ assertThat(sampleRates).hasLength(1);
+ assertThat(sampleRates[0]).isEqualTo(48_000);
+ int[] channelMasks = audioProfile.getChannelMasks();
+ assertThat(channelMasks).hasLength(1);
+ assertThat(channelMasks[0]).isEqualTo(AudioFormat.CHANNEL_OUT_5POINT1);
+ int[] channelIndexMasks = audioProfile.getChannelIndexMasks();
+ assertThat(channelIndexMasks).hasLength(1);
+ assertThat(channelIndexMasks[0]).isEqualTo(0x3F);
+ assertThat(audioProfile.getEncapsulationType())
+ .isEqualTo(AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE);
+ }
+
+ @Test
+ public void buildAudioProfile_withDefaultValues_buildsExpectedObject() {
+ AudioProfile audioProfile = AudioProfileBuilder.newBuilder().build();
+
+ assertThat(audioProfile.getFormat()).isEqualTo(AudioFormat.ENCODING_PCM_16BIT);
+ assertThat(audioProfile.getSampleRates()).isEqualTo(new int[] {48000});
+ assertThat(audioProfile.getChannelMasks())
+ .isEqualTo(new int[] {AudioFormat.CHANNEL_OUT_STEREO});
+ assertThat(audioProfile.getChannelIndexMasks()).isEqualTo(new int[0]);
+ assertThat(audioProfile.getEncapsulationType())
+ .isEqualTo(AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE);
+ }
+
+ @Test
+ public void buildAudioProfile_withSetValues_buildsExpectedObject() {
+ AudioProfile audioProfile =
+ AudioProfileBuilder.newBuilder()
+ .setFormat(AudioFormat.ENCODING_PCM_32BIT)
+ .setSamplingRates(new int[] {96000})
+ .setChannelMasks(new int[] {AudioFormat.CHANNEL_OUT_QUAD})
+ .setChannelIndexMasks(new int[] {0x5})
+ .setEncapsulationType(AudioProfile.AUDIO_ENCAPSULATION_TYPE_IEC61937)
+ .build();
+
+ assertThat(audioProfile.getFormat()).isEqualTo(AudioFormat.ENCODING_PCM_32BIT);
+ assertThat(audioProfile.getSampleRates()).isEqualTo(new int[] {96000});
+ assertThat(audioProfile.getChannelMasks()).isEqualTo(new int[] {AudioFormat.CHANNEL_OUT_QUAD});
+ assertThat(audioProfile.getChannelIndexMasks()).isEqualTo(new int[] {0x5});
+ assertThat(audioProfile.getEncapsulationType())
+ .isEqualTo(AudioProfile.AUDIO_ENCAPSULATION_TYPE_IEC61937);
+ }
+}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/CellIdentityLteBuilderTest.java b/robolectric/src/test/java/org/robolectric/shadows/CellIdentityLteBuilderTest.java
index 72887ddb5..0819f2b70 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/CellIdentityLteBuilderTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/CellIdentityLteBuilderTest.java
@@ -13,7 +13,6 @@ import org.robolectric.annotation.Config;
/** Test for {@link CellIdentityLteBuilder} */
@RunWith(AndroidJUnit4.class)
-@Config(minSdk = Build.VERSION_CODES.JELLY_BEAN_MR1)
public class CellIdentityLteBuilderTest {
private static final String MCC = "310";
@@ -38,7 +37,7 @@ public class CellIdentityLteBuilderTest {
}
@Test
- @Config(minSdk = Build.VERSION_CODES.JELLY_BEAN_MR1, maxSdk = Build.VERSION_CODES.M)
+ @Config(maxSdk = Build.VERSION_CODES.M)
public void build_sdkJtoM() {
CellIdentityLte cellIdentity = getCellIdentityLte();
diff --git a/robolectric/src/test/java/org/robolectric/shadows/CellInfoLteBuilderTest.java b/robolectric/src/test/java/org/robolectric/shadows/CellInfoLteBuilderTest.java
index f61abad90..55abc5cbb 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/CellInfoLteBuilderTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/CellInfoLteBuilderTest.java
@@ -14,7 +14,6 @@ import org.robolectric.annotation.Config;
/** Test for {@link CellInfoLteBuilder} */
@RunWith(AndroidJUnit4.class)
-@Config(minSdk = Build.VERSION_CODES.JELLY_BEAN_MR1)
public class CellInfoLteBuilderTest {
private static final boolean REGISTERED = false;
@@ -37,7 +36,7 @@ public class CellInfoLteBuilderTest {
}
@Test
- @Config(minSdk = Build.VERSION_CODES.JELLY_BEAN_MR1, maxSdk = Build.VERSION_CODES.N_MR1)
+ @Config(maxSdk = Build.VERSION_CODES.N_MR1)
public void build_sdkJtoN() {
CellInfoLte cellInfo = getCellInfoLte();
diff --git a/robolectric/src/test/java/org/robolectric/shadows/CellSignalStrengthLteBuilderTest.java b/robolectric/src/test/java/org/robolectric/shadows/CellSignalStrengthLteBuilderTest.java
index cfd3bbeeb..5637b9336 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/CellSignalStrengthLteBuilderTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/CellSignalStrengthLteBuilderTest.java
@@ -12,7 +12,6 @@ import org.robolectric.annotation.Config;
/** Test for {@link CellSignalStrengthLteBuilder} */
@RunWith(AndroidJUnit4.class)
-@Config(minSdk = Build.VERSION_CODES.JELLY_BEAN_MR1)
public class CellSignalStrengthLteBuilderTest {
// The platform enforces that some of these values are within a certain range - otherwise, it will
@@ -35,7 +34,7 @@ public class CellSignalStrengthLteBuilderTest {
}
@Test
- @Config(minSdk = Build.VERSION_CODES.JELLY_BEAN_MR1, maxSdk = Build.VERSION_CODES.N_MR1)
+ @Config(maxSdk = Build.VERSION_CODES.N_MR1)
public void build_sdkJtoN() {
CellSignalStrengthLte cellSignalStrength = getCellSignalStrength();
diff --git a/robolectric/src/test/java/org/robolectric/shadows/HealthStatsBuilderTest.java b/robolectric/src/test/java/org/robolectric/shadows/HealthStatsBuilderTest.java
new file mode 100644
index 000000000..c860357c8
--- /dev/null
+++ b/robolectric/src/test/java/org/robolectric/shadows/HealthStatsBuilderTest.java
@@ -0,0 +1,175 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.N;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.health.HealthStats;
+import android.os.health.TimerStat;
+import android.util.ArrayMap;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.common.primitives.Ints;
+import java.util.HashSet;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+@RunWith(AndroidJUnit4.class)
+@Config(minSdk = N)
+public class HealthStatsBuilderTest {
+
+ private static final int KEY_1 = 867;
+ private static final int KEY_2 = 5309;
+
+ private static final TimerStat TIMER_STAT_1 = new TimerStat(42, 64);
+ private static final TimerStat TIMER_STAT_2 = new TimerStat(5, 7);
+
+ private static final long MEASUREMENT_1 = 13;
+ private static final long MEASUREMENT_2 = 17;
+
+ private static final String MAP_STRING_1 = "map_string_1";
+ private static final String MAP_STRING_2 = "map_string_2";
+
+ private static final ArrayMap<String, Long> MEASUREMENTS_MAP = new ArrayMap<>();
+ private static final long MEASUREMENTS_VALUE_1 = 19;
+ private static final long MEASUREMENTS_VALUE_2 = 21;
+
+ private static final ArrayMap<String, HealthStats> STATS_MAP = new ArrayMap<>();
+ private static final HealthStats STATS_VALUE_1 =
+ HealthStatsBuilder.newBuilder().setDataType("23").build();
+ private static final HealthStats STATS_VALUE_2 =
+ HealthStatsBuilder.newBuilder().setDataType("27").build();
+
+ private static final ArrayMap<String, TimerStat> TIMERS_MAP = new ArrayMap<>();
+ private static final TimerStat TIMERS_VALUE_1 = new TimerStat(29, 31);
+ private static final TimerStat TIMERS_VALUE_2 = new TimerStat(37, 41);
+
+ static {
+ MEASUREMENTS_MAP.put(MAP_STRING_1, MEASUREMENTS_VALUE_1);
+ MEASUREMENTS_MAP.put(MAP_STRING_2, MEASUREMENTS_VALUE_2);
+
+ STATS_MAP.put(MAP_STRING_1, STATS_VALUE_1);
+ STATS_MAP.put(MAP_STRING_2, STATS_VALUE_2);
+
+ TIMERS_MAP.put(MAP_STRING_1, TIMERS_VALUE_1);
+ TIMERS_MAP.put(MAP_STRING_2, TIMERS_VALUE_2);
+ }
+
+ @Test
+ public void emptyBuilder_isEmpty() {
+ HealthStats stats = HealthStatsBuilder.newBuilder().build();
+
+ assertThat(stats.getDataType()).isNull();
+ assertThat(stats.getMeasurementKeyCount()).isEqualTo(0);
+ assertThat(stats.getMeasurementsKeyCount()).isEqualTo(0);
+ assertThat(stats.getStatsKeyCount()).isEqualTo(0);
+ assertThat(stats.getTimerKeyCount()).isEqualTo(0);
+ assertThat(stats.getTimersKeyCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void setEverything_everythingSetsCorrectly() {
+ HealthStats stats =
+ HealthStatsBuilder.newBuilder()
+ .setDataType("arbitrary_data_type")
+ .addTimerStat(KEY_1, TIMER_STAT_1)
+ .addTimerStat(KEY_2, TIMER_STAT_2)
+ .addMeasurement(KEY_1, MEASUREMENT_1)
+ .addMeasurement(KEY_2, MEASUREMENT_2)
+ .addStats(KEY_1, STATS_MAP)
+ .addTimers(KEY_1, TIMERS_MAP)
+ .addMeasurements(KEY_1, MEASUREMENTS_MAP)
+ .build();
+
+ assertThat(stats.getDataType()).isEqualTo("arbitrary_data_type");
+
+ assertThat(stats.getTimerKeyCount()).isEqualTo(2);
+ assertThat(stats.hasTimer(KEY_1)).isTrue();
+ assertThat(stats.getTimerCount(KEY_1)).isEqualTo(TIMER_STAT_1.getCount());
+ assertThat(stats.getTimerTime(KEY_1)).isEqualTo(TIMER_STAT_1.getTime());
+ compareTimers(stats.getTimer(KEY_1), TIMER_STAT_1);
+ assertThat(stats.hasTimer(KEY_2)).isTrue();
+ assertThat(stats.getTimerCount(KEY_2)).isEqualTo(TIMER_STAT_2.getCount());
+ assertThat(stats.getTimerTime(KEY_2)).isEqualTo(TIMER_STAT_2.getTime());
+ compareTimers(stats.getTimer(KEY_2), TIMER_STAT_2);
+
+ assertThat(stats.getMeasurementKeyCount()).isEqualTo(2);
+ assertThat(stats.hasMeasurement(KEY_1)).isTrue();
+ assertThat(stats.getMeasurement(KEY_1)).isEqualTo(MEASUREMENT_1);
+ assertThat(stats.hasMeasurement(KEY_2)).isTrue();
+ assertThat(stats.getMeasurement(KEY_2)).isEqualTo(MEASUREMENT_2);
+
+ assertThat(stats.getMeasurementsKeyCount()).isEqualTo(1);
+ assertThat(stats.hasMeasurements(KEY_1)).isTrue();
+ assertThat(stats.getMeasurements(KEY_1)).isEqualTo(MEASUREMENTS_MAP);
+
+ assertThat(stats.getStatsKeyCount()).isEqualTo(1);
+ assertThat(stats.hasStats(KEY_1)).isTrue();
+ assertThat(stats.getStats(KEY_1)).isEqualTo(STATS_MAP);
+
+ assertThat(stats.getTimersKeyCount()).isEqualTo(1);
+ assertThat(stats.hasTimers(KEY_1)).isTrue();
+ assertThat(stats.getTimers(KEY_1)).isEqualTo(TIMERS_MAP);
+ }
+
+ @Test
+ public void healthStats_keysAreIterable() {
+ HealthStats stats =
+ HealthStatsBuilder.newBuilder()
+ .setDataType("arbitrary_data_type")
+ .addMeasurement(KEY_1, MEASUREMENT_1)
+ .addMeasurement(KEY_2, MEASUREMENT_2)
+ .build();
+
+ assertThat(stats.getMeasurementKeyCount()).isEqualTo(2);
+ for (int i = 0; i < stats.getMeasurementKeyCount(); i++) {
+ int key = stats.getMeasurementKeyAt(i);
+ switch (key) {
+ case KEY_1:
+ assertThat(stats.getMeasurement(key)).isEqualTo(MEASUREMENT_1);
+ break;
+ case KEY_2:
+ assertThat(stats.getMeasurement(key)).isEqualTo(MEASUREMENT_2);
+ break;
+ default:
+ throw new IllegalStateException("Unexpected HealthStats key");
+ }
+ }
+ }
+
+ @Test
+ public void toSortedIntArray_resultIsSorted() {
+ HashSet<Integer> set = new HashSet<>();
+ set.add(1);
+ set.add(2);
+ set.add(3);
+ ReverseIteratingSet reversedSet = new ReverseIteratingSet(set);
+
+ int[] setAsSortedArray = HealthStatsBuilder.toSortedIntArray(set);
+ int[] reversedSetAsSortedArray = HealthStatsBuilder.toSortedIntArray(reversedSet);
+
+ assertThat(Ints.asList(setAsSortedArray)).isInStrictOrder();
+ assertThat(Ints.asList(reversedSetAsSortedArray)).isInStrictOrder();
+ }
+
+ private final void compareTimers(TimerStat timer1, TimerStat timer2) {
+ assertThat(timer1.getCount()).isEqualTo(timer2.getCount());
+ assertThat(timer1.getTime()).isEqualTo(timer2.getTime());
+ }
+
+ // Identical to HashSet<Integer>, except that the result of toArray() is reversed.
+ private static final class ReverseIteratingSet extends HashSet<Integer> {
+ public ReverseIteratingSet(HashSet<Integer> c) {
+ super(c);
+ }
+
+ @Override
+ public Object[] toArray() {
+ Object[] forward = super.toArray();
+ Object[] backward = new Object[forward.length];
+ for (int i = 0; i < forward.length; i++) {
+ backward[i] = forward[forward.length - 1 - i];
+ }
+ return backward;
+ }
+ }
+}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/NetworkSpecifierFactoryTest.java b/robolectric/src/test/java/org/robolectric/shadows/NetworkSpecifierFactoryTest.java
new file mode 100644
index 000000000..319d3763c
--- /dev/null
+++ b/robolectric/src/test/java/org/robolectric/shadows/NetworkSpecifierFactoryTest.java
@@ -0,0 +1,26 @@
+package org.robolectric.shadows;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.net.StringNetworkSpecifier;
+import android.os.Build;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+@RunWith(AndroidJUnit4.class)
+@Config(minSdk = Build.VERSION_CODES.O)
+public final class NetworkSpecifierFactoryTest {
+
+ private static final String SUBSCRIPTION_ID_SPECIFIER = "1";
+ private static final String ETHERNET_SPECIFIER = "eth0";
+
+ @Test
+ public void newStringNetworkSpecifier() {
+ assertThat(NetworkSpecifierFactory.newStringNetworkSpecifier(SUBSCRIPTION_ID_SPECIFIER))
+ .isInstanceOf(StringNetworkSpecifier.class);
+ assertThat(NetworkSpecifierFactory.newStringNetworkSpecifier(ETHERNET_SPECIFIER))
+ .isInstanceOf(StringNetworkSpecifier.class);
+ }
+}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ResourceHelperTest.java b/robolectric/src/test/java/org/robolectric/shadows/ResourceHelperTest.java
index 9edaac17a..66762daca 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ResourceHelperTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ResourceHelperTest.java
@@ -10,10 +10,10 @@ import org.robolectric.annotation.Config;
/** Unit tests for {@link ResourceHelper}. */
@RunWith(AndroidJUnit4.class)
+@Config(sdk = Config.NEWEST_SDK)
public class ResourceHelperTest {
@Test
- @Config(sdk = Config.NEWEST_SDK)
public void parseFloatAttribute() {
TypedValue out = new TypedValue();
ResourceHelper.parseFloatAttribute(null, "0.16", out, false);
@@ -23,4 +23,42 @@ public class ResourceHelperTest {
ResourceHelper.parseFloatAttribute(null, ".16", out, false);
assertThat(out.getFloat()).isEqualTo(0.16f);
}
+
+ @Test
+ public void parseFloatAttribute_lengthEquals1000_parseSucceed() {
+ TypedValue out = new TypedValue();
+ boolean parseResult =
+ ResourceHelper.parseFloatAttribute(
+ null, generateTestFloatAttribute("0.16", 1000), out, false);
+ assertThat(parseResult).isTrue();
+ assertThat(out.getFloat()).isEqualTo(0.16f);
+ }
+
+ @Test
+ public void parseFloatAttribute_lengthLargerThan1000_returnsFalse() {
+ TypedValue out = new TypedValue();
+ boolean parseResult =
+ ResourceHelper.parseFloatAttribute(
+ null, generateTestFloatAttribute("0.17", 1001), out, false);
+ assertThat(parseResult).isFalse();
+ }
+
+ @Test
+ public void parseFloatAttribute_lengthLessThan1000_parseSucceed() {
+ TypedValue out = new TypedValue();
+ boolean parseResult =
+ ResourceHelper.parseFloatAttribute(
+ null, generateTestFloatAttribute("0.18", 999), out, false);
+ assertThat(parseResult).isTrue();
+ assertThat(out.getFloat()).isEqualTo(0.18f);
+ }
+
+ private static String generateTestFloatAttribute(String prefixAttribute, int length) {
+ StringBuilder builder = new StringBuilder(prefixAttribute);
+ int usedLength = builder.length();
+ for (int i = 0; i < length - usedLength; i++) {
+ builder.append("0");
+ }
+ return builder.toString();
+ }
}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowAccessibilityManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowAccessibilityManagerTest.java
index a3c471788..27f3fabd2 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowAccessibilityManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowAccessibilityManagerTest.java
@@ -1,7 +1,6 @@
package org.robolectric.shadows;
import static android.content.Context.ACCESSIBILITY_SERVICE;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.O_MR1;
import static android.os.Build.VERSION_CODES.Q;
@@ -189,7 +188,6 @@ public class ShadowAccessibilityManagerTest {
assertThat(accessibilityManager.removeAccessibilityStateChangeListener(null)).isFalse();
}
- @Config(minSdk = KITKAT)
@Test
public void setTouchExplorationEnabled_invokesCallbacks() {
AtomicBoolean enabled = new AtomicBoolean(false);
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowAccessibilityNodeInfoTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowAccessibilityNodeInfoTest.java
index 14c91f404..74bf81c9c 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowAccessibilityNodeInfoTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowAccessibilityNodeInfoTest.java
@@ -1,11 +1,12 @@
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.N;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.P;
import static android.os.Build.VERSION_CODES.Q;
+import static android.os.Build.VERSION_CODES.R;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
import static com.google.common.truth.Truth.assertThat;
import static org.robolectric.Shadows.shadowOf;
@@ -202,7 +203,6 @@ public class ShadowAccessibilityNodeInfoTest {
assertThat(node).isEqualTo(clone);
}
- @Config(minSdk = KITKAT)
@Test
public void shouldCloneExtrasCorrectly() {
node.getExtras().putString("key", "value");
@@ -257,6 +257,30 @@ public class ShadowAccessibilityNodeInfoTest {
}
@Test
+ @Config(minSdk = R)
+ public void clone_preservesStateDescription() {
+ String description = "description";
+ AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain();
+ node.setStateDescription(description);
+
+ AccessibilityNodeInfo clone = AccessibilityNodeInfo.obtain(node);
+
+ assertThat(clone.getStateDescription().toString()).isEqualTo(description);
+ }
+
+ @Test
+ @Config(minSdk = UPSIDE_DOWN_CAKE)
+ public void clone_preservesContainerTitle() {
+ String title = "container title";
+ AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain();
+ node.setContainerTitle(title);
+
+ AccessibilityNodeInfo clone = AccessibilityNodeInfo.obtain(node);
+
+ assertThat(clone.getContainerTitle().toString()).isEqualTo(title);
+ }
+
+ @Test
public void testGetBoundsInScreen() {
AccessibilityNodeInfo root = AccessibilityNodeInfo.obtain();
Rect expected = new Rect(0, 0, 100, 100);
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowAccountManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowAccountManagerTest.java
index 4f9f0b2ef..ffbc9c760 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowAccountManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowAccountManagerTest.java
@@ -1,10 +1,10 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1;
import static android.os.Build.VERSION_CODES.O;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
import static org.robolectric.Shadows.shadowOf;
import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
@@ -1051,7 +1051,6 @@ public class ShadowAccountManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR2)
public void getAccountsByTypeForPackage() {
Account[] accountsByTypeForPackage = am.getAccountsByTypeForPackage(null, "org.somepackage");
@@ -1103,14 +1102,33 @@ public class ShadowAccountManagerTest {
shadowOf(am).setAuthenticationErrorOnNextResponse(true);
- try {
- am.getAccountsByTypeAndFeatures(null, null, null, null).getResult();
- fail("should have thrown");
- } catch (AuthenticatorException expected) {
- // Expected
- }
+ assertThrows(
+ AuthenticatorException.class,
+ () ->
+ am.getAccountsByTypeAndFeatures(
+ /* type= */ null,
+ /* features= */ null,
+ /* callback= */ null,
+ /* handler= */ null)
+ .getResult());
+
+ assertThat(
+ am.getAccountsByTypeAndFeatures(
+ /* type= */ null,
+ /* features= */ null,
+ /* callback= */ null,
+ /* handler= */ null)
+ .getResult())
+ .isEmpty();
+ }
+
+ @Test
+ public void setSecurityErrorOnNextGetAccountsByTypeCall() {
+ shadowOf(am).setSecurityErrorOnNextGetAccountsByTypeCall(true);
+
+ assertThrows(SecurityException.class, () -> am.getAccountsByType(null));
- am.getAccountsByTypeAndFeatures(null, null, null, null).getResult();
+ assertThat(am.getAccountsByType(null)).isEmpty();
}
private static class TestAccountManagerCallback<T> implements AccountManagerCallback<T> {
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowActivityManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowActivityManagerTest.java
index 420123969..e2212f443 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowActivityManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowActivityManagerTest.java
@@ -4,8 +4,6 @@ import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREG
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.O;
@@ -161,7 +159,6 @@ public class ShadowActivityManagerTest {
}
@Test
- @Config(minSdk = KITKAT)
public void setIsLowRamDevice() {
shadowActivityManager.setIsLowRamDevice(true);
assertThat(activityManager.isLowRamDevice()).isTrue();
@@ -198,7 +195,6 @@ public class ShadowActivityManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void switchUser() {
shadowOf(application).setSystemService(Context.USER_SERVICE, userManager);
shadowOf(userManager).addUser(10, "secondary_user", 0);
@@ -215,13 +211,11 @@ public class ShadowActivityManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void getCurrentUser_default_returnZero() {
assertThat(ActivityManager.getCurrentUser()).isEqualTo(0);
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void getCurrentUser_nonDefault_returnValueSet() {
shadowOf(application).setSystemService(Context.USER_SERVICE, userManager);
shadowOf(userManager).addUser(10, "secondary_user", 0);
@@ -424,13 +418,11 @@ public class ShadowActivityManagerTest {
assertThat(activityManager.getDeviceConfigurationInfo()).isEqualTo(configurationInfo);
}
- @Config(minSdk = KITKAT)
@Test
public void isApplicationUserDataCleared_returnsDefaultFalse() {
assertThat(shadowActivityManager.isApplicationUserDataCleared()).isFalse();
}
- @Config(minSdk = KITKAT)
@Test
public void isApplicationUserDataCleared_returnsTrue() {
activityManager.clearApplicationUserData();
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowActivityTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowActivityTest.java
index 08b2a2060..387f0cfcc 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowActivityTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowActivityTest.java
@@ -1,8 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.N;
@@ -10,6 +7,7 @@ import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.O_MR1;
import static android.os.Build.VERSION_CODES.Q;
import static android.os.Build.VERSION_CODES.S;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
import static android.os.Looper.getMainLooper;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -50,6 +48,7 @@ import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.database.Cursor;
import android.database.MatrixCursor;
+import android.graphics.Color;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Build.VERSION_CODES;
@@ -178,7 +177,6 @@ public class ShadowActivityTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void shouldReportDestroyedStatus() {
try (ActivityController<DialogCreatingActivity> controller =
Robolectric.buildActivity(DialogCreatingActivity.class)) {
@@ -514,7 +512,6 @@ public class ShadowActivityTest {
}
@Test
- @Config(minSdk = JELLY_BEAN)
public void shouldCallFinishOnFinishAffinity() {
Activity activity = new Activity();
activity.finishAffinity();
@@ -1018,6 +1015,110 @@ public class ShadowActivityTest {
}
@Test
+ @Config(minSdk = UPSIDE_DOWN_CAKE)
+ public void getOverriddenActivityTransitionOpen_withoutBackgroundColor() {
+ try (ActivityController<Activity> controller = buildActivity(Activity.class).setup()) {
+ Activity activity = controller.get();
+ activity.overrideActivityTransition(Activity.OVERRIDE_TRANSITION_OPEN, 15, 2);
+ ShadowActivity.OverriddenActivityTransition overriddenActivityTransition =
+ shadowOf(activity).getOverriddenActivityTransition(Activity.OVERRIDE_TRANSITION_OPEN);
+
+ assertThat(overriddenActivityTransition).isNotNull();
+ assertThat(overriddenActivityTransition.enterAnim).isEqualTo(15);
+ assertThat(overriddenActivityTransition.exitAnim).isEqualTo(2);
+ assertThat(overriddenActivityTransition.backgroundColor).isEqualTo(Color.TRANSPARENT);
+ }
+ }
+
+ @Test
+ @Config(minSdk = UPSIDE_DOWN_CAKE)
+ public void getOverriddenActivityTransitionClose_withoutBackgroundColor() {
+ try (ActivityController<Activity> controller = buildActivity(Activity.class).setup()) {
+ Activity activity = controller.get();
+ ShadowActivity shadowActivity = shadowOf(activity);
+ activity.overrideActivityTransition(Activity.OVERRIDE_TRANSITION_CLOSE, 15, 2);
+ ShadowActivity.OverriddenActivityTransition overriddenActivityTransition =
+ shadowActivity.getOverriddenActivityTransition(Activity.OVERRIDE_TRANSITION_CLOSE);
+
+ assertThat(overriddenActivityTransition).isNotNull();
+ assertThat(overriddenActivityTransition.enterAnim).isEqualTo(15);
+ assertThat(overriddenActivityTransition.exitAnim).isEqualTo(2);
+ assertThat(overriddenActivityTransition.backgroundColor).isEqualTo(Color.TRANSPARENT);
+ }
+ }
+
+ @Test
+ @Config(minSdk = UPSIDE_DOWN_CAKE)
+ public void getOverriddenActivityTransitionOpen_withBackgroundColor() {
+ try (ActivityController<Activity> controller = buildActivity(Activity.class).setup()) {
+ Activity activity = controller.get();
+ activity.overrideActivityTransition(Activity.OVERRIDE_TRANSITION_OPEN, 33, 12, Color.RED);
+ ShadowActivity.OverriddenActivityTransition overriddenActivityTransition =
+ shadowOf(activity).getOverriddenActivityTransition(Activity.OVERRIDE_TRANSITION_OPEN);
+
+ assertThat(overriddenActivityTransition).isNotNull();
+ assertThat(overriddenActivityTransition.enterAnim).isEqualTo(33);
+ assertThat(overriddenActivityTransition.exitAnim).isEqualTo(12);
+ assertThat(overriddenActivityTransition.backgroundColor).isEqualTo(Color.RED);
+ }
+ }
+
+ @Test
+ @Config(minSdk = UPSIDE_DOWN_CAKE)
+ public void getOverriddenActivityTransitionClose_withBackgroundColor() {
+ try (ActivityController<Activity> controller = buildActivity(Activity.class).setup()) {
+ Activity activity = controller.get();
+ ShadowActivity shadowActivity = shadowOf(activity);
+ activity.overrideActivityTransition(Activity.OVERRIDE_TRANSITION_CLOSE, 33, 12, Color.RED);
+ ShadowActivity.OverriddenActivityTransition overriddenActivityTransition =
+ shadowActivity.getOverriddenActivityTransition(Activity.OVERRIDE_TRANSITION_CLOSE);
+
+ assertThat(overriddenActivityTransition).isNotNull();
+ assertThat(overriddenActivityTransition.enterAnim).isEqualTo(33);
+ assertThat(overriddenActivityTransition.exitAnim).isEqualTo(12);
+ assertThat(overriddenActivityTransition.backgroundColor).isEqualTo(Color.RED);
+ }
+ }
+
+ @Test
+ @Config(minSdk = UPSIDE_DOWN_CAKE)
+ public void getOverriddenActivityTransition_invalidType() {
+ try (ActivityController<Activity> controller = buildActivity(Activity.class).setup()) {
+ Activity activity = controller.get();
+ activity.overrideActivityTransition(Activity.OVERRIDE_TRANSITION_CLOSE, 33, 12, Color.RED);
+ ShadowActivity.OverriddenActivityTransition overriddenActivityTransition =
+ shadowOf(activity).getOverriddenActivityTransition(-1);
+ assertThat(overriddenActivityTransition).isNull();
+ }
+ }
+
+ @Test
+ @Config(minSdk = UPSIDE_DOWN_CAKE)
+ public void getOverriddenActivityTransition_beforeOverridingOrAfterClearing() {
+ try (ActivityController<Activity> controller = buildActivity(Activity.class).setup()) {
+ Activity activity = controller.get();
+ ShadowActivity shadowActivity = shadowOf(activity);
+
+ assertThat(shadowActivity.getOverriddenActivityTransition(Activity.OVERRIDE_TRANSITION_OPEN))
+ .isNull();
+
+ assertThat(shadowActivity.getOverriddenActivityTransition(Activity.OVERRIDE_TRANSITION_CLOSE))
+ .isNull();
+
+ activity.overrideActivityTransition(Activity.OVERRIDE_TRANSITION_OPEN, 12, 33);
+ activity.overrideActivityTransition(Activity.OVERRIDE_TRANSITION_CLOSE, 33, 12);
+ activity.clearOverrideActivityTransition(Activity.OVERRIDE_TRANSITION_OPEN);
+ activity.clearOverrideActivityTransition(Activity.OVERRIDE_TRANSITION_CLOSE);
+
+ assertThat(shadowActivity.getOverriddenActivityTransition(Activity.OVERRIDE_TRANSITION_OPEN))
+ .isNull();
+
+ assertThat(shadowActivity.getOverriddenActivityTransition(Activity.OVERRIDE_TRANSITION_CLOSE))
+ .isNull();
+ }
+ }
+
+ @Test
public void getActionBar_shouldWorkIfActivityHasAnAppropriateTheme() {
try (ActivityController<ActionBarThemedActivity> controller =
Robolectric.buildActivity(ActionBarThemedActivity.class)) {
@@ -1302,7 +1403,6 @@ public class ShadowActivityTest {
}
@Test
- @Config(minSdk = KITKAT)
public void reportFullyDrawn_reported() {
Activity activity = Robolectric.setupActivity(Activity.class);
activity.reportFullyDrawn();
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowAlarmManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowAlarmManagerTest.java
index 7a6ab89af..a2ae9222b 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowAlarmManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowAlarmManagerTest.java
@@ -154,7 +154,6 @@ public class ShadowAlarmManagerTest {
verify(onFire, times(2)).run();
}
- @Config(minSdk = VERSION_CODES.KITKAT)
@Test
public void setWindow_pendingIntent() {
Runnable onFire = mock(Runnable.class);
@@ -222,7 +221,6 @@ public class ShadowAlarmManagerTest {
verify(onFire).onAlarm();
}
- @Config(minSdk = VERSION_CODES.KITKAT)
@Test
public void setExact_pendingIntent() {
Runnable onFire = mock(Runnable.class);
@@ -282,7 +280,6 @@ public class ShadowAlarmManagerTest {
}
}
- @Config(minSdk = VERSION_CODES.KITKAT)
@Test
public void set_pendingIntent_workSource() {
Runnable onFire = mock(Runnable.class);
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowAmbientDisplayConfigurationTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowAmbientDisplayConfigurationTest.java
new file mode 100644
index 000000000..20632d2d7
--- /dev/null
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowAmbientDisplayConfigurationTest.java
@@ -0,0 +1,215 @@
+package org.robolectric.shadows;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.Build.VERSION_CODES;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+import org.robolectric.util.ReflectionHelpers.ClassParameter;
+
+/** Tests for {@link ShadowAmbientDisplayConfiguration}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(minSdk = VERSION_CODES.R)
+public final class ShadowAmbientDisplayConfigurationTest {
+
+ private Object instanceAmbientDisplayConfiguration;
+
+ @Before
+ public void setUp() throws Exception {
+ instanceAmbientDisplayConfiguration =
+ ReflectionHelpers.callConstructor(
+ Class.forName("android.hardware.display.AmbientDisplayConfiguration"),
+ ClassParameter.from(Context.class, RuntimeEnvironment.getApplication()));
+ }
+
+ @Test
+ public void ambientDisplayComponent_shouldReturnNullByDefault() throws Exception {
+ String component =
+ ReflectionHelpers.callInstanceMethod(
+ instanceAmbientDisplayConfiguration, "ambientDisplayComponent");
+ assertThat(component).isNull();
+ }
+
+ @Test
+ public void ambientDisplayComponent_whenValidDozeComponentIsSet_shouldReturnDozeComponent()
+ throws Exception {
+ ShadowAmbientDisplayConfiguration.setDozeComponent(
+ "com.google.android.aod/.TestDozeAlwaysOnDisplay");
+
+ String component =
+ ReflectionHelpers.callInstanceMethod(
+ instanceAmbientDisplayConfiguration, "ambientDisplayComponent");
+ assertThat(component).isEqualTo("com.google.android.aod/.TestDozeAlwaysOnDisplay");
+ }
+
+ @Test
+ public void ambientDisplayAvailable_whenValidDozeComponentIsSet_shouldReturnTrue()
+ throws Exception {
+ ShadowAmbientDisplayConfiguration.setDozeComponent(
+ "com.google.android.aod/.TestDozeAlwaysOnDisplay");
+
+ assertThat(
+ (Boolean)
+ ReflectionHelpers.callInstanceMethod(
+ instanceAmbientDisplayConfiguration, "ambientDisplayAvailable"))
+ .isTrue();
+ }
+
+ @Test
+ public void ambientDisplayAvailable_whenInvalidDozeComponentIsSet_shouldReturnFalse()
+ throws Exception {
+ ShadowAmbientDisplayConfiguration.setDozeComponent("");
+
+ assertThat(
+ (Boolean)
+ ReflectionHelpers.callInstanceMethod(
+ instanceAmbientDisplayConfiguration, "ambientDisplayAvailable"))
+ .isFalse();
+ }
+
+ @Test
+ public void
+ alwaysOnDisplayAvailable_whenOverrideDozeAlwaysOnDisplayAvailableStateToTrue_shouldReturnTrue()
+ throws Exception {
+ ShadowAmbientDisplayConfiguration.setDozeAlwaysOnDisplayAvailable(
+ /* dozeAlwaysOnDisplayAvailable= */ true);
+
+ assertThat(
+ (Boolean)
+ ReflectionHelpers.callInstanceMethod(
+ instanceAmbientDisplayConfiguration, "alwaysOnDisplayAvailable"))
+ .isTrue();
+ }
+
+ @Test
+ public void
+ alwaysOnDisplayAvailable_whenOverrideDozeAlwaysOnDisplayAvailableStateToFalse_shouldReturnFalse()
+ throws Exception {
+ ShadowAmbientDisplayConfiguration.setDozeAlwaysOnDisplayAvailable(
+ /* dozeAlwaysOnDisplayAvailable= */ false);
+
+ assertThat(
+ (Boolean)
+ ReflectionHelpers.callInstanceMethod(
+ instanceAmbientDisplayConfiguration, "alwaysOnDisplayAvailable"))
+ .isFalse();
+ }
+
+ @Test
+ public void
+ alwaysOnDisplayDebuggingEnabled_whenBothDebuggableAndAodSystemPropertyAreSet_shouldReturnTrue()
+ throws Exception {
+ ShadowSystemProperties.override("ro.debuggable", "1");
+ ShadowSystemProperties.override("debug.doze.aod", "true");
+
+ assertThat(
+ (Boolean)
+ ReflectionHelpers.callInstanceMethod(
+ instanceAmbientDisplayConfiguration, "alwaysOnDisplayDebuggingEnabled"))
+ .isTrue();
+ }
+
+ @Test
+ public void
+ alwaysOnDisplayDebuggingEnabled_whenOnlyDebuggableSystemPropertyIsSet_shouldReturnFalse()
+ throws Exception {
+ ShadowSystemProperties.override("ro.debuggable", "1");
+
+ assertThat(
+ (Boolean)
+ ReflectionHelpers.callInstanceMethod(
+ instanceAmbientDisplayConfiguration, "alwaysOnDisplayDebuggingEnabled"))
+ .isFalse();
+ }
+
+ @Test
+ public void alwaysOnDisplayDebuggingEnabled_whenOnlyAodSystemPropertyIsSet_shouldReturnFalse()
+ throws Exception {
+ ShadowSystemProperties.override("debug.doze.aod", "true");
+
+ assertThat(
+ (Boolean)
+ ReflectionHelpers.callInstanceMethod(
+ instanceAmbientDisplayConfiguration, "alwaysOnDisplayDebuggingEnabled"))
+ .isFalse();
+ }
+
+ @Test
+ public void
+ alwaysOnAvailable_whenValidSystemPropertiesAndValidDozeComponentAreSet_shouldReturnTrue()
+ throws Exception {
+ ShadowSystemProperties.override("ro.debuggable", "1");
+ ShadowSystemProperties.override("debug.doze.aod", "true");
+
+ ShadowAmbientDisplayConfiguration.setDozeAlwaysOnDisplayAvailable(
+ /* dozeAlwaysOnDisplayAvailable= */ false);
+ ShadowAmbientDisplayConfiguration.setDozeComponent(
+ "com.google.android.aod/.TestDozeAlwaysOnDisplay");
+
+ assertThat(
+ (Boolean)
+ ReflectionHelpers.callInstanceMethod(
+ instanceAmbientDisplayConfiguration, "alwaysOnAvailable"))
+ .isTrue();
+ }
+
+ @Test
+ public void
+ alwaysOnAvailable_whenOverrideDozeAlwaysOnDisplayAvailableStateAndValidDozeComponentAreSet_shouldReturnTrue()
+ throws Exception {
+ ShadowSystemProperties.override("ro.debuggable", "0");
+ ShadowSystemProperties.override("debug.doze.aod", "false");
+
+ ShadowAmbientDisplayConfiguration.setDozeAlwaysOnDisplayAvailable(
+ /* dozeAlwaysOnDisplayAvailable= */ true);
+ ShadowAmbientDisplayConfiguration.setDozeComponent(
+ "com.google.android.aod/.TestDozeAlwaysOnDisplay");
+
+ assertThat(
+ (Boolean)
+ ReflectionHelpers.callInstanceMethod(
+ instanceAmbientDisplayConfiguration, "alwaysOnAvailable"))
+ .isTrue();
+ }
+
+ @Test
+ public void alwaysOnAvailable_whenInvalidDozeComponentIsSet_shouldReturnFalse() throws Exception {
+ ShadowSystemProperties.override("ro.debuggable", "1");
+ ShadowSystemProperties.override("debug.doze.aod", "true");
+
+ ShadowAmbientDisplayConfiguration.setDozeAlwaysOnDisplayAvailable(
+ /* dozeAlwaysOnDisplayAvailable= */ true);
+ ShadowAmbientDisplayConfiguration.setDozeComponent("");
+
+ assertThat(
+ (Boolean)
+ ReflectionHelpers.callInstanceMethod(
+ instanceAmbientDisplayConfiguration, "alwaysOnAvailable"))
+ .isFalse();
+ }
+
+ @Test
+ public void
+ alwaysOnAvailable_whenInvalidSystemPropertiesAreSetAndOverrideDozeAlwaysOnDisplayAvailableStateToFalse_shouldReturnFalse()
+ throws Exception {
+ ShadowSystemProperties.override("ro.debuggable", "0");
+ ShadowSystemProperties.override("debug.doze.aod", "false");
+
+ ShadowAmbientDisplayConfiguration.setDozeAlwaysOnDisplayAvailable(
+ /* dozeAlwaysOnDisplayAvailable= */ false);
+ ShadowAmbientDisplayConfiguration.setDozeComponent(
+ "com.google.android.aod/.TestDozeAlwaysOnDisplay");
+
+ assertThat(
+ (Boolean)
+ ReflectionHelpers.callInstanceMethod(
+ instanceAmbientDisplayConfiguration, "alwaysOnAvailable"))
+ .isFalse();
+ }
+}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowAppOpsManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowAppOpsManagerTest.java
index d94c27568..168e03fa1 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowAppOpsManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowAppOpsManagerTest.java
@@ -15,7 +15,6 @@ import static android.app.AppOpsManager.OP_FINE_LOCATION;
import static android.app.AppOpsManager.OP_GPS;
import static android.app.AppOpsManager.OP_SEND_SMS;
import static android.app.AppOpsManager.OP_VIBRATE;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.Q;
import static android.os.Build.VERSION_CODES.R;
import static com.google.common.truth.Truth.assertThat;
@@ -53,7 +52,6 @@ import org.robolectric.util.ReflectionHelpers.ClassParameter;
/** Unit tests for {@link ShadowAppOpsManager}. */
@RunWith(AndroidJUnit4.class)
-@Config(minSdk = KITKAT)
public class ShadowAppOpsManagerTest {
private static final String PACKAGE_NAME1 = "com.company1.pkg1";
@@ -94,14 +92,12 @@ public class ShadowAppOpsManagerTest {
}
@Test
- @Config(minSdk = VERSION_CODES.KITKAT)
- public void checkOpNoThrow_noModeSet_atLeastKitKat_shouldReturnModeAllowed() {
+ public void checkOpNoThrow_noModeSet_shouldReturnModeAllowed() {
assertThat(appOps.checkOpNoThrow(/* op= */ 2, UID_1, PACKAGE_NAME1)).isEqualTo(MODE_ALLOWED);
}
@Test
- @Config(minSdk = VERSION_CODES.KITKAT)
- public void setMode_withModeDefault_atLeastKitKat_checkOpNoThrow_shouldReturnModeDefault() {
+ public void setMode_withModeDefault_checkOpNoThrow_shouldReturnModeDefault() {
appOps.setMode(/* op= */ 2, UID_1, PACKAGE_NAME1, MODE_DEFAULT);
assertThat(appOps.checkOpNoThrow(/* op= */ 2, UID_1, PACKAGE_NAME1)).isEqualTo(MODE_DEFAULT);
}
@@ -217,7 +213,6 @@ public class ShadowAppOpsManagerTest {
}
@Test
- @Config(minSdk = VERSION_CODES.KITKAT)
public void startStopWatchingMode() {
OnOpChangedListener callback = mock(OnOpChangedListener.class);
appOps.startWatchingMode(OPSTR_FINE_LOCATION, PACKAGE_NAME1, callback);
@@ -429,7 +424,7 @@ public class ShadowAppOpsManagerTest {
}
@Test
- @Config(minSdk = VERSION_CODES.KITKAT, maxSdk = VERSION_CODES.Q)
+ @Config(maxSdk = VERSION_CODES.Q)
public void startOpNoThrow_setModeAllowed() {
appOps.setMode(OP_FINE_LOCATION, UID_1, PACKAGE_NAME1, MODE_ALLOWED);
@@ -438,7 +433,7 @@ public class ShadowAppOpsManagerTest {
}
@Test
- @Config(minSdk = VERSION_CODES.KITKAT, maxSdk = VERSION_CODES.Q)
+ @Config(maxSdk = VERSION_CODES.Q)
public void startOpNoThrow_setModeErrored() {
appOps.setMode(OP_FINE_LOCATION, UID_1, PACKAGE_NAME1, MODE_ERRORED);
@@ -513,7 +508,6 @@ public class ShadowAppOpsManagerTest {
// check passes without exception
}
- @Config(minSdk = KITKAT)
@Test
public void getPackageForOps_setNone_getNull() {
int[] intNull = null;
@@ -521,7 +515,6 @@ public class ShadowAppOpsManagerTest {
assertThat(packageOps).isNull();
}
- @Config(minSdk = KITKAT)
@Test
public void getPackageForOps_setOne_getOne() {
String packageName = "com.android.package";
@@ -533,7 +526,6 @@ public class ShadowAppOpsManagerTest {
assertThat(containsPackageOpPair(packageOps, packageName, 0, MODE_ALLOWED)).isTrue();
}
- @Config(minSdk = KITKAT)
@Test
public void getPackageForOps_setMultiple_getMultiple() {
String packageName1 = "com.android.package";
@@ -557,7 +549,6 @@ public class ShadowAppOpsManagerTest {
assertThat(containsPackageOpPair(packageOps, packageName2, 0, MODE_ALLOWED)).isTrue();
}
- @Config(minSdk = KITKAT)
@Test
public void getPackageForOps_setMultiple_onlyGetThoseAskedFor() {
String packageName1 = "com.android.package";
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowAppWidgetManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowAppWidgetManagerTest.java
index 3774edb89..10e11912c 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowAppWidgetManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowAppWidgetManagerTest.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static android.os.Build.VERSION_CODES.L;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Looper.getMainLooper;
@@ -168,7 +167,6 @@ public class ShadowAppWidgetManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void bindAppWidgetIdIfAllowed_shouldSetOptionsBundle() {
ComponentName provider = new ComponentName("A", "B");
Bundle options = new Bundle();
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowApplicationTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowApplicationTest.java
index dd367590c..83e57227e 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowApplicationTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowApplicationTest.java
@@ -1,7 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-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;
@@ -128,7 +126,6 @@ public class ShadowApplicationTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void shouldProvideServicesIntroducedInJellyBeanMr1() throws Exception {
assertThat(context.getSystemService(Context.DISPLAY_SERVICE))
.isInstanceOf(android.hardware.display.DisplayManager.class);
@@ -136,7 +133,6 @@ public class ShadowApplicationTest {
}
@Test
- @Config(minSdk = KITKAT)
public void shouldProvideServicesIntroducedInKitKat() throws Exception {
assertThat(context.getSystemService(Context.PRINT_SERVICE)).isInstanceOf(PrintManager.class);
assertThat(context.getSystemService(Context.CAPTIONING_SERVICE))
@@ -187,7 +183,6 @@ public class ShadowApplicationTest {
}
@Test
- @Config(minSdk = KITKAT)
public void shouldCorrectlyInstantiatedAccessibilityService() throws Exception {
AccessibilityManager accessibilityManager =
(AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowAudioManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowAudioManagerTest.java
index 51757ceec..f35e38783 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowAudioManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowAudioManagerTest.java
@@ -1,6 +1,5 @@
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.M;
import static android.os.Build.VERSION_CODES.N;
@@ -10,8 +9,8 @@ import static android.os.Build.VERSION_CODES.Q;
import static android.os.Build.VERSION_CODES.R;
import static android.os.Build.VERSION_CODES.S;
import static android.os.Build.VERSION_CODES.TIRAMISU;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth8.assertThat;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
@@ -27,6 +26,7 @@ import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioManager.OnModeChangedListener;
import android.media.AudioPlaybackConfiguration;
+import android.media.AudioProfile;
import android.media.AudioRecordingConfiguration;
import android.media.AudioSystem;
import android.media.MediaRecorder.AudioSource;
@@ -173,6 +173,7 @@ public class ShadowAudioManagerTest {
case AudioManager.STREAM_RING:
case AudioManager.STREAM_SYSTEM:
case AudioManager.STREAM_VOICE_CALL:
+ case AudioManager.STREAM_ACCESSIBILITY:
assertThat(audioManager.getStreamMaxVolume(stream))
.isEqualTo(ShadowAudioManager.DEFAULT_MAX_VOLUME);
break;
@@ -250,6 +251,7 @@ public class ShadowAudioManagerTest {
case AudioManager.STREAM_RING:
case AudioManager.STREAM_SYSTEM:
case AudioManager.STREAM_VOICE_CALL:
+ case AudioManager.STREAM_ACCESSIBILITY:
assertThat(audioManager.getStreamVolume(stream))
.isEqualTo(ShadowAudioManager.DEFAULT_MAX_VOLUME);
break;
@@ -377,6 +379,24 @@ public class ShadowAudioManagerTest {
}
@Test
+ public void lockMode_locked_modeRemainsTheSame() {
+ shadowOf(audioManager).lockMode(true);
+
+ audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
+
+ assertThat(audioManager.getMode()).isEqualTo(AudioManager.MODE_NORMAL);
+ }
+
+ @Test
+ public void lockMode_notLocked_modeIsSet() {
+ shadowOf(audioManager).lockMode(false);
+
+ audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
+
+ assertThat(audioManager.getMode()).isEqualTo(AudioManager.MODE_IN_COMMUNICATION);
+ }
+
+ @Test
public void isSpeakerphoneOn_shouldReturnSpeakerphoneState() {
assertThat(audioManager.isSpeakerphoneOn()).isFalse();
audioManager.setSpeakerphoneOn(true);
@@ -462,6 +482,49 @@ public class ShadowAudioManagerTest {
}
@Test
+ @Config(minSdk = TIRAMISU)
+ public void getAudioDevicesForAttributes_returnsEmptyListByDefault() {
+ AudioAttributes movieAttribute =
+ new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_MOVIE).build();
+
+ assertThat(audioManager.getAudioDevicesForAttributes(movieAttribute)).isEmpty();
+ }
+
+ @Test
+ @Config(minSdk = TIRAMISU)
+ public void setAudioDevicesForAttributes_updatesAudioDevicesForAttributes() {
+ AudioAttributes movieAttribute =
+ new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_MOVIE).build();
+ ImmutableList<AudioDeviceInfo> newDevices =
+ ImmutableList.of(
+ AudioDeviceInfoBuilder.newBuilder()
+ .setType(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER)
+ .build());
+
+ shadowOf(audioManager).setAudioDevicesForAttributes(movieAttribute, newDevices);
+
+ assertThat(audioManager.getAudioDevicesForAttributes(movieAttribute)).isEqualTo(newDevices);
+ }
+
+ @Test
+ @Config(minSdk = TIRAMISU)
+ public void setAudioDevicesForAttributes_returnsEmptyListForOtherAttributes() {
+ AudioAttributes movieAttribute =
+ new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_MOVIE).build();
+ AudioAttributes otherAttribute =
+ new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build();
+ ImmutableList<AudioDeviceInfo> newDevices =
+ ImmutableList.of(
+ AudioDeviceInfoBuilder.newBuilder()
+ .setType(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER)
+ .build());
+
+ shadowOf(audioManager).setAudioDevicesForAttributes(movieAttribute, newDevices);
+
+ assertThat(audioManager.getAudioDevicesForAttributes(otherAttribute)).isEmpty();
+ }
+
+ @Test
@Config(minSdk = M)
public void registerAudioDeviceCallback_availableDevices_onAudioDevicesAddedCallback()
throws Exception {
@@ -848,7 +911,29 @@ public class ShadowAudioManagerTest {
@Config(minSdk = S)
public void setCommunicationDevice_updatesCommunicationDevice() throws Exception {
AudioDeviceInfo scoDevice = createAudioDevice(DEVICE_OUT_BLUETOOTH_SCO);
- shadowOf(audioManager).setCommunicationDevice(scoDevice);
+ audioManager.setCommunicationDevice(scoDevice);
+
+ assertThat(audioManager.getCommunicationDevice()).isEqualTo(scoDevice);
+ }
+
+ @Test
+ @Config(minSdk = S)
+ public void lockCommunicationDevice_locked_deviceIsNotSet() throws Exception {
+ AudioDeviceInfo scoDevice = createAudioDevice(DEVICE_OUT_BLUETOOTH_SCO);
+ shadowOf(audioManager).lockCommunicationDevice(true);
+
+ audioManager.setCommunicationDevice(scoDevice);
+
+ assertThat(audioManager.getCommunicationDevice()).isNull();
+ }
+
+ @Test
+ @Config(minSdk = S)
+ public void lockCommunicationDevice_notLocked_deviceIsSet() throws Exception {
+ AudioDeviceInfo scoDevice = createAudioDevice(DEVICE_OUT_BLUETOOTH_SCO);
+ shadowOf(audioManager).lockCommunicationDevice(false);
+
+ audioManager.setCommunicationDevice(scoDevice);
assertThat(audioManager.getCommunicationDevice()).isEqualTo(scoDevice);
}
@@ -857,10 +942,10 @@ public class ShadowAudioManagerTest {
@Config(minSdk = S)
public void clearCommunicationDevice_clearsCommunicationDevice() throws Exception {
AudioDeviceInfo scoDevice = createAudioDevice(DEVICE_OUT_BLUETOOTH_SCO);
- shadowOf(audioManager).setCommunicationDevice(scoDevice);
+ audioManager.setCommunicationDevice(scoDevice);
assertThat(audioManager.getCommunicationDevice()).isEqualTo(scoDevice);
- shadowOf(audioManager).clearCommunicationDevice();
+ audioManager.clearCommunicationDevice();
assertThat(audioManager.getCommunicationDevice()).isNull();
}
@@ -1351,7 +1436,6 @@ public class ShadowAudioManagerTest {
}
@Test
- @Config(minSdk = KITKAT)
public void dispatchMediaKeyEvent_recordsEvent() {
KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY);
@@ -1361,7 +1445,6 @@ public class ShadowAudioManagerTest {
}
@Test
- @Config(minSdk = KITKAT)
public void clearDispatchedMediaKeyEvents_clearsDispatchedEvents() {
audioManager.dispatchMediaKeyEvent(
new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY));
@@ -1371,6 +1454,93 @@ public class ShadowAudioManagerTest {
assertThat(shadowOf(audioManager).getDispatchedMediaKeyEvents()).isEmpty();
}
+ @Test
+ @Config(minSdk = UPSIDE_DOWN_CAKE)
+ public void setHotwordStreamSupportedWithLookbackAudio_updatesIsHotwordStreamSupported() {
+ assertThat(shadowOf(audioManager).isHotwordStreamSupported(/* lookbackAudio= */ true))
+ .isFalse();
+ assertThat(shadowOf(audioManager).isHotwordStreamSupported(/* lookbackAudio= */ false))
+ .isFalse();
+
+ shadowOf(audioManager)
+ .setHotwordStreamSupported(/* lookbackAudio= */ true, /* isSupported= */ true);
+
+ // isHotwordStreamSupported with lookbackAudio=true is set.
+ assertThat(shadowOf(audioManager).isHotwordStreamSupported(/* lookbackAudio= */ true)).isTrue();
+ // isHotwordStreamSupported with lookbackAudio=false is not set.
+ assertThat(shadowOf(audioManager).isHotwordStreamSupported(/* lookbackAudio= */ false))
+ .isFalse();
+ }
+
+ @Test
+ @Config(minSdk = UPSIDE_DOWN_CAKE)
+ public void setHotwordStreamSupportedWithoutLookbackAudio_updatesIsHotwordStreamSupported() {
+ assertThat(shadowOf(audioManager).isHotwordStreamSupported(/* lookbackAudio= */ false))
+ .isFalse();
+ assertThat(shadowOf(audioManager).isHotwordStreamSupported(/* lookbackAudio= */ true))
+ .isFalse();
+
+ shadowOf(audioManager)
+ .setHotwordStreamSupported(/* lookbackAudio= */ false, /* isSupported= */ true);
+
+ // isHotwordStreamSupported with lookbackAudio=false is set.
+ assertThat(shadowOf(audioManager).isHotwordStreamSupported(/* lookbackAudio= */ false))
+ .isTrue();
+ // isHotwordStreamSupported with lookbackAudio=true is not set.
+ assertThat(shadowOf(audioManager).isHotwordStreamSupported(/* lookbackAudio= */ true))
+ .isFalse();
+ }
+
+ @Test
+ @Config(minSdk = TIRAMISU)
+ public void getDirectProfilesForAttributes_returnsEmptyListByDefault() {
+ AudioAttributes audioAttributes =
+ new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_MEDIA)
+ .setContentType(AudioAttributes.CONTENT_TYPE_MOVIE)
+ .build();
+
+ assertThat(shadowOf(audioManager).getDirectProfilesForAttributes(audioAttributes)).isEmpty();
+ }
+
+ @Test
+ @Config(minSdk = TIRAMISU)
+ public void
+ addAndRemoveOutputDeviceWithDirectProfiles_updatesDirectProfilesForAttributes_notifiesCallback() {
+ AudioDeviceCallback callback = mock(AudioDeviceCallback.class);
+ audioManager.registerAudioDeviceCallback(callback, /* handler= */ null);
+ verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration
+ ImmutableList<AudioProfile> expectedProfiles =
+ ImmutableList.of(
+ AudioProfileBuilder.newBuilder()
+ .setFormat(AudioFormat.ENCODING_AC3)
+ .setSamplingRates(new int[] {48_000})
+ .setChannelMasks(new int[] {AudioFormat.CHANNEL_OUT_5POINT1})
+ .setEncapsulationType(AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE)
+ .build());
+ AudioDeviceInfo outputDevice =
+ AudioDeviceInfoBuilder.newBuilder()
+ .setType(AudioDeviceInfo.TYPE_HDMI)
+ .setProfiles(expectedProfiles)
+ .build();
+ AudioAttributes audioAttributes =
+ new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_MEDIA)
+ .setContentType(AudioAttributes.CONTENT_TYPE_MOVIE)
+ .build();
+
+ shadowOf(audioManager).addOutputDeviceWithDirectProfiles(outputDevice);
+
+ assertThat(shadowOf(audioManager).getDirectProfilesForAttributes(audioAttributes))
+ .isEqualTo(expectedProfiles);
+ verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {outputDevice});
+
+ shadowOf(audioManager).removeOutputDeviceWithDirectProfiles(outputDevice);
+
+ assertThat(shadowOf(audioManager).getDirectProfilesForAttributes(audioAttributes)).isEmpty();
+ verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {outputDevice});
+ }
+
private static AudioDeviceInfo createAudioDevice(int type) throws ReflectiveOperationException {
AudioDeviceInfo info = Shadow.newInstanceOf(AudioDeviceInfo.class);
Field portField = AudioDeviceInfo.class.getDeclaredField("mPort");
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowAudioTrackTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowAudioTrackTest.java
index e8869bd49..10dc3e33a 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowAudioTrackTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowAudioTrackTest.java
@@ -5,6 +5,7 @@ import static android.media.AudioTrack.WRITE_BLOCKING;
import static android.media.AudioTrack.WRITE_NON_BLOCKING;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.M;
+import static android.os.Build.VERSION_CODES.N;
import static android.os.Build.VERSION_CODES.Q;
import static android.os.Build.VERSION_CODES.R;
import static android.os.Build.VERSION_CODES.S;
@@ -13,13 +14,20 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import android.media.AudioAttributes;
+import android.media.AudioDeviceInfo;
import android.media.AudioFormat;
import android.media.AudioManager;
+import android.media.AudioRouting;
+import android.media.AudioRouting.OnRoutingChangedListener;
import android.media.AudioSystem;
import android.media.AudioTrack;
import android.media.PlaybackParams;
+import android.os.Handler;
+import android.os.Looper;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.nio.ByteBuffer;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
@@ -533,6 +541,117 @@ public class ShadowAudioTrackTest implements ShadowAudioTrack.OnAudioDataWritten
.isEqualTo(AudioTrack.ERROR_DEAD_OBJECT);
}
+ @Test
+ @Config(minSdk = N)
+ public void getRoutedDevice_withoutSetRoutedDevice_returnsNull() {
+ AudioTrack audioTrack = new AudioTrack.Builder().build();
+
+ assertThat(audioTrack.getRoutedDevice()).isNull();
+ }
+
+ @Test
+ @Config(minSdk = N)
+ public void getRoutedDevice_afterSetRoutedDevice_returnsRoutedDevice() {
+ AudioTrack audioTrack = new AudioTrack.Builder().build();
+ AudioDeviceInfo audioDeviceInfo =
+ AudioDeviceInfoBuilder.newBuilder().setType(AudioDeviceInfo.TYPE_HDMI).build();
+
+ ShadowAudioTrack.setRoutedDevice(audioDeviceInfo);
+
+ assertThat(audioTrack.getRoutedDevice()).isEqualTo(audioDeviceInfo);
+ }
+
+ @Test
+ @Config(minSdk = N)
+ public void addOnRoutingChangedListener_beforeSetRoutedDevice_listenerCalledOnceDeviceSet() {
+ AudioTrack audioTrack = new AudioTrack.Builder().build();
+ AudioDeviceInfo audioDeviceInfo =
+ AudioDeviceInfoBuilder.newBuilder().setType(AudioDeviceInfo.TYPE_HDMI).build();
+ AtomicReference<AudioRouting> listenerRouting = new AtomicReference<>();
+
+ audioTrack.addOnRoutingChangedListener(
+ (OnRoutingChangedListener) listenerRouting::set, new Handler(Looper.getMainLooper()));
+ ShadowLooper.idleMainLooper();
+
+ assertThat(listenerRouting.get()).isNull();
+
+ ShadowAudioTrack.setRoutedDevice(audioDeviceInfo);
+ ShadowLooper.idleMainLooper();
+
+ assertThat(listenerRouting.get()).isEqualTo(audioTrack);
+ assertThat(listenerRouting.get().getRoutedDevice()).isEqualTo(audioDeviceInfo);
+ }
+
+ @Test
+ @Config(minSdk = N)
+ public void
+ addOnRoutingChangedListener_afterSetRoutedDevice_listenerCalledImmediatelyAndWhenNewDeviceSet() {
+ AudioTrack audioTrack = new AudioTrack.Builder().build();
+ AudioDeviceInfo audioDeviceInfo1 =
+ AudioDeviceInfoBuilder.newBuilder().setType(AudioDeviceInfo.TYPE_HDMI).build();
+ AudioDeviceInfo audioDeviceInfo2 =
+ AudioDeviceInfoBuilder.newBuilder().setType(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP).build();
+ AtomicReference<AudioRouting> listenerRouting = new AtomicReference<>();
+
+ ShadowAudioTrack.setRoutedDevice(audioDeviceInfo1);
+ audioTrack.addOnRoutingChangedListener(
+ (OnRoutingChangedListener) listenerRouting::set, new Handler(Looper.getMainLooper()));
+ ShadowLooper.idleMainLooper();
+
+ assertThat(listenerRouting.get()).isEqualTo(audioTrack);
+ assertThat(listenerRouting.get().getRoutedDevice()).isEqualTo(audioDeviceInfo1);
+
+ ShadowAudioTrack.setRoutedDevice(audioDeviceInfo2);
+ ShadowLooper.idleMainLooper();
+
+ assertThat(listenerRouting.get()).isEqualTo(audioTrack);
+ assertThat(listenerRouting.get().getRoutedDevice()).isEqualTo(audioDeviceInfo2);
+ }
+
+ @Test
+ @Config(minSdk = N)
+ public void setRoutedDevice_toNull_listenerCalled() {
+ AudioTrack audioTrack = new AudioTrack.Builder().build();
+ AudioDeviceInfo audioDeviceInfo =
+ AudioDeviceInfoBuilder.newBuilder().setType(AudioDeviceInfo.TYPE_HDMI).build();
+ AtomicReference<AudioRouting> listenerRouting = new AtomicReference<>();
+ ShadowAudioTrack.setRoutedDevice(audioDeviceInfo);
+ audioTrack.addOnRoutingChangedListener(
+ (OnRoutingChangedListener) listenerRouting::set, new Handler(Looper.getMainLooper()));
+ ShadowLooper.idleMainLooper();
+
+ ShadowAudioTrack.setRoutedDevice(null);
+ ShadowLooper.idleMainLooper();
+
+ assertThat(listenerRouting.get()).isEqualTo(audioTrack);
+ assertThat(listenerRouting.get().getRoutedDevice()).isEqualTo(null);
+ }
+
+ @Test
+ @Config(minSdk = N)
+ public void removeOnRoutingChangedListener_noFurtherUpdatesSent() {
+ AudioTrack audioTrack = new AudioTrack.Builder().build();
+ AudioDeviceInfo audioDeviceInfo1 =
+ AudioDeviceInfoBuilder.newBuilder().setType(AudioDeviceInfo.TYPE_HDMI).build();
+ AudioDeviceInfo audioDeviceInfo2 =
+ AudioDeviceInfoBuilder.newBuilder().setType(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER).build();
+ ShadowAudioTrack.setRoutedDevice(audioDeviceInfo1);
+ AtomicInteger listenerCounter1 = new AtomicInteger();
+ AtomicInteger listenerCounter2 = new AtomicInteger();
+ OnRoutingChangedListener listener1 = routing -> listenerCounter1.incrementAndGet();
+ OnRoutingChangedListener listener2 = routing -> listenerCounter2.incrementAndGet();
+ audioTrack.addOnRoutingChangedListener(listener1, new Handler(Looper.getMainLooper()));
+ audioTrack.addOnRoutingChangedListener(listener2, new Handler(Looper.getMainLooper()));
+ ShadowLooper.idleMainLooper();
+
+ audioTrack.removeOnRoutingChangedListener(listener1);
+ ShadowAudioTrack.setRoutedDevice(audioDeviceInfo2);
+ ShadowLooper.idleMainLooper();
+
+ assertThat(listenerCounter1.get()).isEqualTo(1);
+ assertThat(listenerCounter2.get()).isEqualTo(2);
+ }
+
@Override
@Config(minSdk = Q)
public void onAudioDataWritten(
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBackupManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBackupManagerTest.java
index b7c8cfa9d..259173fbb 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBackupManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBackupManagerTest.java
@@ -2,31 +2,30 @@ package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.M;
+import static android.os.Build.VERSION_CODES.Q;
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.verify;
+import static org.junit.Assert.assertThrows;
import static org.robolectric.Shadows.shadowOf;
import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
import android.app.Application;
import android.app.backup.BackupManager;
+import android.app.backup.BackupTransport;
import android.app.backup.RestoreObserver;
import android.app.backup.RestoreSession;
import android.app.backup.RestoreSet;
+import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import com.google.common.truth.Correspondence;
-import java.util.Arrays;
-import java.util.Collections;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Objects;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;
import org.robolectric.util.ReflectionHelpers;
@@ -34,20 +33,19 @@ import org.robolectric.util.ReflectionHelpers;
@RunWith(AndroidJUnit4.class)
public class ShadowBackupManagerTest {
private BackupManager backupManager;
- @Mock private TestRestoreObserver restoreObserver;
+ private TestRestoreObserver restoreObserver;
@Before
public void setUp() {
- MockitoAnnotations.initMocks(this);
-
shadowMainLooper().pause();
shadowOf((Application) ApplicationProvider.getApplicationContext())
.grantPermissions(android.Manifest.permission.BACKUP);
backupManager = new BackupManager(ApplicationProvider.getApplicationContext());
+ restoreObserver = new TestRestoreObserver();
- shadowOf(backupManager).addAvailableRestoreSets(123L, Arrays.asList("foo.bar", "bar.baz"));
- shadowOf(backupManager).addAvailableRestoreSets(456L, Collections.singletonList("hello.world"));
+ shadowOf(backupManager).addAvailableRestoreSets(123L, ImmutableList.of("foo.bar", "bar.baz"));
+ shadowOf(backupManager).addAvailableRestoreSets(456L, ImmutableList.of("hello.world"));
}
@Test
@@ -96,12 +94,8 @@ public class ShadowBackupManagerTest {
public void isBackupEnabled_noPermission_shouldThrowSecurityException() {
shadowOf((Application) ApplicationProvider.getApplicationContext())
.denyPermissions(android.Manifest.permission.BACKUP);
- try {
- backupManager.isBackupEnabled();
- fail("SecurityException should be thrown");
- } catch (SecurityException e) {
- // pass
- }
+
+ assertThrows(SecurityException.class, () -> backupManager.isBackupEnabled());
}
@Test
@@ -111,10 +105,8 @@ public class ShadowBackupManagerTest {
assertThat(result).isEqualTo(BackupManager.SUCCESS);
shadowMainLooper().idle();
- ArgumentCaptor<RestoreSet[]> restoreSetArg = ArgumentCaptor.forClass(RestoreSet[].class);
- verify(restoreObserver).restoreSetsAvailable(restoreSetArg.capture());
- RestoreSet[] restoreSets = restoreSetArg.getValue();
+ RestoreSet[] restoreSets = restoreObserver.getRestoreSets();
assertThat(restoreSets).hasLength(2);
assertThat(restoreSets)
.asList()
@@ -130,12 +122,14 @@ public class ShadowBackupManagerTest {
assertThat(result).isEqualTo(BackupManager.SUCCESS);
shadowMainLooper().idle();
- verify(restoreObserver).restoreStarting(eq(2));
- verify(restoreObserver).restoreFinished(eq(BackupManager.SUCCESS));
-
+ assertThat(restoreObserver.getRestoreStartingNumPackages()).isEqualTo(2);
+ assertThat(restoreObserver.getRestoreFinishedResult()).isEqualTo(BackupManager.SUCCESS);
assertThat(shadowOf(backupManager).getPackageRestoreToken("foo.bar")).isEqualTo(123L);
+ assertThat(shadowOf(backupManager).getPackageRestoreCount("foo.bar")).isEqualTo(1);
assertThat(shadowOf(backupManager).getPackageRestoreToken("bar.baz")).isEqualTo(123L);
+ assertThat(shadowOf(backupManager).getPackageRestoreCount("bar.baz")).isEqualTo(1);
assertThat(shadowOf(backupManager).getPackageRestoreToken("hello.world")).isEqualTo(0L);
+ assertThat(shadowOf(backupManager).getPackageRestoreCount("hello.world")).isEqualTo(0);
}
@Test
@@ -146,11 +140,90 @@ public class ShadowBackupManagerTest {
assertThat(result).isEqualTo(BackupManager.SUCCESS);
shadowMainLooper().idle();
- verify(restoreObserver).restoreStarting(eq(1));
- verify(restoreObserver).restoreFinished(eq(BackupManager.SUCCESS));
+ assertThat(restoreObserver.getRestoreStartingNumPackages()).isEqualTo(1);
+ assertThat(restoreObserver.getRestoreFinishedResult()).isEqualTo(BackupManager.SUCCESS);
assertThat(shadowOf(backupManager).getPackageRestoreToken("foo.bar")).isEqualTo(0L);
+ assertThat(shadowOf(backupManager).getPackageRestoreCount("foo.bar")).isEqualTo(0);
assertThat(shadowOf(backupManager).getPackageRestoreToken("bar.baz")).isEqualTo(123L);
+ assertThat(shadowOf(backupManager).getPackageRestoreCount("bar.baz")).isEqualTo(1);
+ }
+
+ @Test
+ @Config(minSdk = Q)
+ public void restorePackages_multipleRestores_countsRestores() {
+ RestoreSession restoreSession = backupManager.beginRestoreSession();
+
+ int result = restoreSession.restorePackages(123L, restoreObserver, ImmutableSet.of("foo.bar"));
+ assertThat(result).isEqualTo(BackupManager.SUCCESS);
+ restoreSession.endRestoreSession();
+
+ restoreSession = backupManager.beginRestoreSession();
+ result = restoreSession.restorePackages(123L, restoreObserver, ImmutableSet.of("foo.bar"));
+ assertThat(result).isEqualTo(BackupManager.SUCCESS);
+
+ assertThat(shadowOf(backupManager).getPackageRestoreToken("foo.bar")).isEqualTo(123L);
+ assertThat(shadowOf(backupManager).getPackageRestoreCount("foo.bar")).isEqualTo(2);
+ }
+
+ @Test
+ @Config(minSdk = Q)
+ public void restorePackages_transportError_returnsErrorInRestoreFinishedCallback() {
+ shadowOf(backupManager)
+ .addAvailableRestoreSets(
+ 789L, ImmutableList.of("foo.bar"), BackupTransport.TRANSPORT_ERROR);
+ RestoreSession restoreSession = backupManager.beginRestoreSession();
+
+ int result = restoreSession.restorePackages(789L, restoreObserver, ImmutableSet.of("foo.bar"));
+ assertThat(result).isEqualTo(BackupManager.SUCCESS);
+ shadowMainLooper().idle();
+
+ assertThat(restoreObserver.getRestoreFinishedResult())
+ .isEqualTo(BackupTransport.TRANSPORT_ERROR);
+ }
+
+ @Test
+ @Config(minSdk = Q)
+ public void restorePackages_multipleRestoreSets_returnsEachResult() {
+ // Add two restore set for token 789
+ shadowOf(backupManager)
+ .addAvailableRestoreSets(
+ 789L, ImmutableList.of("foo.bar"), BackupTransport.TRANSPORT_ERROR);
+ shadowOf(backupManager)
+ .addAvailableRestoreSets(789L, ImmutableList.of("foo.bar"), BackupManager.SUCCESS);
+ RestoreSession restoreSession = backupManager.beginRestoreSession();
+
+ List<Integer> restoreResults = new ArrayList<>();
+ for (int attempt = 1; attempt <= 3; attempt++) {
+ if (restoreSession != null) {
+ restoreSession.endRestoreSession();
+ }
+ restoreObserver = new TestRestoreObserver();
+ restoreSession = backupManager.beginRestoreSession();
+ int result =
+ restoreSession.restorePackages(789L, restoreObserver, ImmutableSet.of("foo.bar"));
+ assertThat(result).isEqualTo(BackupManager.SUCCESS);
+ shadowMainLooper().idle();
+ restoreResults.add(restoreObserver.getRestoreFinishedResult());
+ }
+
+ // The first attempt is expected to fail, any subsequent attempt is expected to succeed
+ assertThat(restoreResults)
+ .containsExactly(
+ BackupTransport.TRANSPORT_ERROR, BackupManager.SUCCESS, BackupManager.SUCCESS);
+ }
+
+ @Test
+ @Config(minSdk = Q)
+ public void restorePackages_unknownToken_returnsError() {
+ RestoreSession restoreSession = backupManager.beginRestoreSession();
+
+ int result = restoreSession.restorePackages(1L, restoreObserver, ImmutableSet.of("foo.bar"));
+ assertThat(result).isEqualTo(BackupManager.SUCCESS);
+ shadowMainLooper().idle();
+
+ assertThat(restoreObserver.getRestoreStartingNumPackages()).isEqualTo(0);
+ assertThat(restoreObserver.getRestoreFinishedResult()).isEqualTo(-1);
}
@Test
@@ -159,9 +232,10 @@ public class ShadowBackupManagerTest {
restoreSession.restoreSome(123L, restoreObserver, new String[0]);
assertThat(shadowOf(backupManager).getPackageRestoreToken("bar.baz")).isEqualTo(0L);
+ assertThat(shadowOf(backupManager).getPackageRestoreCount("bar.baz")).isEqualTo(0);
restoreSession.endRestoreSession();
shadowMainLooper().idle();
- Mockito.reset(restoreObserver);
+ restoreObserver = new TestRestoreObserver();
restoreSession = backupManager.beginRestoreSession();
int result = restoreSession.restorePackage("bar.baz", restoreObserver);
@@ -169,9 +243,10 @@ public class ShadowBackupManagerTest {
assertThat(result).isEqualTo(BackupManager.SUCCESS);
shadowMainLooper().idle();
- verify(restoreObserver).restoreStarting(eq(1));
- verify(restoreObserver).restoreFinished(eq(BackupManager.SUCCESS));
+ assertThat(restoreObserver.getRestoreStartingNumPackages()).isEqualTo(1);
+ assertThat(restoreObserver.getRestoreFinishedResult()).isEqualTo(BackupManager.SUCCESS);
assertThat(shadowOf(backupManager).getPackageRestoreToken("bar.baz")).isEqualTo(123L);
+ assertThat(shadowOf(backupManager).getPackageRestoreCount("bar.baz")).isEqualTo(1);
}
@Test
@@ -195,15 +270,14 @@ public class ShadowBackupManagerTest {
long defaultVal = 0L;
long restoreToken = 123L;
RestoreSession restoreSession = backupManager.beginRestoreSession();
+
int result =
restoreSession.restoreSome(restoreToken, restoreObserver, new String[] {"bar.baz"});
-
assertThat(result).isEqualTo(BackupManager.SUCCESS);
-
shadowMainLooper().idle();
- verify(restoreObserver).restoreStarting(eq(1));
- verify(restoreObserver).restoreFinished(eq(BackupManager.SUCCESS));
+ assertThat(restoreObserver.getRestoreStartingNumPackages()).isEqualTo(1);
+ assertThat(restoreObserver.getRestoreFinishedResult()).isEqualTo(BackupManager.SUCCESS);
assertThat(backupManager.getAvailableRestoreToken("foo.bar")).isEqualTo(defaultVal);
assertThat(backupManager.getAvailableRestoreToken("bar.baz")).isEqualTo(restoreToken);
}
@@ -215,5 +289,39 @@ public class ShadowBackupManagerTest {
"field \"" + fieldName + "\" matches");
}
- private static class TestRestoreObserver extends RestoreObserver {}
+ private static class TestRestoreObserver extends RestoreObserver {
+ @Nullable private RestoreSet[] restoreSets;
+ @Nullable private Integer restoreStartingNumPackages;
+ @Nullable private Integer restoreFinishedResult;
+
+ @Override
+ public void restoreSetsAvailable(RestoreSet[] restoreSets) {
+ this.restoreSets = restoreSets;
+ }
+
+ @Override
+ public void restoreStarting(int numPackages) {
+ this.restoreStartingNumPackages = numPackages;
+ }
+
+ @Override
+ public void restoreFinished(int result) {
+ this.restoreFinishedResult = result;
+ }
+
+ @Nullable
+ public RestoreSet[] getRestoreSets() {
+ return restoreSets;
+ }
+
+ @Nullable
+ public Integer getRestoreStartingNumPackages() {
+ return restoreStartingNumPackages;
+ }
+
+ @Nullable
+ public Integer getRestoreFinishedResult() {
+ return restoreFinishedResult;
+ }
+ }
}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBasicTagTechnologyTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBasicTagTechnologyTest.java
index d3918cc38..313066b77 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBasicTagTechnologyTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBasicTagTechnologyTest.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.nfc.tech.IsoDep;
@@ -8,11 +7,9 @@ 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;
/** Unit tests for {@link ShadowBasicTagTechnology}. */
@RunWith(AndroidJUnit4.class)
-@Config(minSdk = KITKAT)
public final class ShadowBasicTagTechnologyTest {
// IsoDep extends BasicTagTechnology, which is otherwise a package-protected class.
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBinderTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBinderTest.java
index b02e06691..0acc2b87a 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBinderTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBinderTest.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static android.os.Build.VERSION_CODES.Q;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
@@ -100,7 +99,6 @@ public class ShadowBinderTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void testSetCallingUserHandle() {
UserHandle newUser = shadowOf(userManager).addUser(10, "secondary_user", 0);
ShadowBinder.setCallingUserHandle(newUser);
@@ -136,7 +134,6 @@ public class ShadowBinderTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void testGetCallingUserHandleShouldUseThatOfProcessByDefault() {
assertThat(Binder.getCallingUserHandle()).isEqualTo(android.os.Process.myUserHandle());
}
@@ -160,7 +157,6 @@ public class ShadowBinderTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void testResetUpdatesCallingUserHandle() {
UserHandle newUser = shadowOf(userManager).addUser(10, "secondary_user", 0);
ShadowBinder.setCallingUserHandle(newUser);
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBitmapTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBitmapTest.java
index dfe336b6c..face8d842 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBitmapTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBitmapTest.java
@@ -1,7 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.S;
import static com.google.common.truth.Truth.assertThat;
@@ -113,7 +111,6 @@ public class ShadowBitmapTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void hasMipmap() {
Bitmap bitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888);
assertThat(bitmap.hasMipMap()).isFalse();
@@ -122,7 +119,6 @@ public class ShadowBitmapTest {
}
@Test
- @Config(minSdk = KITKAT)
public void getAllocationByteCount() {
Bitmap bitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888);
assertThat(bitmap.getAllocationByteCount()).isGreaterThan(0);
@@ -178,7 +174,6 @@ public class ShadowBitmapTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void shouldCreateMutableBitmapWithDisplayMetrics() {
final DisplayMetrics metrics = new DisplayMetrics();
metrics.densityDpi = 1000;
@@ -263,7 +258,6 @@ public class ShadowBitmapTest {
}
@Test(expected = NullPointerException.class)
- @Config(minSdk = JELLY_BEAN_MR1)
public void byteCountIsAccurate() {
Bitmap b1 = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888);
assertThat(b1.getByteCount()).isEqualTo(400);
@@ -276,7 +270,6 @@ public class ShadowBitmapTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void shouldSetDensity() {
final Bitmap bitmap = Bitmap.createBitmap(new DisplayMetrics(), 100, 100, Bitmap.Config.ARGB_8888);
bitmap.setDensity(1000);
@@ -646,7 +639,6 @@ public class ShadowBitmapTest {
original.reconfigure(100, 100, Bitmap.Config.ARGB_8888);
}
- @Config(minSdk = Build.VERSION_CODES.KITKAT)
@Test
public void isPremultiplied_argb888_defaultsTrue() {
Bitmap original = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
@@ -654,7 +646,6 @@ public class ShadowBitmapTest {
assertThat(original.isPremultiplied()).isTrue();
}
- @Config(minSdk = Build.VERSION_CODES.KITKAT)
@Test
public void isPremultiplied_argb888_noAlpha_defaultsFalse() {
Bitmap original = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
@@ -663,7 +654,6 @@ public class ShadowBitmapTest {
assertThat(original.isPremultiplied()).isFalse();
}
- @Config(minSdk = Build.VERSION_CODES.KITKAT)
@Test
public void isPremultiplied_rgb565_defaultsFalse() {
Bitmap original = Bitmap.createBitmap(100, 100, Bitmap.Config.RGB_565);
@@ -671,7 +661,6 @@ public class ShadowBitmapTest {
assertThat(original.isPremultiplied()).isFalse();
}
- @Config(minSdk = Build.VERSION_CODES.KITKAT)
@Test
public void setPremultiplied_argb888_isFalse() {
Bitmap original = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothA2dpTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothA2dpTest.java
index c5995991f..b1c195b20 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothA2dpTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothA2dpTest.java
@@ -1,13 +1,18 @@
package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.P;
+import static android.os.Build.VERSION_CODES.R;
import static android.os.Build.VERSION_CODES.S;
+import static android.os.Build.VERSION_CODES.TIRAMISU;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
import static org.robolectric.Shadows.shadowOf;
import android.app.Application;
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothCodecConfig;
+import android.bluetooth.BluetoothCodecStatus;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Intent;
@@ -168,4 +173,90 @@ public class ShadowBluetoothA2dpTest {
assertThat((BluetoothDevice) intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE))
.isEqualTo(connectedBluetoothDevice);
}
+
+ @Test
+ @Config(minSdk = TIRAMISU)
+ public void getCodecStatus_returnValueFromSetter() {
+ BluetoothCodecStatus status =
+ new BluetoothCodecStatus.Builder()
+ .setCodecConfig(
+ new BluetoothCodecConfig.Builder()
+ .setCodecType(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC)
+ .setBitsPerSample(BluetoothCodecConfig.BITS_PER_SAMPLE_32)
+ .build())
+ .build();
+
+ shadowBluetoothA2dp.setCodecStatus(connectedBluetoothDevice, status);
+
+ assertThat(bluetoothA2dp.getCodecStatus(connectedBluetoothDevice)).isEqualTo(status);
+ }
+
+ @Test
+ @Config(minSdk = TIRAMISU)
+ public void getCodecConfigPreference_returnValueFromSetter() {
+ BluetoothCodecConfig codecConfig =
+ new BluetoothCodecConfig.Builder()
+ .setCodecType(BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC)
+ .setBitsPerSample(BluetoothCodecConfig.BITS_PER_SAMPLE_16)
+ .build();
+
+ bluetoothA2dp.setCodecConfigPreference(connectedBluetoothDevice, codecConfig);
+
+ assertThat(shadowBluetoothA2dp.getCodecConfigPreference(connectedBluetoothDevice))
+ .isEqualTo(codecConfig);
+ }
+
+ @Test
+ @Config(minSdk = R)
+ public void isOptionalCodecsEnabled_deviceIsNull_throwException() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> bluetoothA2dp.isOptionalCodecsEnabled(/* device= */ null));
+ }
+
+ @Test
+ @Config(minSdk = R)
+ public void isOptionalCodecsEnabled_defaultUnknown() {
+ assertThat(bluetoothA2dp.isOptionalCodecsEnabled(connectedBluetoothDevice))
+ .isEqualTo(BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN);
+ }
+
+ @Test
+ @Config(minSdk = R)
+ public void isOptionalCodecsEnabled_returnValueFromSetOptionalCodecsEnabled() {
+ bluetoothA2dp.setOptionalCodecsEnabled(
+ connectedBluetoothDevice, BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED);
+
+ assertThat(shadowBluetoothA2dp.isOptionalCodecsEnabled(connectedBluetoothDevice))
+ .isEqualTo(BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED);
+ }
+
+ @Test
+ @Config(minSdk = R)
+ public void isOptionalCodecsEnabled_returnValueFromSetOptionalCodecsDisabled() {
+ bluetoothA2dp.setOptionalCodecsEnabled(
+ connectedBluetoothDevice, BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED);
+
+ assertThat(shadowBluetoothA2dp.isOptionalCodecsEnabled(connectedBluetoothDevice))
+ .isEqualTo(BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED);
+ }
+
+ @Test
+ @Config(minSdk = R)
+ public void setOptionalCodecsEnabled_invalidValue_ignore() {
+ bluetoothA2dp.setOptionalCodecsEnabled(connectedBluetoothDevice, 100);
+
+ assertThat(shadowBluetoothA2dp.isOptionalCodecsEnabled(connectedBluetoothDevice))
+ .isEqualTo(BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN);
+ }
+
+ @Test
+ @Config(minSdk = R)
+ public void setOptionalCodecsEnabled_deviceIsNull_throwException() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ bluetoothA2dp.setOptionalCodecsEnabled(
+ /* device= */ null, BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED));
+ }
}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothAdapterTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothAdapterTest.java
index a9c3e8d81..ae6eb3bdc 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothAdapterTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothAdapterTest.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.O;
@@ -9,6 +8,7 @@ import static android.os.Build.VERSION_CODES.R;
import static android.os.Build.VERSION_CODES.S;
import static android.os.Build.VERSION_CODES.S_V2;
import static android.os.Build.VERSION_CODES.TIRAMISU;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static com.google.common.truth.Truth.assertThat;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -95,6 +95,26 @@ public class ShadowBluetoothAdapterTest {
}
@Test
+ @Config(minSdk = UPSIDE_DOWN_CAKE)
+ public void canSetAndGetIsLeCodedPhySupported() {
+ assertThat(bluetoothAdapter.isLeCodedPhySupported()).isTrue();
+
+ shadowOf(bluetoothAdapter).setIsLeCodedPhySupported(false);
+
+ assertThat(bluetoothAdapter.isLeCodedPhySupported()).isFalse();
+ }
+
+ @Test
+ @Config(minSdk = UPSIDE_DOWN_CAKE)
+ public void canSetAndGetIsLe2MPhySupported() {
+ assertThat(bluetoothAdapter.isLe2MPhySupported()).isTrue();
+
+ shadowOf(bluetoothAdapter).setIsLe2MPhySupported(false);
+
+ assertThat(bluetoothAdapter.isLe2MPhySupported()).isFalse();
+ }
+
+ @Test
public void testAdapterDefaultsDisabled() {
assertThat(bluetoothAdapter.isEnabled()).isFalse();
}
@@ -370,7 +390,6 @@ public class ShadowBluetoothAdapterTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR2)
public void testLeScan() {
BluetoothAdapter.LeScanCallback callback1 = newLeScanCallback();
BluetoothAdapter.LeScanCallback callback2 = newLeScanCallback();
@@ -388,7 +407,6 @@ public class ShadowBluetoothAdapterTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR2)
public void testGetSingleLeScanCallback() {
BluetoothAdapter.LeScanCallback callback1 = newLeScanCallback();
BluetoothAdapter.LeScanCallback callback2 = newLeScanCallback();
@@ -430,9 +448,8 @@ public class ShadowBluetoothAdapterTest {
@Test
public void secureRfcomm_notNull() throws Exception {
assertThat(
- bluetoothAdapter.listenUsingRfcommWithServiceRecord(
- "serviceName", UUID.randomUUID()))
- .isNotNull();
+ bluetoothAdapter.listenUsingRfcommWithServiceRecord("serviceName", UUID.randomUUID()))
+ .isNotNull();
}
@Test
@@ -904,4 +921,36 @@ public class ShadowBluetoothAdapterTest {
shadowOf(adapter).setLeAudioSupported(BluetoothStatusCodes.FEATURE_SUPPORTED);
assertThat(adapter.isLeAudioSupported()).isEqualTo(BluetoothStatusCodes.FEATURE_SUPPORTED);
}
+
+ @Config(minSdk = UPSIDE_DOWN_CAKE)
+ @Test
+ public void canGetAndSetDistanceMeasurementSupport() {
+ // By default distance measurement is not supported
+ assertThat(bluetoothAdapter.isDistanceMeasurementSupported())
+ .isEqualTo(BluetoothStatusCodes.FEATURE_NOT_SUPPORTED);
+
+ // set distance measurement feature to supported.
+ shadowOf(bluetoothAdapter)
+ .setDistanceMeasurementSupported(BluetoothStatusCodes.FEATURE_SUPPORTED);
+
+ assertThat(bluetoothAdapter.isDistanceMeasurementSupported())
+ .isEqualTo(BluetoothStatusCodes.FEATURE_SUPPORTED);
+ }
+
+ @Test
+ @SuppressWarnings("JdkImmutableCollections")
+ public void getBondedDevices_whenSeededWithSet_returnsThatSet() {
+ BluetoothDevice device1 = bluetoothAdapter.getRemoteDevice("AB:CD:EF:12:34:56");
+ BluetoothDevice device2 = bluetoothAdapter.getRemoteDevice("12:34:56:AB:CD:EF");
+ shadowOf(bluetoothAdapter).setBondedDevices(Set.of(device1, device2));
+
+ assertThat(bluetoothAdapter.getBondedDevices()).containsExactly(device1, device2);
+ }
+
+ @Test
+ public void getBondedDevices_whenSeededWithNull_returnsNull() {
+ shadowOf(bluetoothAdapter).setBondedDevices(null);
+
+ assertThat(bluetoothAdapter.getBondedDevices()).isNull();
+ }
}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothDeviceTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothDeviceTest.java
index d63ea83ef..93a5d5dfc 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothDeviceTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothDeviceTest.java
@@ -5,7 +5,6 @@ import static android.bluetooth.BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES;
import static android.bluetooth.BluetoothDevice.BOND_BONDED;
import static android.bluetooth.BluetoothDevice.BOND_NONE;
import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_CLASSIC;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.Q;
@@ -31,6 +30,7 @@ import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
+import javax.annotation.Nullable;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
@@ -167,7 +167,6 @@ public class ShadowBluetoothDeviceTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR2)
public void connectGatt_doesntCrash() {
shadowOf(application).grantPermissions(BLUETOOTH_CONNECT);
BluetoothDevice bluetoothDevice = ShadowBluetoothDevice.newInstance(MOCK_MAC_ADDRESS);
@@ -221,7 +220,6 @@ public class ShadowBluetoothDeviceTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR2)
public void canSetAndGetType() {
shadowOf(application).grantPermissions(BLUETOOTH_CONNECT);
BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(MOCK_MAC_ADDRESS);
@@ -231,7 +229,6 @@ public class ShadowBluetoothDeviceTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR2)
public void canGetBluetoothGatts() {
shadowOf(application).grantPermissions(BLUETOOTH_CONNECT);
BluetoothDevice device = ShadowBluetoothDevice.newInstance(MOCK_MAC_ADDRESS);
@@ -248,7 +245,6 @@ public class ShadowBluetoothDeviceTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR2)
public void connectGatt_setsBluetoothGattCallback() {
shadowOf(application).grantPermissions(BLUETOOTH_CONNECT);
BluetoothDevice device = ShadowBluetoothDevice.newInstance(MOCK_MAC_ADDRESS);
@@ -262,7 +258,6 @@ public class ShadowBluetoothDeviceTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR2)
public void canSimulateGattConnectionChange() {
shadowOf(application).grantPermissions(BLUETOOTH_CONNECT);
BluetoothDevice device = ShadowBluetoothDevice.newInstance(MOCK_MAC_ADDRESS);
@@ -278,6 +273,38 @@ public class ShadowBluetoothDeviceTest {
}
@Test
+ @Config(minSdk = O)
+ public void connectGatt_withInterceptor() {
+ shadowOf(application).grantPermissions(BLUETOOTH_CONNECT);
+ BluetoothDevice bluetoothDevice = ShadowBluetoothDevice.newInstance(MOCK_MAC_ADDRESS);
+
+ final class FakeGattConnectionInterceptor
+ implements ShadowBluetoothDevice.BluetoothGattConnectionInterceptor {
+ @Nullable private BluetoothGatt interceptedGatt = null;
+
+ public BluetoothGatt getInterceptedGatt() {
+ return interceptedGatt;
+ }
+
+ @Override
+ public void onNewGattConnection(BluetoothGatt gatt) {
+ interceptedGatt = gatt;
+ }
+ }
+
+ FakeGattConnectionInterceptor interceptor = new FakeGattConnectionInterceptor();
+ shadowOf(bluetoothDevice).setGattConnectionInterceptor(interceptor);
+
+ BluetoothGatt gatt =
+ bluetoothDevice.connectGatt(
+ ApplicationProvider.getApplicationContext(),
+ /* autoConnect= */ false,
+ new BluetoothGattCallback() {});
+ assertThat(gatt).isNotNull();
+ assertThat(gatt).isEqualTo(interceptor.getInterceptedGatt());
+ }
+
+ @Test
public void createRfcommSocketToServiceRecord_returnsSocket() throws Exception {
BluetoothDevice device = ShadowBluetoothDevice.newInstance(MOCK_MAC_ADDRESS);
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothGattServerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothGattServerTest.java
index cdac21f20..c31cb92d3 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothGattServerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothGattServerTest.java
@@ -7,11 +7,14 @@ import static org.robolectric.Shadows.shadowOf;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattServer;
import android.bluetooth.BluetoothGattServerCallback;
+import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
+import java.util.UUID;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -25,10 +28,35 @@ import org.robolectric.annotation.Config;
public class ShadowBluetoothGattServerTest {
private static final int INITIAL_VALUE = -99;
+ private static final int REQUEST_ID = 1;
+ private static final int OFFSET = 0;
private static final String ACTION_CONNECTION = "CONNECT/DISCONNECT";
+ private static final String ACTION_CHARACTERISTIC_WRITE = "WRITE";
+ private static final String ACTION_CHARACTERISTIC_WRITE_NO_RESPONSE = "WRITE_NO_RESPONSE";
private static final String MOCK_MAC_ADDRESS = "00:11:22:33:AA:BB";
+ private static final byte[] PAYLOAD = new byte[] {'m', 'm', 'e'};
+ private static final byte[] PAYLOAD2 = new byte[] {'c', 'd', 'i'};
private static final byte[] RESPONSE_VALUE1 = new byte[] {'a', 'b', 'c'};
private static final byte[] RESPONSE_VALUE2 = new byte[] {'1', '2', '3'};
+ private static final BluetoothGattCharacteristic characteristicWithWriteProperty =
+ new BluetoothGattCharacteristic(
+ UUID.fromString("00000000-0000-0000-0000-0000000000A1"),
+ BluetoothGattCharacteristic.PROPERTY_WRITE,
+ BluetoothGattCharacteristic.PERMISSION_WRITE);
+ private static final BluetoothGattCharacteristic characteristicWithWriteNoResponseProperty =
+ new BluetoothGattCharacteristic(
+ UUID.fromString("00000000-0000-0000-0000-0000000000A2"),
+ BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE,
+ BluetoothGattCharacteristic.PERMISSION_WRITE);
+ private static final BluetoothGattCharacteristic characteristicWithReadProperty =
+ new BluetoothGattCharacteristic(
+ UUID.fromString("00000000-0000-0000-0000-0000000000A3"),
+ BluetoothGattCharacteristic.PROPERTY_READ,
+ BluetoothGattCharacteristic.PERMISSION_READ);
+ private static final BluetoothGattService service =
+ new BluetoothGattService(
+ UUID.fromString("00000000-0000-0000-0001-0000000000A1"),
+ BluetoothGattService.SERVICE_TYPE_PRIMARY);
private BluetoothManager manager;
private Context context;
@@ -47,6 +75,23 @@ public class ShadowBluetoothGattServerTest {
resultState = newState;
resultAction = ACTION_CONNECTION;
}
+
+ @Override
+ public void onCharacteristicWriteRequest(
+ BluetoothDevice device,
+ int requestId,
+ BluetoothGattCharacteristic characteristic,
+ boolean isWrite,
+ boolean isRead,
+ int offset,
+ byte[] payload) {
+ if (characteristic.getWriteType() == BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT) {
+ resultAction = ACTION_CHARACTERISTIC_WRITE;
+ } else if (characteristic.getWriteType()
+ == BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE) {
+ resultAction = ACTION_CHARACTERISTIC_WRITE_NO_RESPONSE;
+ }
+ }
};
@Before
@@ -119,6 +164,89 @@ public class ShadowBluetoothGattServerTest {
}
@Test
+ public void test_getWrittenBytes_initially() {
+ assertThat(shadowOf(server).getWrittenBytes()).isEmpty();
+ }
+
+ @Test
+ public void test_getWrittenBytes_afterCharacteristicWriteRequest() {
+ shadowOf(server).setGattServerCallback(callback);
+ assertThat(
+ shadowOf(server)
+ .notifyOnCharacteristicWriteRequest(
+ device,
+ REQUEST_ID,
+ characteristicWithWriteProperty,
+ false,
+ false,
+ OFFSET,
+ PAYLOAD))
+ .isTrue();
+ assertThat(shadowOf(server).getWrittenBytes()).hasSize(1);
+ assertThat(
+ shadowOf(server)
+ .notifyOnCharacteristicWriteRequest(
+ device,
+ REQUEST_ID,
+ characteristicWithWriteProperty,
+ false,
+ false,
+ OFFSET,
+ PAYLOAD2))
+ .isTrue();
+ assertThat(shadowOf(server).getWrittenBytes()).hasSize(2);
+ assertThat(shadowOf(server).getWrittenBytes().get(0)).isEqualTo(PAYLOAD);
+ assertThat(shadowOf(server).getWrittenBytes().get(1)).isEqualTo(PAYLOAD2);
+ }
+
+ @Test
+ public void test_getWrittenBytes_afterClearWrittenBytes() {
+ assertThat(
+ shadowOf(server)
+ .notifyOnCharacteristicWriteRequest(
+ device,
+ REQUEST_ID,
+ characteristicWithWriteProperty,
+ false,
+ false,
+ OFFSET,
+ PAYLOAD))
+ .isTrue();
+ shadowOf(server).clearWrittenBytes();
+ assertThat(shadowOf(server).getWrittenBytes()).isEmpty();
+ }
+
+ @Test
+ public void test_getWrittenBytes_acceptsNull() {
+ assertThat(
+ shadowOf(server)
+ .notifyOnCharacteristicWriteRequest(
+ device,
+ REQUEST_ID,
+ characteristicWithWriteProperty,
+ false,
+ false,
+ OFFSET,
+ PAYLOAD))
+ .isTrue();
+ assertThat(shadowOf(server).getWrittenBytes()).hasSize(1);
+ assertThat(
+ shadowOf(server)
+ .notifyOnCharacteristicWriteRequest(
+ device,
+ REQUEST_ID,
+ characteristicWithWriteProperty,
+ false,
+ false,
+ OFFSET,
+ null))
+ .isTrue();
+ assertThat(shadowOf(server).getWrittenBytes()).hasSize(2);
+ assertThat(shadowOf(server).getWrittenBytes().get(0)).isEqualTo(PAYLOAD);
+ assertThat(shadowOf(server).getWrittenBytes().get(1)).isEqualTo(null);
+ }
+
+ @Test
public void test_isConnectedToDevice_initially() {
assertThat(shadowOf(server).isConnectedToDevice(device)).isFalse();
}
@@ -179,6 +307,73 @@ public class ShadowBluetoothGattServerTest {
}
@Test
+ public void test_notifyOnCharacteristicWriteRequest_withoutCallback() {
+ shadowOf(server).setGattServerCallback(null);
+ assertThat(
+ shadowOf(server)
+ .notifyOnCharacteristicWriteRequest(
+ device,
+ REQUEST_ID,
+ characteristicWithWriteProperty,
+ false,
+ false,
+ OFFSET,
+ PAYLOAD))
+ .isFalse();
+ }
+
+ @Test
+ public void test_notifyOnCharacteristicWriteRequest_withCallback_wrongProperty() {
+ shadowOf(server).setGattServerCallback(callback);
+ assertThat(
+ shadowOf(server)
+ .notifyOnCharacteristicWriteRequest(
+ device,
+ REQUEST_ID,
+ characteristicWithReadProperty,
+ false,
+ false,
+ OFFSET,
+ PAYLOAD))
+ .isFalse();
+ assertThat(resultAction).isNull();
+ }
+
+ @Test
+ public void test_notifyOnCharacteristicWriteRequest_correctSetup_writeProperty() {
+ shadowOf(server).setGattServerCallback(callback);
+ assertThat(
+ shadowOf(server)
+ .notifyOnCharacteristicWriteRequest(
+ device,
+ REQUEST_ID,
+ characteristicWithWriteProperty,
+ false,
+ false,
+ OFFSET,
+ PAYLOAD))
+ .isTrue();
+ assertThat(resultAction).isEqualTo(ACTION_CHARACTERISTIC_WRITE);
+ }
+
+ @Test
+ public void test_notifyOnCharacteristicWriteRequest_correctSetup_writeNoResponseProperty() {
+ shadowOf(server).setGattServerCallback(callback);
+ assertThat(
+ shadowOf(server)
+ .notifyOnCharacteristicWriteRequest(
+ device,
+ REQUEST_ID,
+ characteristicWithWriteNoResponseProperty,
+ false,
+ false,
+ OFFSET,
+ PAYLOAD))
+ .isTrue();
+ assertThat(resultAction).isEqualTo(ACTION_CHARACTERISTIC_WRITE_NO_RESPONSE);
+ }
+
+ @Test
public void test_isConnectionCancelled_afterCancelConnection() {
server.cancelConnection(device);
assertThat(shadowOf(server).isConnectionCancelled(device)).isTrue();
@@ -206,4 +401,54 @@ public class ShadowBluetoothGattServerTest {
shadowOf(server).notifyConnection(device);
assertThat(shadowOf(server).isConnectionCancelled(device)).isFalse();
}
+
+ @Test
+ public void test_getServices_beforeAddService() {
+ assertThat(shadowOf(server).getServices()).isEmpty();
+ }
+
+ @Test
+ public void test_getServices_afterAddService() {
+ server.addService(service);
+ assertThat(shadowOf(server).getServices()).containsExactly(service);
+ }
+
+ @Test
+ public void test_getServices_afterAddService_afterRemoveService() {
+ server.addService(service);
+ server.removeService(service);
+ assertThat(shadowOf(server).getServices()).isEmpty();
+ }
+
+ @Test
+ public void test_getServices_afterAddService_afterClearService() {
+ server.addService(service);
+ server.clearServices();
+ assertThat(shadowOf(server).getServices()).isEmpty();
+ }
+
+ @Test
+ public void test_getService_beforeAddService() {
+ assertThat(shadowOf(server).getService(service.getUuid())).isNull();
+ }
+
+ @Test
+ public void test_getService_afterAddService() {
+ server.addService(service);
+ assertThat(shadowOf(server).getService(service.getUuid())).isEqualTo(service);
+ }
+
+ @Test
+ public void test_getService_afterAddService_afterRemoveService() {
+ server.addService(service);
+ server.removeService(service);
+ assertThat(shadowOf(server).getService(service.getUuid())).isNull();
+ }
+
+ @Test
+ public void test_getService_afterAddService_afterClearService() {
+ server.addService(service);
+ server.clearServices();
+ assertThat(shadowOf(server).getService(service.getUuid())).isNull();
+ }
}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothGattTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothGattTest.java
index 874ded718..f405847c8 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothGattTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothGattTest.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.O;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
@@ -23,7 +22,6 @@ import org.robolectric.annotation.Config;
/** Tests for {@link ShadowBluetoothGatt}. */
@RunWith(AndroidJUnit4.class)
-@Config(minSdk = JELLY_BEAN_MR2)
public class ShadowBluetoothGattTest {
private static final byte[] CHARACTERISTIC_VALUE = new byte[] {'a', 'b', 'c'};
@@ -33,10 +31,12 @@ public class ShadowBluetoothGattTest {
private static final String ACTION_DISCOVER = "DISCOVER";
private static final String ACTION_READ = "READ";
private static final String ACTION_WRITE = "WRITE";
+ private static final String ACTION_MTU = "MTU";
private static final String REMOTE_ADDRESS = "R-A";
private int resultStatus = INITIAL_VALUE;
private int resultState = INITIAL_VALUE;
+ private int resultMtu = INITIAL_VALUE;
private String resultAction;
private BluetoothGattCharacteristic resultCharacteristic;
private BluetoothGattDescriptor resultDescriptor;
@@ -89,6 +89,13 @@ public class ShadowBluetoothGattTest {
resultDescriptor = descriptor;
resultAction = ACTION_WRITE;
}
+
+ @Override
+ public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
+ resultStatus = status;
+ resultMtu = mtu;
+ resultAction = ACTION_MTU;
+ }
};
private final BluetoothGattCharacteristic characteristicWithReadProperty =
@@ -236,6 +243,24 @@ public class ShadowBluetoothGattTest {
@Test
@Config(minSdk = O)
+ public void requestMtu_failsBeforeCallbackSet() {
+ assertThat(bluetoothGatt.requestMtu(1)).isFalse();
+ }
+
+ @Test
+ @Config(minSdk = O)
+ public void requestMtu_success() {
+ shadowOf(bluetoothGatt).setGattCallback(callback);
+
+ int mtu = 1;
+ assertThat(bluetoothGatt.requestMtu(mtu)).isTrue();
+ assertThat(resultAction).isEqualTo(ACTION_MTU);
+ assertThat(resultStatus).isEqualTo(BluetoothGatt.GATT_SUCCESS);
+ assertThat(resultMtu).isEqualTo(mtu);
+ }
+
+ @Test
+ @Config(minSdk = O)
public void discoverServices_noDiscoverableServices_returnsFalse() {
assertThat(bluetoothGatt.discoverServices()).isFalse();
assertThat(bluetoothGatt.getServices()).isEmpty();
@@ -532,6 +557,98 @@ public class ShadowBluetoothGattTest {
}
@Test
+ @Config
+ public void writeIncomingCharacteristic_updated_withoutCallback() {
+ service1.addCharacteristic(characteristicWithWriteProperties);
+ assertThrows(
+ IllegalStateException.class,
+ () ->
+ shadowOf(bluetoothGatt).writeIncomingCharacteristic(characteristicWithWriteProperties));
+ }
+
+ @Test
+ @Config
+ public void writeIncomingCharacteristic_updated_withCallbackOnly() {
+ shadowOf(bluetoothGatt).setGattCallback(callback);
+ assertThat(
+ shadowOf(bluetoothGatt).writeIncomingCharacteristic(characteristicWithWriteProperties))
+ .isFalse();
+ assertThat(resultStatus).isEqualTo(INITIAL_VALUE);
+ assertThat(resultAction).isNull();
+ assertThat(resultCharacteristic).isNull();
+ assertThat(shadowOf(bluetoothGatt).getLatestWrittenBytes()).isNull();
+ }
+
+ @Test
+ @Config
+ public void writeIncomingCharacteristic_updated_correctlySetup() {
+ shadowOf(bluetoothGatt).setGattCallback(callback);
+ characteristicWithWriteProperties.setValue(CHARACTERISTIC_VALUE);
+ service2.addCharacteristic(characteristicWithWriteProperties);
+ assertThat(characteristicWithWriteProperties.getService()).isNotNull();
+ assertThat(
+ shadowOf(bluetoothGatt).writeIncomingCharacteristic(characteristicWithWriteProperties))
+ .isTrue();
+ assertThat(resultStatus).isEqualTo(BluetoothGatt.GATT_SUCCESS);
+ assertThat(resultAction).isEqualTo(ACTION_WRITE);
+ assertThat(resultCharacteristic).isEqualTo(characteristicWithWriteProperties);
+ assertThat(shadowOf(bluetoothGatt).getLatestWrittenBytes()).isEqualTo(CHARACTERISTIC_VALUE);
+ }
+
+ @Test
+ @Config
+ public void writeIncomingCharacteristic_updated_withCallbackAndServiceSet_wrongProperty() {
+ shadowOf(bluetoothGatt).setGattCallback(callback);
+ service1.addCharacteristic(characteristicWithReadProperty);
+ assertThat(characteristicWithReadProperty.getService()).isNotNull();
+
+ assertThat(shadowOf(bluetoothGatt).writeIncomingCharacteristic(characteristicWithReadProperty))
+ .isFalse();
+ assertThat(resultStatus).isEqualTo(INITIAL_VALUE);
+ assertThat(resultAction).isNull();
+ assertThat(resultCharacteristic).isNull();
+ assertThat(shadowOf(bluetoothGatt).getLatestWrittenBytes()).isNull();
+ }
+
+ @Test
+ @Config
+ public void writeIncomingCharacteristic_updated_onlyWriteProperty() {
+ BluetoothGattCharacteristic characteristic =
+ new BluetoothGattCharacteristic(
+ UUID.fromString("00000000-0000-0000-0000-0000000000A6"),
+ BluetoothGattCharacteristic.PROPERTY_WRITE,
+ BluetoothGattCharacteristic.PERMISSION_WRITE);
+
+ shadowOf(bluetoothGatt).setGattCallback(callback);
+ service1.addCharacteristic(characteristic);
+ characteristic.setValue(CHARACTERISTIC_VALUE);
+ assertThat(shadowOf(bluetoothGatt).writeIncomingCharacteristic(characteristic)).isTrue();
+ assertThat(resultStatus).isEqualTo(BluetoothGatt.GATT_SUCCESS);
+ assertThat(resultAction).isEqualTo(ACTION_WRITE);
+ assertThat(resultCharacteristic).isEqualTo(characteristic);
+ assertThat(shadowOf(bluetoothGatt).getLatestWrittenBytes()).isEqualTo(CHARACTERISTIC_VALUE);
+ }
+
+ @Test
+ @Config
+ public void writeIncomingCharacteristic_updated_onlyWriteNoResponseProperty() {
+ BluetoothGattCharacteristic characteristic =
+ new BluetoothGattCharacteristic(
+ UUID.fromString("00000000-0000-0000-0000-0000000000A7"),
+ BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE,
+ BluetoothGattCharacteristic.PERMISSION_WRITE);
+
+ shadowOf(bluetoothGatt).setGattCallback(callback);
+ service1.addCharacteristic(characteristic);
+ characteristic.setValue(CHARACTERISTIC_VALUE);
+ assertThat(shadowOf(bluetoothGatt).writeIncomingCharacteristic(characteristic)).isTrue();
+ assertThat(resultStatus).isEqualTo(BluetoothGatt.GATT_SUCCESS);
+ assertThat(resultAction).isEqualTo(ACTION_WRITE);
+ assertThat(resultCharacteristic).isEqualTo(characteristic);
+ assertThat(shadowOf(bluetoothGatt).getLatestWrittenBytes()).isEqualTo(CHARACTERISTIC_VALUE);
+ }
+
+ @Test
public void test_getBluetoothConnectionManager() {
assertThat(shadowOf(bluetoothGatt).getBluetoothConnectionManager()).isNotNull();
}
@@ -616,8 +733,12 @@ public class ShadowBluetoothGattTest {
service1.addCharacteristic(characteristicWithReadProperty);
shadowOf(bluetoothGatt).addDiscoverableService(service1);
shadowOf(bluetoothGatt).allowCharacteristicNotification(characteristicWithReadProperty);
+
+ // All setCharacteristicNotification calls are allowed on characteristicWithReadProperty.
assertThat(bluetoothGatt.setCharacteristicNotification(characteristicWithReadProperty, true))
.isTrue();
+ assertThat(bluetoothGatt.setCharacteristicNotification(characteristicWithReadProperty, false))
+ .isTrue();
}
@Test
@@ -626,7 +747,43 @@ public class ShadowBluetoothGattTest {
service1.addCharacteristic(characteristicWithReadProperty);
shadowOf(bluetoothGatt).addDiscoverableService(service1);
shadowOf(bluetoothGatt).disallowCharacteristicNotification(characteristicWithReadProperty);
+
+ // No setCharacteristicNotification calls are allowed on characteristicWithReadProperty.
+ assertThat(bluetoothGatt.setCharacteristicNotification(characteristicWithReadProperty, true))
+ .isFalse();
+ assertThat(bluetoothGatt.setCharacteristicNotification(characteristicWithReadProperty, false))
+ .isFalse();
+ }
+
+ @Test
+ @Config(minSdk = O)
+ public void disallowCharacteristicNotification_byDefault() {
+ service1.addCharacteristic(characteristicWithReadProperty);
+ shadowOf(bluetoothGatt).addDiscoverableService(service1);
+
+ // setCharacteristicNotification calls are disallowed by default when neither
+ // allowCharacteristicNotification nor disallowCharacteristicNotification is called.
+
+ // No setCharacteristicNotification calls are allowed on characteristicWithReadProperty.
+ assertThat(bluetoothGatt.setCharacteristicNotification(characteristicWithReadProperty, true))
+ .isFalse();
+ assertThat(bluetoothGatt.setCharacteristicNotification(characteristicWithReadProperty, false))
+ .isFalse();
+ }
+
+ @Test
+ @Config(minSdk = O)
+ public void disallowCharacteristicNotification_overridesPreviousAllow() {
+ service1.addCharacteristic(characteristicWithReadProperty);
+ shadowOf(bluetoothGatt).addDiscoverableService(service1);
+
+ shadowOf(bluetoothGatt).allowCharacteristicNotification(characteristicWithReadProperty);
+ shadowOf(bluetoothGatt).disallowCharacteristicNotification(characteristicWithReadProperty);
+
+ // No setCharacteristicNotification calls are allowed on characteristicWithReadProperty.
assertThat(bluetoothGatt.setCharacteristicNotification(characteristicWithReadProperty, true))
.isFalse();
+ assertThat(bluetoothGatt.setCharacteristicNotification(characteristicWithReadProperty, false))
+ .isFalse();
}
}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothHeadsetTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothHeadsetTest.java
index 545677640..97065ac33 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothHeadsetTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothHeadsetTest.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.P;
import static android.os.Build.VERSION_CODES.S;
import static android.os.Looper.getMainLooper;
@@ -109,6 +108,28 @@ public class ShadowBluetoothHeadsetTest {
}
@Test
+ public void getDevicesMatchingConnectionStates_returnsMatchingDevices() {
+ shadowOf(bluetoothHeadset).addDevice(device1, BluetoothProfile.STATE_CONNECTING);
+ shadowOf(bluetoothHeadset).addDevice(device2, BluetoothProfile.STATE_DISCONNECTED);
+
+ assertThat(
+ bluetoothHeadset.getDevicesMatchingConnectionStates(
+ new int[] {BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_DISCONNECTED}))
+ .containsExactly(device1, device2);
+ }
+
+ @Test
+ public void getDevicesMatchingConnectionStates_subsetOfAvailableStates_returnsMatchingDevices() {
+ shadowOf(bluetoothHeadset).addDevice(device1, BluetoothProfile.STATE_CONNECTING);
+ shadowOf(bluetoothHeadset).addDevice(device2, BluetoothProfile.STATE_DISCONNECTED);
+
+ assertThat(
+ bluetoothHeadset.getDevicesMatchingConnectionStates(
+ new int[] {BluetoothProfile.STATE_CONNECTING}))
+ .containsExactly(device1);
+ }
+
+ @Test
public void connect_addsDeviceToConnectedListAndReturnsTrue() {
boolean result = bluetoothHeadset.connect(device1);
@@ -256,7 +277,6 @@ public class ShadowBluetoothHeadsetTest {
}
@Test
- @Config(minSdk = KITKAT)
public void sendVendorSpecificResultCode_defaultsToTrueForConnectedDevice() {
shadowOf(bluetoothHeadset).addConnectedDevice(device1);
@@ -264,13 +284,11 @@ public class ShadowBluetoothHeadsetTest {
}
@Test
- @Config(minSdk = KITKAT)
public void sendVendorSpecificResultCode_alwaysFalseForDisconnectedDevice() {
assertThat(bluetoothHeadset.sendVendorSpecificResultCode(device1, "command", "arg")).isFalse();
}
@Test
- @Config(minSdk = KITKAT)
public void sendVendorSpecificResultCode_canBeForcedToFalseForConnectedDevice() {
shadowOf(bluetoothHeadset).addConnectedDevice(device1);
shadowOf(bluetoothHeadset).setAllowsSendVendorSpecificResultCode(false);
@@ -279,7 +297,6 @@ public class ShadowBluetoothHeadsetTest {
}
@Test
- @Config(minSdk = KITKAT)
public void sendVendorSpecificResultCode_throwsOnNullCommand() {
try {
bluetoothHeadset.sendVendorSpecificResultCode(device1, null, "arg");
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothLeAdvertiserTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothLeAdvertiserTest.java
index cd8759cff..76358dc76 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothLeAdvertiserTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothLeAdvertiserTest.java
@@ -1,16 +1,29 @@
package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.O;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import static org.robolectric.Shadows.shadowOf;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGattServer;
+import android.bluetooth.BluetoothManager;
import android.bluetooth.le.AdvertiseCallback;
import android.bluetooth.le.AdvertiseData;
import android.bluetooth.le.AdvertiseSettings;
+import android.bluetooth.le.AdvertisingSet;
+import android.bluetooth.le.AdvertisingSetCallback;
+import android.bluetooth.le.AdvertisingSetParameters;
import android.bluetooth.le.BluetoothLeAdvertiser;
+import android.bluetooth.le.PeriodicAdvertisingParameters;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
import android.os.ParcelUuid;
+import androidx.test.core.app.ApplicationProvider;
+import java.util.Optional;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -31,6 +44,14 @@ public class ShadowBluetoothLeAdvertiserTest {
private static final String CALLBACK2_SUCCESS_RESULT = "c2s";
private static final String CALLBACK2_FAILURE_RESULT = "c2f";
+ private final Context context = ApplicationProvider.getApplicationContext();
+ private final BluetoothManager bluetoothManager =
+ context.getSystemService(BluetoothManager.class);
+ private final BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
+ private final ShadowBluetoothAdapter shadowBluetoothAdapter = shadowOf(bluetoothAdapter);
+ private final BluetoothGattServer gattServer = bluetoothManager.openGattServer(context, null);
+ private final Handler mainLooperHandler = new Handler(Looper.getMainLooper());
+
private BluetoothLeAdvertiser bluetoothLeAdvertiser;
private BluetoothLeAdvertiser bluetoothLeAdvertiserNameSet;
private AdvertiseSettings advertiseSettings1;
@@ -42,6 +63,32 @@ public class ShadowBluetoothLeAdvertiserTest {
private AdvertiseCallback advertiseCallback1;
private AdvertiseCallback advertiseCallback2;
+ private Optional<Integer> advertisingSetStartStatusOptional;
+ private boolean advertisingSetStopped;
+ private AdvertisingSetCallback advertisingSetCallback =
+ new AdvertisingSetCallback() {
+ @Override
+ public void onAdvertisingSetStarted(
+ AdvertisingSet advertisingSet, int txPower, int status) {
+ advertisingSetStartStatusOptional = Optional.of(status);
+ /* switch (status) {
+ case AdvertisingSetCallback.ADVERTISE_SUCCESS:
+ advertisingSetStartStatusOptional.
+ break;
+ case AdvertisingSetCallback.ADVERTISE_FAILED_DATA_TOO_LARGE:
+ break;
+ case AdvertisingSetCallback.ADVERTISE_FAILED_ALREADY_STARTED:
+ break;
+ default:
+ break; */
+ }
+
+ @Override
+ public void onAdvertisingSetStopped(AdvertisingSet advertisingSet) {
+ advertisingSetStopped = true;
+ }
+ };
+
private String result;
private int error;
private AdvertiseSettings settings;
@@ -123,6 +170,9 @@ public class ShadowBluetoothLeAdvertiserTest {
.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_LOW)
.setConnectable(false)
.build();
+
+ advertisingSetStartStatusOptional = Optional.empty();
+ advertisingSetStopped = false;
}
@Test
@@ -450,4 +500,346 @@ public class ShadowBluetoothLeAdvertiserTest {
bluetoothLeAdvertiser.stopAdvertising(advertiseCallback2);
assertThat(shadowOf(bluetoothLeAdvertiser).getAdvertisementRequestCount()).isEqualTo(1);
}
+
+ @Test
+ @Config(minSdk = UPSIDE_DOWN_CAKE)
+ public void startAdvertisingSet() {
+ bluetoothLeAdvertiser.startAdvertisingSet(
+ buildAdvertisingSetParams(
+ true, true, true, BluetoothDevice.PHY_LE_CODED, BluetoothDevice.PHY_LE_CODED),
+ advertiseData1,
+ null,
+ null,
+ null,
+ 0,
+ 0,
+ gattServer,
+ advertisingSetCallback,
+ mainLooperHandler);
+
+ assertThat(advertisingSetStartStatusOptional.get())
+ .isEqualTo(AdvertisingSetCallback.ADVERTISE_SUCCESS);
+ }
+
+ @Test
+ @Config(minSdk = UPSIDE_DOWN_CAKE)
+ public void
+ startAdvertisingSet_advertisingAlreadyStarted_invokeOnAdvertisingSetStartedWithAlreadyStartedStatusCode() {
+ bluetoothLeAdvertiser.startAdvertisingSet(
+ buildAdvertisingSetParams(
+ true, true, true, BluetoothDevice.PHY_LE_CODED, BluetoothDevice.PHY_LE_CODED),
+ advertiseData1,
+ null,
+ null,
+ null,
+ 0,
+ 0,
+ gattServer,
+ advertisingSetCallback,
+ mainLooperHandler);
+
+ bluetoothLeAdvertiser.startAdvertisingSet(
+ buildAdvertisingSetParams(
+ true, true, true, BluetoothDevice.PHY_LE_CODED, BluetoothDevice.PHY_LE_CODED),
+ advertiseData1,
+ null,
+ null,
+ null,
+ 0,
+ 0,
+ gattServer,
+ advertisingSetCallback,
+ mainLooperHandler);
+
+ assertThat(advertisingSetStartStatusOptional.get())
+ .isEqualTo(AdvertisingSetCallback.ADVERTISE_FAILED_ALREADY_STARTED);
+ }
+
+ @Test
+ @Config(minSdk = UPSIDE_DOWN_CAKE)
+ public void startAdvertisingSet_nullCallback_throwsIllegalArgumentException() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ bluetoothLeAdvertiser.startAdvertisingSet(
+ buildAdvertisingSetParams(
+ true, true, true, BluetoothDevice.PHY_LE_CODED, BluetoothDevice.PHY_LE_CODED),
+ advertiseData1,
+ null,
+ null,
+ null,
+ 0,
+ 0,
+ gattServer,
+ null,
+ mainLooperHandler));
+ }
+
+ @Test
+ @Config(minSdk = UPSIDE_DOWN_CAKE)
+ public void
+ startAdvertisingSet_legacyModeWithTooBigAdvertiseData_throwsIllegalArgumentException() {
+ AdvertiseData oversizedData =
+ new AdvertiseData.Builder()
+ .addServiceUuid(ParcelUuid.fromString("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF"))
+ .addServiceUuid(ParcelUuid.fromString("EEEEEEEE-FFFF-FFFF-FFFF-FFFFFFFFFFFF"))
+ .build();
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ bluetoothLeAdvertiser.startAdvertisingSet(
+ buildAdvertisingSetParams(
+ true, true, true, BluetoothDevice.PHY_LE_CODED, BluetoothDevice.PHY_LE_CODED),
+ oversizedData,
+ /* scanResponse= */ null,
+ /* periodicParameters= */ null,
+ /* periodicData= */ null,
+ /* duration= */ 0,
+ /* maxExtendedAdvertisingEvents= */ 0,
+ gattServer,
+ advertisingSetCallback,
+ mainLooperHandler));
+ }
+
+ @Test
+ @Config(minSdk = UPSIDE_DOWN_CAKE)
+ public void
+ startAdvertisingSet_legacyModeWithTooBigScanResponse_throwsIllegalArgumentException() {
+ AdvertiseData oversizedData =
+ new AdvertiseData.Builder()
+ .addServiceUuid(ParcelUuid.fromString("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF"))
+ .addServiceUuid(ParcelUuid.fromString("EEEEEEEE-FFFF-FFFF-FFFF-FFFFFFFFFFFF"))
+ .build();
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ bluetoothLeAdvertiser.startAdvertisingSet(
+ buildAdvertisingSetParams(
+ true, true, true, BluetoothDevice.PHY_LE_CODED, BluetoothDevice.PHY_LE_CODED),
+ advertiseData1,
+ oversizedData,
+ /* periodicParameters= */ null,
+ /* periodicData= */ null,
+ /* duration= */ 0,
+ /* maxExtendedAdvertisingEvents= */ 0,
+ gattServer,
+ advertisingSetCallback,
+ mainLooperHandler));
+ }
+
+ @Test
+ @Config(minSdk = UPSIDE_DOWN_CAKE)
+ public void
+ startAdvertisingSet_nonLegacyModePrimaryPhySetButNotSupported_throwsIllegalArgumentException() {
+ shadowBluetoothAdapter.setIsLeCodedPhySupported(false);
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ bluetoothLeAdvertiser.startAdvertisingSet(
+ buildAdvertisingSetParams(
+ false, true, false, BluetoothDevice.PHY_LE_CODED, BluetoothDevice.PHY_LE_1M),
+ advertiseData1,
+ /* scanResponse= */ null,
+ /* periodicParameters= */ null,
+ /* periodicData= */ null,
+ /* duration= */ 0,
+ /* maxExtendedAdvertisingEvents= */ 0,
+ gattServer,
+ advertisingSetCallback,
+ mainLooperHandler));
+ }
+
+ @Test
+ @Config(minSdk = UPSIDE_DOWN_CAKE)
+ public void
+ startAdvertisingSet_nonLegacyModeSecondaryPhySetButNotSupported_throwsIllegalArgumentException() {
+ shadowBluetoothAdapter.setIsLeCodedPhySupported(false);
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ bluetoothLeAdvertiser.startAdvertisingSet(
+ buildAdvertisingSetParams(
+ false, true, false, BluetoothDevice.PHY_LE_1M, BluetoothDevice.PHY_LE_CODED),
+ advertiseData1,
+ /* scanResponse= */ null,
+ /* periodicParameters= */ null,
+ /* periodicData= */ null,
+ /* duration= */ 0,
+ /* maxExtendedAdvertisingEvents= */ 0,
+ gattServer,
+ advertisingSetCallback,
+ mainLooperHandler));
+ }
+
+ @Test
+ @Config(minSdk = UPSIDE_DOWN_CAKE)
+ public void
+ startAdvertisingSet_nonLegacyModeWithTooBigAdvertiseData_throwsIllegalArgumentException() {
+ shadowBluetoothAdapter.setIsLeExtendedAdvertisingSupported(false);
+ AdvertiseData oversizedData =
+ new AdvertiseData.Builder()
+ .addServiceUuid(ParcelUuid.fromString("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF"))
+ .addServiceUuid(ParcelUuid.fromString("EEEEEEEE-FFFF-FFFF-FFFF-FFFFFFFFFFFF"))
+ .build();
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ bluetoothLeAdvertiser.startAdvertisingSet(
+ buildAdvertisingSetParams(
+ false, true, false, BluetoothDevice.PHY_LE_CODED, BluetoothDevice.PHY_LE_CODED),
+ oversizedData,
+ /* scanResponse= */ null,
+ /* periodicParameters= */ null,
+ /* periodicData= */ null,
+ /* duration= */ 0,
+ /* maxExtendedAdvertisingEvents= */ 0,
+ gattServer,
+ advertisingSetCallback,
+ mainLooperHandler));
+ }
+
+ @Test
+ @Config(minSdk = UPSIDE_DOWN_CAKE)
+ public void
+ startAdvertisingSet_nonLegacyModeWithTooBigScanResponse_throwsIllegalArgumentException() {
+ shadowBluetoothAdapter.setIsLeExtendedAdvertisingSupported(false);
+ AdvertiseData oversizedData =
+ new AdvertiseData.Builder()
+ .addServiceUuid(ParcelUuid.fromString("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF"))
+ .addServiceUuid(ParcelUuid.fromString("EEEEEEEE-FFFF-FFFF-FFFF-FFFFFFFFFFFF"))
+ .build();
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ bluetoothLeAdvertiser.startAdvertisingSet(
+ buildAdvertisingSetParams(
+ false, true, false, BluetoothDevice.PHY_LE_CODED, BluetoothDevice.PHY_LE_CODED),
+ advertiseData1,
+ oversizedData,
+ /* periodicParameters= */ null,
+ /* periodicData= */ null,
+ /* duration= */ 0,
+ /* maxExtendedAdvertisingEvents= */ 0,
+ gattServer,
+ advertisingSetCallback,
+ mainLooperHandler));
+ }
+
+ @Test
+ @Config(minSdk = UPSIDE_DOWN_CAKE)
+ public void
+ startAdvertisingSet_nonLegacyModeWithTooBigPeriodicData_throwsIllegalArgumentException() {
+ shadowBluetoothAdapter.setIsLeExtendedAdvertisingSupported(false);
+ AdvertiseData oversizedData =
+ new AdvertiseData.Builder()
+ .addServiceUuid(ParcelUuid.fromString("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF"))
+ .addServiceUuid(ParcelUuid.fromString("EEEEEEEE-FFFF-FFFF-FFFF-FFFFFFFFFFFF"))
+ .build();
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ bluetoothLeAdvertiser.startAdvertisingSet(
+ buildAdvertisingSetParams(
+ false, true, false, BluetoothDevice.PHY_LE_CODED, BluetoothDevice.PHY_LE_CODED),
+ advertiseData1,
+ /* scanResponse= */ null,
+ new PeriodicAdvertisingParameters.Builder().setInterval(1).build(),
+ oversizedData,
+ /* duration= */ 0,
+ /* maxExtendedAdvertisingEvents= */ 0,
+ gattServer,
+ advertisingSetCallback,
+ mainLooperHandler));
+ }
+
+ @Test
+ @Config(minSdk = UPSIDE_DOWN_CAKE)
+ public void
+ startAdvertisingSet_maxExtendedAdvertisingEventsOutOfRange_throwsIllegalArgumentException() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ bluetoothLeAdvertiser.startAdvertisingSet(
+ buildAdvertisingSetParams(
+ false, true, false, BluetoothDevice.PHY_LE_CODED, BluetoothDevice.PHY_LE_CODED),
+ advertiseData1,
+ /* scanResponse= */ null,
+ /* periodicParameters= */ null,
+ /* periodicData= */ null,
+ /* duration= */ 0,
+ -1,
+ gattServer,
+ advertisingSetCallback,
+ mainLooperHandler));
+ }
+
+ @Test
+ @Config(minSdk = UPSIDE_DOWN_CAKE)
+ public void startAdvertisingSet_durationOutOfRange_throwsIllegalArgumentException() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ bluetoothLeAdvertiser.startAdvertisingSet(
+ buildAdvertisingSetParams(
+ false, true, false, BluetoothDevice.PHY_LE_CODED, BluetoothDevice.PHY_LE_CODED),
+ advertiseData1,
+ /* scanResponse= */ null,
+ /* periodicParameters= */ null,
+ /* periodicData= */ null,
+ -1,
+ /* maxExtendedAdvertisingEvents= */ 0,
+ gattServer,
+ advertisingSetCallback,
+ mainLooperHandler));
+ }
+
+ @Test
+ @Config(minSdk = UPSIDE_DOWN_CAKE)
+ public void stopAdvertisingSet() {
+ bluetoothLeAdvertiser.startAdvertisingSet(
+ buildAdvertisingSetParams(
+ true, true, true, BluetoothDevice.PHY_LE_CODED, BluetoothDevice.PHY_LE_CODED),
+ advertiseData1,
+ null,
+ null,
+ null,
+ 0,
+ 0,
+ gattServer,
+ advertisingSetCallback,
+ mainLooperHandler);
+
+ bluetoothLeAdvertiser.stopAdvertisingSet(advertisingSetCallback);
+
+ assertThat(advertisingSetStopped).isTrue();
+ }
+
+ @Test
+ @Config(minSdk = UPSIDE_DOWN_CAKE)
+ public void stopAdvertisingSet_nullCallback_throwsIllegalArgumentException() {
+ assertThrows(
+ IllegalArgumentException.class, () -> bluetoothLeAdvertiser.stopAdvertisingSet(null));
+ }
+
+ private AdvertisingSetParameters buildAdvertisingSetParams(
+ boolean isLegacy,
+ boolean isConnectable,
+ boolean isScannable,
+ int primaryPhy,
+ int secondaryPhy) {
+ return new AdvertisingSetParameters.Builder()
+ .setLegacyMode(isLegacy)
+ .setConnectable(isConnectable)
+ .setScannable(isScannable)
+ .setPrimaryPhy(primaryPhy)
+ .setSecondaryPhy(secondaryPhy)
+ .build();
+ }
}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothLeScannerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothLeScannerTest.java
index f4a4d71cc..51b12b86f 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothLeScannerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothLeScannerTest.java
@@ -11,11 +11,14 @@ import android.bluetooth.BluetoothAdapter;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanRecord;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.Intent;
import android.os.ParcelUuid;
import androidx.test.core.app.ApplicationProvider;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
@@ -30,15 +33,27 @@ import org.robolectric.annotation.Config;
@RunWith(RobolectricTestRunner.class)
@Config(minSdk = LOLLIPOP)
public class ShadowBluetoothLeScannerTest {
+ private BluetoothAdapter adapter;
private BluetoothLeScanner bluetoothLeScanner;
private List<ScanFilter> scanFilters;
private ScanSettings scanSettings;
- private ScanCallback scanCallback;
private PendingIntent pendingIntent;
+ private static final class FakeScanCallback extends ScanCallback {
+ List<ScanResult> scanResults = new ArrayList<>();
+
+ @Override
+ public void onScanResult(int callbackType, ScanResult scanResult) {
+ assertThat(callbackType).isEqualTo(ScanSettings.CALLBACK_TYPE_ALL_MATCHES);
+ scanResults.add(scanResult);
+ }
+ }
+
+ private FakeScanCallback scanCallback;
+
@Before
public void setUp() throws Exception {
- BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ adapter = BluetoothAdapter.getDefaultAdapter();
if (RuntimeEnvironment.getApiLevel() < M) {
// On SDK < 23, bluetooth has to be in STATE_ON in order to get a BluetoothLeScanner.
shadowOf(adapter).setState(BluetoothAdapter.STATE_ON);
@@ -61,14 +76,7 @@ public class ShadowBluetoothLeScannerTest {
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
.setReportDelay(0)
.build();
- scanCallback =
- new ScanCallback() {
- @Override
- public void onScanResult(int callbackType, ScanResult scanResult) {}
-
- @Override
- public void onScanFailed(int errorCode) {}
- };
+ scanCallback = new FakeScanCallback();
pendingIntent =
PendingIntent.getBroadcast(
ApplicationProvider.getApplicationContext(), 0, new Intent("SCAN_CALLBACK"), 0);
@@ -159,4 +167,80 @@ public class ShadowBluetoothLeScannerTest {
assertThat(shadowOf(bluetoothLeScanner).getActiveScans().get(0).scanSettings())
.isEqualTo(scanSettings);
}
+
+ @Test
+ @Config(minSdk = O)
+ public void startScan_withScanResult_andNullFilters() {
+ ScanResult scanResultOne =
+ new ScanResult(
+ adapter.getRemoteDevice("AA:BB:CC:DD:EE:FF"),
+ /* eventType= */ 1,
+ /* primaryPhy= */ 1,
+ /* secondaryPhy= */ 1,
+ /* advertisingSid= */ 1,
+ /* txPower= */ 1,
+ /* rssi= */ 1,
+ /* periodicActivitySignal= */ 1,
+ /* scanRecord= */ ScanRecord.parseFromBytes(new byte[] {}),
+ /* timestamp= */ 1);
+ ScanResult scanResultTwo =
+ new ScanResult(
+ adapter.getRemoteDevice("BB:BB:CC:DD:EE:FF"),
+ /* eventType= */ 2,
+ /* primaryPhy= */ 2,
+ /* secondaryPhy= */ 2,
+ /* advertisingSid= */ 2,
+ /* txPower= */ 2,
+ /* rssi= */ 2,
+ /* periodicActivitySignal= */ 2,
+ /* scanRecord= */ ScanRecord.parseFromBytes(new byte[] {}),
+ /* timestamp= */ 2);
+
+ ShadowBluetoothLeScanner shadowBluetoothLeScanner = shadowOf(bluetoothLeScanner);
+ shadowBluetoothLeScanner.addScanResult(scanResultOne);
+ shadowBluetoothLeScanner.addScanResult(scanResultTwo);
+
+ bluetoothLeScanner.startScan(/* filters= */ null, /* settings= */ null, scanCallback);
+
+ assertThat(scanCallback.scanResults).containsExactly(scanResultOne, scanResultTwo);
+ }
+
+ @Test
+ @Config(minSdk = O)
+ public void startScan_withScanResult_andFilter() {
+ String addressOne = "AA:BB:CC:DD:EE:FF";
+ ScanResult scanResultOne =
+ new ScanResult(
+ adapter.getRemoteDevice(addressOne),
+ /* eventType= */ 1,
+ /* primaryPhy= */ 1,
+ /* secondaryPhy= */ 1,
+ /* advertisingSid= */ 1,
+ /* txPower= */ 1,
+ /* rssi= */ 1,
+ /* periodicActivitySignal= */ 1,
+ /* scanRecord= */ ScanRecord.parseFromBytes(new byte[] {}),
+ /* timestamp= */ 1);
+ ScanResult scanResultTwo =
+ new ScanResult(
+ adapter.getRemoteDevice("BB:BB:CC:DD:EE:FF"),
+ /* eventType= */ 2,
+ /* primaryPhy= */ 2,
+ /* secondaryPhy= */ 2,
+ /* advertisingSid= */ 2,
+ /* txPower= */ 2,
+ /* rssi= */ 2,
+ /* periodicActivitySignal= */ 2,
+ /* scanRecord= */ ScanRecord.parseFromBytes(new byte[] {}),
+ /* timestamp= */ 2);
+
+ ShadowBluetoothLeScanner shadowBluetoothLeScanner = shadowOf(bluetoothLeScanner);
+ shadowBluetoothLeScanner.addScanResult(scanResultOne);
+ shadowBluetoothLeScanner.addScanResult(scanResultTwo);
+
+ ScanFilter filter = new ScanFilter.Builder().setDeviceAddress(addressOne).build();
+ bluetoothLeScanner.startScan(Arrays.asList(filter), /* settings= */ null, scanCallback);
+
+ assertThat(scanCallback.scanResults).containsExactly(scanResultOne);
+ }
}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothManagerTest.java
index 5e9848ae2..4fb52e0e3 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothManagerTest.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.R;
import static android.os.Build.VERSION_CODES.S;
@@ -25,7 +24,6 @@ import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
@RunWith(AndroidJUnit4.class)
-@Config(minSdk = JELLY_BEAN_MR2)
public class ShadowBluetoothManagerTest {
private static final String DEVICE_ADDRESS_1 = "00:11:22:AA:BB:CC";
private static final String DEVICE_ADDRESS_2 = "11:22:33:BB:CC:DD";
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBuildTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBuildTest.java
index 88ee76029..62e25aba5 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBuildTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBuildTest.java
@@ -1,5 +1,6 @@
package org.robolectric.shadows;
+import static android.os.Build.VERSION_CODES.L;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.R;
@@ -132,6 +133,30 @@ public class ShadowBuildTest {
assertThat(Build.getSerial()).isEqualTo("robo_serial");
}
+ @Test
+ @Config(minSdk = L)
+ public void supported32BitAbis() {
+ assertThat(Build.SUPPORTED_32_BIT_ABIS).isEqualTo(new String[] {"armeabi-v7a", "armeabi"});
+ ShadowBuild.setSupported32BitAbis(new String[] {"x86"});
+ assertThat(Build.SUPPORTED_32_BIT_ABIS).isEqualTo(new String[] {"x86"});
+ }
+
+ @Test
+ @Config(minSdk = L)
+ public void supported64BitAbis() {
+ assertThat(Build.SUPPORTED_64_BIT_ABIS).isEqualTo(new String[] {"armeabi-v7a", "armeabi"});
+ ShadowBuild.setSupported64BitAbis(new String[] {"x86_64"});
+ assertThat(Build.SUPPORTED_64_BIT_ABIS).isEqualTo(new String[] {"x86_64"});
+ }
+
+ @Test
+ @Config(minSdk = L)
+ public void supportedAbis() {
+ assertThat(Build.SUPPORTED_ABIS).isEqualTo(new String[] {"armeabi-v7a"});
+ ShadowBuild.setSupportedAbis(new String[] {"x86"});
+ assertThat(Build.SUPPORTED_ABIS).isEqualTo(new String[] {"x86"});
+ }
+
/** Verifies that each test gets a fresh set of Build values. */
private void checkValues() {
assertThat(Build.FINGERPRINT).isEqualTo("robolectric");
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowCameraTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowCameraTest.java
index 0c5297088..c79d135a2 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowCameraTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowCameraTest.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static com.google.common.truth.Truth.assertThat;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.fail;
@@ -17,7 +16,6 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Shadows;
-import org.robolectric.annotation.Config;
@RunWith(AndroidJUnit4.class)
public class ShadowCameraTest {
@@ -279,7 +277,6 @@ public class ShadowCameraTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void testCameraInfoShutterSound() {
Camera.CameraInfo cameraQueryCannotDisable = new Camera.CameraInfo();
Camera.CameraInfo cameraInfoCannotDisable = new Camera.CameraInfo();
@@ -313,7 +310,6 @@ public class ShadowCameraTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void testShutterEnabled() {
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
cameraInfo.facing = Camera.CameraInfo.CAMERA_FACING_BACK;
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowConfigurationTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowConfigurationTest.java
index 79de3de40..9e0ea9e70 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowConfigurationTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowConfigurationTest.java
@@ -4,13 +4,11 @@ import static android.content.res.Configuration.SCREENLAYOUT_UNDEFINED;
import static com.google.common.truth.Truth.assertThat;
import android.content.res.Configuration;
-import android.os.Build;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.util.Locale;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.annotation.Config;
@RunWith(AndroidJUnit4.class)
public class ShadowConfigurationTest {
@@ -30,7 +28,6 @@ public class ShadowConfigurationTest {
}
@Test
- @Config(minSdk = Build.VERSION_CODES.JELLY_BEAN_MR1)
public void testSetLocale() {
configuration.setLocale( Locale.US );
assertThat(configuration.locale).isEqualTo(Locale.US);
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowConnectivityManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowConnectivityManagerTest.java
index b224e4dfb..5fbe161d6 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowConnectivityManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowConnectivityManagerTest.java
@@ -3,7 +3,6 @@ package org.robolectric.shadows;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.N;
@@ -72,7 +71,7 @@ public class ShadowConnectivityManagerTest {
}
@Test
- public void getNetworkInfo_shouldReturnDefaultNetworks() throws Exception {
+ public void getNetworkInfo_shouldReturnDefaultNetworks() {
NetworkInfo wifi = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
assertThat(wifi.getDetailedState()).isEqualTo(NetworkInfo.DetailedState.DISCONNECTED);
@@ -80,8 +79,9 @@ public class ShadowConnectivityManagerTest {
assertThat(mobile.getDetailedState()).isEqualTo(NetworkInfo.DetailedState.CONNECTED);
}
- @Test @Config(minSdk = LOLLIPOP)
- public void getNetworkInfo_shouldReturnSomeForAllNetworks() throws Exception {
+ @Test
+ @Config(minSdk = LOLLIPOP)
+ public void getNetworkInfo_shouldReturnSomeForAllNetworks() {
Network[] allNetworks = connectivityManager.getAllNetworks();
for (Network network: allNetworks) {
NetworkInfo networkInfo = connectivityManager.getNetworkInfo(network);
@@ -89,8 +89,9 @@ public class ShadowConnectivityManagerTest {
}
}
- @Test @Config(minSdk = LOLLIPOP)
- public void getNetworkInfo_shouldReturnAddedNetwork() throws Exception {
+ @Test
+ @Config(minSdk = LOLLIPOP)
+ public void getNetworkInfo_shouldReturnAddedNetwork() {
Network vpnNetwork = ShadowNetwork.newInstance(123);
NetworkInfo vpnNetworkInfo =
ShadowNetworkInfo.newInstance(
@@ -105,8 +106,9 @@ public class ShadowConnectivityManagerTest {
assertThat(returnedNetworkInfo).isSameInstanceAs(vpnNetworkInfo);
}
- @Test @Config(minSdk = LOLLIPOP)
- public void getNetworkInfo_shouldNotReturnRemovedNetwork() throws Exception {
+ @Test
+ @Config(minSdk = LOLLIPOP)
+ public void getNetworkInfo_shouldNotReturnRemovedNetwork() {
Network wifiNetwork = ShadowNetwork.newInstance(ShadowConnectivityManager.NET_ID_WIFI);
shadowOf(connectivityManager).removeNetwork(wifiNetwork);
@@ -124,14 +126,14 @@ public class ShadowConnectivityManagerTest {
}
@Test
- public void shouldGetAndSetBackgroundDataSetting() throws Exception {
+ public void shouldGetAndSetBackgroundDataSetting() {
assertThat(connectivityManager.getBackgroundDataSetting()).isFalse();
shadowOf(connectivityManager).setBackgroundDataSetting(true);
assertThat(connectivityManager.getBackgroundDataSetting()).isTrue();
}
@Test
- public void setActiveNetworkInfo_shouldSetActiveNetworkInfo() throws Exception {
+ public void setActiveNetworkInfo_shouldSetActiveNetworkInfo() {
shadowOf(connectivityManager).setActiveNetworkInfo(null);
assertThat(connectivityManager.getActiveNetworkInfo()).isNull();
shadowOf(connectivityManager)
@@ -166,7 +168,7 @@ public class ShadowConnectivityManagerTest {
@Test
@Config(minSdk = M)
- public void setActiveNetworkInfo_shouldSetActiveNetwork() throws Exception {
+ public void setActiveNetworkInfo_shouldSetActiveNetwork() {
shadowOf(connectivityManager).setActiveNetworkInfo(null);
assertThat(connectivityManager.getActiveNetworkInfo()).isNull();
shadowOf(connectivityManager)
@@ -188,7 +190,7 @@ public class ShadowConnectivityManagerTest {
}
@Test
- public void getAllNetworkInfo_shouldReturnAllNetworkInterfaces() throws Exception {
+ public void getAllNetworkInfo_shouldReturnAllNetworkInterfaces() {
NetworkInfo[] infos = connectivityManager.getAllNetworkInfo();
assertThat(infos).asList().hasSize(2);
assertThat(infos).asList().contains(connectivityManager.getActiveNetworkInfo());
@@ -199,7 +201,7 @@ public class ShadowConnectivityManagerTest {
@Test
@Config(minSdk = LOLLIPOP)
- public void getAllNetworkInfo_shouldEqualGetAllNetworks() throws Exception {
+ public void getAllNetworkInfo_shouldEqualGetAllNetworks() {
// Update the active network so that we're no longer in the default state.
NetworkInfo networkInfo =
ShadowNetworkInfo.newInstance(
@@ -227,21 +229,24 @@ public class ShadowConnectivityManagerTest {
assertThat(connectivityManager.getAllNetworkInfo()).isNull();
}
- @Test @Config(minSdk = LOLLIPOP)
- public void getAllNetworks_shouldReturnAllNetworks() throws Exception {
+ @Test
+ @Config(minSdk = LOLLIPOP)
+ public void getAllNetworks_shouldReturnAllNetworks() {
Network[] networks = connectivityManager.getAllNetworks();
assertThat(networks).asList().hasSize(2);
}
- @Test @Config(minSdk = LOLLIPOP)
- public void getAllNetworks_shouldReturnNoNetworksWhenCleared() throws Exception {
+ @Test
+ @Config(minSdk = LOLLIPOP)
+ public void getAllNetworks_shouldReturnNoNetworksWhenCleared() {
shadowOf(connectivityManager).clearAllNetworks();
Network[] networks = connectivityManager.getAllNetworks();
assertThat(networks).isEmpty();
}
- @Test @Config(minSdk = LOLLIPOP)
- public void getAllNetworks_shouldReturnAddedNetworks() throws Exception {
+ @Test
+ @Config(minSdk = LOLLIPOP)
+ public void getAllNetworks_shouldReturnAddedNetworks() {
// Let's start clear.
shadowOf(connectivityManager).clearAllNetworks();
@@ -266,8 +271,9 @@ public class ShadowConnectivityManagerTest {
assertThat(returnedNetworkInfo).isSameInstanceAs(vpnNetworkInfo);
}
- @Test @Config(minSdk = LOLLIPOP)
- public void getAllNetworks_shouldNotReturnRemovedNetworks() throws Exception {
+ @Test
+ @Config(minSdk = LOLLIPOP)
+ public void getAllNetworks_shouldNotReturnRemovedNetworks() {
Network wifiNetwork = ShadowNetwork.newInstance(ShadowConnectivityManager.NET_ID_WIFI);
shadowOf(connectivityManager).removeNetwork(wifiNetwork);
@@ -280,13 +286,13 @@ public class ShadowConnectivityManagerTest {
}
@Test
- public void getNetworkPreference_shouldGetDefaultValue() throws Exception {
+ public void getNetworkPreference_shouldGetDefaultValue() {
assertThat(connectivityManager.getNetworkPreference()).isEqualTo(ConnectivityManager.DEFAULT_NETWORK_PREFERENCE);
}
@Test
@Config(minSdk = M)
- public void getReportedNetworkConnectivity() throws Exception {
+ public void getReportedNetworkConnectivity() {
Network wifiNetwork = ShadowNetwork.newInstance(ShadowConnectivityManager.NET_ID_WIFI);
connectivityManager.reportNetworkConnectivity(wifiNetwork, true);
@@ -303,15 +309,16 @@ public class ShadowConnectivityManagerTest {
}
@Test
- public void setNetworkPreference_shouldSetDefaultValue() throws Exception {
+ public void setNetworkPreference_shouldSetDefaultValue() {
connectivityManager.setNetworkPreference(ConnectivityManager.TYPE_MOBILE);
assertThat(connectivityManager.getNetworkPreference()).isEqualTo(connectivityManager.getNetworkPreference());
connectivityManager.setNetworkPreference(ConnectivityManager.TYPE_WIFI);
assertThat(connectivityManager.getNetworkPreference()).isEqualTo(ConnectivityManager.TYPE_WIFI);
}
- @Test @Config(minSdk = LOLLIPOP)
- public void getNetworkCallbacks_shouldHaveEmptyDefault() throws Exception {
+ @Test
+ @Config(minSdk = LOLLIPOP)
+ public void getNetworkCallbacks_shouldHaveEmptyDefault() {
assertThat(shadowOf(connectivityManager).getNetworkCallbacks()).isEmpty();
}
@@ -330,15 +337,16 @@ public class ShadowConnectivityManagerTest {
@Test
@Config(minSdk = LOLLIPOP)
- public void requestNetwork_shouldAddCallback() throws Exception {
+ public void requestNetwork_shouldAddCallback() {
NetworkRequest.Builder builder = new NetworkRequest.Builder();
ConnectivityManager.NetworkCallback callback = createSimpleCallback();
connectivityManager.requestNetwork(builder.build(), callback);
assertThat(shadowOf(connectivityManager).getNetworkCallbacks()).hasSize(1);
}
- @Test @Config(minSdk = LOLLIPOP)
- public void registerCallback_shouldAddCallback() throws Exception {
+ @Test
+ @Config(minSdk = LOLLIPOP)
+ public void registerCallback_shouldAddCallback() {
NetworkRequest.Builder builder = new NetworkRequest.Builder();
ConnectivityManager.NetworkCallback callback = createSimpleCallback();
connectivityManager.registerNetworkCallback(builder.build(), callback);
@@ -347,7 +355,7 @@ public class ShadowConnectivityManagerTest {
@Test
@Config(minSdk = M)
- public void registerCallback_withPendingIntent_shouldAddCallback() throws Exception {
+ public void registerCallback_withPendingIntent_shouldAddCallback() {
NetworkRequest.Builder builder = new NetworkRequest.Builder();
PendingIntent pendingIntent = createSimplePendingIntent();
connectivityManager.registerNetworkCallback(builder.build(), pendingIntent);
@@ -356,7 +364,7 @@ public class ShadowConnectivityManagerTest {
@Test
@Config(minSdk = O)
- public void requestNetwork_withTimeout_shouldAddCallback() throws Exception {
+ public void requestNetwork_withTimeout_shouldAddCallback() {
NetworkRequest.Builder builder = new NetworkRequest.Builder();
ConnectivityManager.NetworkCallback callback = createSimpleCallback();
connectivityManager.requestNetwork(builder.build(), callback, 0);
@@ -365,7 +373,7 @@ public class ShadowConnectivityManagerTest {
@Test
@Config(minSdk = O)
- public void requestNetwork_withHandler_shouldAddCallback() throws Exception {
+ public void requestNetwork_withHandler_shouldAddCallback() {
NetworkRequest.Builder builder = new NetworkRequest.Builder();
ConnectivityManager.NetworkCallback callback = createSimpleCallback();
connectivityManager.requestNetwork(builder.build(), callback, new Handler());
@@ -374,7 +382,7 @@ public class ShadowConnectivityManagerTest {
@Test
@Config(minSdk = O)
- public void requestNetwork_withHandlerAndTimer_shouldAddCallback() throws Exception {
+ public void requestNetwork_withHandlerAndTimer_shouldAddCallback() {
NetworkRequest.Builder builder = new NetworkRequest.Builder();
ConnectivityManager.NetworkCallback callback = createSimpleCallback();
connectivityManager.requestNetwork(builder.build(), callback, new Handler(), 0);
@@ -383,14 +391,15 @@ public class ShadowConnectivityManagerTest {
@Test
@Config(minSdk = N)
- public void registerDefaultCallback_shouldAddCallback() throws Exception {
+ public void registerDefaultCallback_shouldAddCallback() {
ConnectivityManager.NetworkCallback callback = createSimpleCallback();
connectivityManager.registerDefaultNetworkCallback(callback);
assertThat(shadowOf(connectivityManager).getNetworkCallbacks()).hasSize(1);
}
- @Test @Config(minSdk = LOLLIPOP)
- public void unregisterCallback_shouldRemoveCallbacks() throws Exception {
+ @Test
+ @Config(minSdk = LOLLIPOP)
+ public void unregisterCallback_shouldRemoveCallbacks() {
NetworkRequest.Builder builder = new NetworkRequest.Builder();
// Add two different callbacks.
ConnectivityManager.NetworkCallback callback1 = createSimpleCallback();
@@ -407,7 +416,7 @@ public class ShadowConnectivityManagerTest {
@Test
@Config(minSdk = M)
- public void unregisterCallback_withPendingIntent_shouldRemoveCallbacks() throws Exception {
+ public void unregisterCallback_withPendingIntent_shouldRemoveCallbacks() {
NetworkRequest.Builder builder = new NetworkRequest.Builder();
// Add two pendingIntents, should treat them as equal based on Intent#filterEquals
PendingIntent pendingIntent1 = createSimplePendingIntent();
@@ -420,15 +429,20 @@ public class ShadowConnectivityManagerTest {
assertThat(shadowOf(connectivityManager).getNetworkCallbackPendingIntents()).isEmpty();
}
- @Test(expected=IllegalArgumentException.class) @Config(minSdk = LOLLIPOP)
- public void unregisterCallback_shouldNotAllowNullCallback() throws Exception {
+ @Config(minSdk = LOLLIPOP)
+ @Test
+ public void unregisterCallback_shouldNotAllowNullCallback() {
// Verify that exception is thrown.
- connectivityManager.unregisterNetworkCallback((ConnectivityManager.NetworkCallback) null);
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ connectivityManager.unregisterNetworkCallback(
+ (ConnectivityManager.NetworkCallback) null));
}
- @Test
@Config(minSdk = M)
- public void unregisterCallback_withPendingIntent_shouldNotAllowNullCallback() throws Exception {
+ @Test
+ public void unregisterCallback_withPendingIntent_shouldNotAllowNullCallback() {
// Verify that exception is thrown.
assertThrows(
IllegalArgumentException.class,
@@ -496,7 +510,7 @@ public class ShadowConnectivityManagerTest {
@Test
@Config(minSdk = LOLLIPOP)
- public void addDefaultNetworkActiveListener_shouldAddListener() throws Exception {
+ public void addDefaultNetworkActiveListener_shouldAddListener() {
ConnectivityManager.OnNetworkActiveListener listener1 =
spy(createSimpleOnNetworkActiveListener());
ConnectivityManager.OnNetworkActiveListener listener2 =
@@ -512,7 +526,7 @@ public class ShadowConnectivityManagerTest {
@Test
@Config(minSdk = LOLLIPOP)
- public void removeDefaultNetworkActiveListener_shouldRemoveListeners() throws Exception {
+ public void removeDefaultNetworkActiveListener_shouldRemoveListeners() {
// Add two different callbacks.
ConnectivityManager.OnNetworkActiveListener listener1 =
spy(createSimpleOnNetworkActiveListener());
@@ -541,16 +555,18 @@ public class ShadowConnectivityManagerTest {
verify(listener2).onNetworkActive();
}
- @Test(expected = IllegalArgumentException.class)
@Config(minSdk = LOLLIPOP)
- public void removeDefaultNetworkActiveListener_shouldNotAllowNullListener() throws Exception {
+ @Test
+ public void removeDefaultNetworkActiveListener_shouldNotAllowNullListener() {
// Verify that exception is thrown.
- connectivityManager.removeDefaultNetworkActiveListener(null);
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> connectivityManager.removeDefaultNetworkActiveListener(null));
}
@Test
@Config(minSdk = LOLLIPOP)
- public void getNetworkCapabilities() throws Exception {
+ public void getNetworkCapabilities() {
NetworkCapabilities nc = ShadowNetworkCapabilities.newInstance();
shadowOf(nc).addCapability(NetworkCapabilities.NET_CAPABILITY_MMS);
@@ -566,7 +582,7 @@ public class ShadowConnectivityManagerTest {
@Test
@Config(minSdk = LOLLIPOP)
- public void getNetworkCapabilities_shouldReturnDefaultCapabilities() throws Exception {
+ public void getNetworkCapabilities_shouldReturnDefaultCapabilities() {
for (Network network : connectivityManager.getAllNetworks()) {
NetworkCapabilities nc = connectivityManager.getNetworkCapabilities(network);
assertThat(nc).isNotNull();
@@ -594,7 +610,6 @@ public class ShadowConnectivityManagerTest {
}
@Test
- @Config(minSdk = KITKAT)
public void setAirplaneMode() {
connectivityManager.setAirplaneMode(false);
assertThat(
@@ -650,20 +665,24 @@ public class ShadowConnectivityManagerTest {
.isEqualTo(RESTRICT_BACKGROUND_STATUS_DISABLED);
}
- @Test(expected = IllegalArgumentException.class)
@Config(minSdk = N)
- public void setRestrictBackgroundStatus_throwsExceptionOnIncorrectStatus0() throws Exception{
- shadowOf(connectivityManager).setRestrictBackgroundStatus(0);
+ @Test
+ public void setRestrictBackgroundStatus_throwsExceptionOnIncorrectStatus0() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> shadowOf(connectivityManager).setRestrictBackgroundStatus(0));
}
- @Test(expected = IllegalArgumentException.class)
@Config(minSdk = N)
- public void setRestrictBackgroundStatus_throwsExceptionOnIncorrectStatus4() throws Exception{
- shadowOf(connectivityManager).setRestrictBackgroundStatus(4);
+ @Test
+ public void setRestrictBackgroundStatus_throwsExceptionOnIncorrectStatus4() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> shadowOf(connectivityManager).setRestrictBackgroundStatus(4));
}
@Test
- public void checkPollingTetherThreadNotCreated() throws Exception {
+ public void checkPollingTetherThreadNotCreated() {
for (StackTraceElement[] elements : Thread.getAllStackTraces().values()) {
for (StackTraceElement element : elements) {
if (element.toString().contains("android.net.TetheringManager")) {
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowContentProviderTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowContentProviderTest.java
index ebfaf5735..daa36e5b2 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowContentProviderTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowContentProviderTest.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 static org.robolectric.Shadows.shadowOf;
@@ -8,12 +7,10 @@ import android.content.ContentProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.annotation.Config;
import org.robolectric.shadows.testing.TestContentProvider1;
@RunWith(AndroidJUnit4.class)
public class ShadowContentProviderTest {
- @Config(minSdk = KITKAT)
@Test
public void testSetCallingPackage() {
ContentProvider provider = new TestContentProvider1();
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowContentResolverTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowContentResolverTest.java
index 6e2e2d03f..94a19debb 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowContentResolverTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowContentResolverTest.java
@@ -3,7 +3,6 @@ package org.robolectric.shadows;
import static android.content.ContentResolver.QUERY_ARG_SQL_SELECTION;
import static android.content.ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS;
import static android.content.ContentResolver.QUERY_ARG_SQL_SORT_ORDER;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.O;
import static android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
import static com.google.common.truth.Truth.assertThat;
@@ -764,11 +763,6 @@ public class ShadowContentResolverTest {
assertThat(resultOperations).isNotNull();
assertThat(resultOperations.size()).isEqualTo(0);
- ContentProviderResult[] contentProviderResults =
- new ContentProviderResult[] {
- new ContentProviderResult(1), new ContentProviderResult(1),
- };
- shadowContentResolver.setContentProviderResult(contentProviderResults);
Uri uri = Uri.parse("content://org.robolectric");
ArrayList<ContentProviderOperation> operations = new ArrayList<>();
operations.add(
@@ -789,7 +783,7 @@ public class ShadowContentResolverTest {
resultOperations = shadowContentResolver.getContentProviderOperations(AUTHORITY);
assertThat(resultOperations).isEqualTo(operations);
- assertThat(result).isEqualTo(contentProviderResults);
+ assertThat(result).isNotNull();
}
@Test
@@ -1167,7 +1161,6 @@ public class ShadowContentResolverTest {
}
@Test
- @Config(minSdk = KITKAT)
public void takeAndReleasePersistableUriPermissions() {
List<UriPermission> permissions = contentResolver.getPersistedUriPermissions();
assertThat(permissions).isEmpty();
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowContextImplTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowContextImplTest.java
index 2ff62ac04..647307155 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowContextImplTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowContextImplTest.java
@@ -1,7 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.N;
@@ -87,26 +85,6 @@ public class ShadowContextImplTest {
.isFalse();
}
- @Config(maxSdk = JELLY_BEAN_MR2)
- @Test
- public void getExternalFilesDir_withType_returnFolderWithGivenTypeName() {
- File file = context.getExternalFilesDir("something");
- assertThat(file.isDirectory()).isTrue();
- assertThat(file.canWrite()).isTrue();
- assertThat(file.getName()).isEqualTo("something");
- assertThat(file.getParentFile().getName()).isEqualTo(context.getPackageName());
- }
-
- @Config(maxSdk = JELLY_BEAN_MR2)
- @Test
- public void getExternalFilesDir_withNullType_returnFolderWithPackageName() {
- File file = context.getExternalFilesDir(null);
- assertThat(file.isDirectory()).isTrue();
- assertThat(file.canWrite()).isTrue();
- assertThat(file.getName()).isEqualTo(context.getPackageName());
- }
-
- @Config(minSdk = KITKAT)
@Test
public void getExternalFilesDirs_withType_returnFolderWithGivenTypeName() {
File[] dirs = context.getExternalFilesDirs("something");
@@ -117,7 +95,6 @@ public class ShadowContextImplTest {
assertThat(dirs[0].getParentFile().getName()).isEqualTo(context.getPackageName());
}
- @Config(minSdk = KITKAT)
@Test
public void getExternalFilesDirs_withNullType_returnFolderWithPackageName() {
File[] dirs = context.getExternalFilesDirs(null);
@@ -128,7 +105,6 @@ public class ShadowContextImplTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR2)
public void getSystemService_shouldReturnBluetoothAdapter() {
assertThat(context.getSystemService(Context.BLUETOOTH_SERVICE))
.isInstanceOf(BluetoothManager.class);
@@ -352,7 +328,6 @@ public class ShadowContextImplTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void sendBroadcastAsUser_sendBroadcast() {
UserHandle userHandle = Process.myUserHandle();
String action = "foo-action";
@@ -365,7 +340,6 @@ public class ShadowContextImplTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void sendOrderedBroadcastAsUser_sendsBroadcast() {
UserHandle userHandle = Process.myUserHandle();
String action = "foo-action";
@@ -409,13 +383,11 @@ public class ShadowContextImplTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR2)
public void getUserId_returns0() {
assertThat(context.getUserId()).isEqualTo(0);
}
@Test
- @Config(minSdk = JELLY_BEAN_MR2)
public void getUserId_userIdHasBeenSet_returnsCorrectUserId() {
int userId = 10;
shadowContext.setUserId(userId);
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowContextTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowContextTest.java
index b93dc589d..51f71d358 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowContextTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowContextTest.java
@@ -1,7 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.P;
import static com.google.common.truth.Truth.assertThat;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -34,7 +32,6 @@ public class ShadowContextTest {
private final Context context = ApplicationProvider.getApplicationContext();
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void createConfigurationContext() {
Configuration configuration = new Configuration(context.getResources().getConfiguration());
configuration.mcc = 234;
@@ -121,13 +118,11 @@ public class ShadowContextTest {
}
@Test
- @Config(minSdk = KITKAT)
public void getExternalCacheDirs_nonEmpty() {
assertThat(context.getExternalCacheDirs()).isNotEmpty();
}
@Test
- @Config(minSdk = KITKAT)
public void getExternalCacheDirs_createsDirectories() {
File[] externalCacheDirs = context.getExternalCacheDirs();
for (File d : externalCacheDirs) {
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowContextWrapperTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowContextWrapperTest.java
index 03d9d732c..502e1a4be 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowContextWrapperTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowContextWrapperTest.java
@@ -2,7 +2,6 @@ package org.robolectric.shadows;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.P;
import static com.google.common.truth.Truth.assertThat;
@@ -317,7 +316,6 @@ public class ShadowContextWrapperTest {
}
@Test
- @Config(minSdk = KITKAT)
public void sendOrderedBroadcastAsUser_shouldReturnValues() throws Exception {
String action = "test";
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowDateUtilsTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowDateUtilsTest.java
index 5cc9e8bff..77826d059 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowDateUtilsTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowDateUtilsTest.java
@@ -1,7 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
-import static android.os.Build.VERSION_CODES.KITKAT;
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;
@@ -31,8 +29,8 @@ public class ShadowDateUtilsTest {
}
@Test
- @Config(minSdk = KITKAT, maxSdk = LOLLIPOP_MR1)
- public void formatDateTime_withCurrentYear_worksSinceKitKat() {
+ @Config(maxSdk = LOLLIPOP_MR1)
+ public void formatDateTime_withCurrentYear() {
final long millisAtStartOfYear = getMillisAtStartOfYear();
String actual =
@@ -57,18 +55,6 @@ public class ShadowDateUtilsTest {
}
@Test
- @Config(maxSdk = JELLY_BEAN_MR2)
- public void formatDateTime_withCurrentYear_worksPreKitKat() {
- Calendar calendar = Calendar.getInstance();
- final int currentYear = calendar.get(Calendar.YEAR);
- final long millisAtStartOfYear = getMillisAtStartOfYear();
-
- String actual =
- DateUtils.formatDateTime(context, millisAtStartOfYear, DateUtils.FORMAT_NUMERIC_DATE);
- assertThat(actual).isEqualTo("1/1/" + currentYear);
- }
-
- @Test
public void formatDateTime_withPastYear() {
String actual =
DateUtils.formatDateTime(context, 1420099200000L, DateUtils.FORMAT_NUMERIC_DATE);
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowDevicePolicyManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowDevicePolicyManagerTest.java
index af4194a89..0767e8b3d 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowDevicePolicyManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowDevicePolicyManagerTest.java
@@ -18,7 +18,6 @@ import static android.app.admin.DevicePolicyManager.STATE_USER_SETUP_COMPLETE;
import static android.app.admin.DevicePolicyManager.STATE_USER_SETUP_FINALIZED;
import static android.app.admin.DevicePolicyManager.STATE_USER_SETUP_INCOMPLETE;
import static android.app.admin.DevicePolicyManager.STATE_USER_UNMANAGED;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
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;
@@ -98,7 +97,6 @@ public final class ShadowDevicePolicyManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR2)
public void isDeviceOwnerAppShouldReturnFalseForNonDeviceOwnerApp() {
// GIVEN an test package which is not the device owner app of the device
String testPackage = testComponent.getPackageName();
@@ -121,7 +119,6 @@ public final class ShadowDevicePolicyManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR2)
public void isDeviceOwnerShouldReturnTrueForDeviceOwner() {
// GIVEN an test package which is the device owner app of the device
String testPackage = testComponent.getPackageName();
@@ -133,7 +130,6 @@ public final class ShadowDevicePolicyManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR2)
public void getDeviceOwnerShouldReturnDeviceOwnerPackageName() {
// GIVEN an test package which is the device owner app of the device
String testPackage = testComponent.getPackageName();
@@ -145,7 +141,6 @@ public final class ShadowDevicePolicyManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR2)
public void getDeviceOwnerShouldReturnNullWhenThereIsNoDeviceOwner() {
// WHEN DevicePolicyManager#getProfileOwner is called without a device owner
// THEN the method should return null
@@ -291,7 +286,6 @@ public final class ShadowDevicePolicyManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR2)
public void getActiveAdminsShouldReturnDeviceOwner() {
// GIVEN an test package which is the device owner app of the device
shadowOf(devicePolicyManager).setDeviceOwner(testComponent);
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowDialogTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowDialogTest.java
index f1270f3d8..e5561e9f7 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowDialogTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowDialogTest.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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -25,7 +24,6 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.R;
-import org.robolectric.annotation.Config;
@RunWith(AndroidJUnit4.class)
public class ShadowDialogTest {
@@ -186,7 +184,6 @@ public class ShadowDialogTest {
}
@Test
- @Config(minSdk = KITKAT)
public void show_shouldWorkWithAPI19() {
Dialog dialog = new Dialog(context);
dialog.show();
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayEventReceiverTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayEventReceiverTest.java
index d3bc403af..9a5f4538f 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayEventReceiverTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayEventReceiverTest.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static com.google.common.truth.Truth.assertThat;
import android.os.Looper;
@@ -10,7 +9,6 @@ import dalvik.system.CloseGuard;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.annotation.Config;
/** Tests for {@link ShadowDisplayEventReceiver}. */
@RunWith(AndroidJUnit4.class)
@@ -29,7 +27,6 @@ public class ShadowDisplayEventReceiverTest {
}
@Test
- @Config(minSdk = JELLY_BEAN)
public void closeGuard_autoCloses() throws Throwable {
final AtomicBoolean closeGuardWarned = new AtomicBoolean(false);
CloseGuard.Reporter originalReporter = CloseGuard.getReporter();
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayManagerGlobalTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayManagerGlobalTest.java
index 530dccfbe..8c509d0b7 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayManagerGlobalTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayManagerGlobalTest.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static com.google.common.truth.Truth.assertThat;
import android.hardware.display.DisplayManagerGlobal;
@@ -8,7 +7,6 @@ import android.view.Display;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.annotation.Config;
import org.robolectric.annotation.experimental.LazyApplication;
import org.robolectric.annotation.experimental.LazyApplication.LazyLoad;
@@ -18,7 +16,6 @@ public class ShadowDisplayManagerGlobalTest {
@LazyApplication(LazyLoad.ON)
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void testDisplayManagerGlobalIsLazyLoaded() {
assertThat(ShadowDisplayManagerGlobal.getGlobalInstance()).isNull();
assertThat(DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY))
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayManagerTest.java
index c92b389f6..5c9228e89 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayManagerTest.java
@@ -1,7 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static android.os.Build.VERSION_CODES.P;
import static android.os.Build.VERSION_CODES.Q;
import static com.google.common.truth.Truth.assertThat;
@@ -23,6 +21,7 @@ import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.util.ArrayList;
import java.util.List;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -42,18 +41,6 @@ public class ShadowDisplayManagerTest {
}
@Test
- @Config(maxSdk = JELLY_BEAN)
- public void notSupportedInJellyBean() {
- try {
- ShadowDisplayManager.removeDisplay(0);
- fail("Expected Exception thrown");
- } catch (UnsupportedOperationException e) {
- assertThat(e).hasMessageThat().contains("displays not supported in Jelly Bean");
- }
- }
-
- @Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void getDisplayInfo_shouldReturnCopy() {
DisplayInfo displayInfo = getGlobal().getDisplayInfo(Display.DEFAULT_DISPLAY);
int origAppWidth = displayInfo.appWidth;
@@ -63,13 +50,11 @@ public class ShadowDisplayManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void forNonexistentDisplay_getDisplayInfo_shouldReturnNull() {
assertThat(getGlobal().getDisplayInfo(3)).isEqualTo(null);
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void forNonexistentDisplay_changeDisplay_shouldThrow() {
try {
ShadowDisplayManager.changeDisplay(3, "");
@@ -80,7 +65,6 @@ public class ShadowDisplayManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void forNonexistentDisplay_removeDisplay_shouldThrow() {
try {
ShadowDisplayManager.removeDisplay(3);
@@ -91,7 +75,6 @@ public class ShadowDisplayManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void addDisplay() {
int displayId = ShadowDisplayManager.addDisplay("w100dp-h200dp");
assertThat(displayId).isGreaterThan(0);
@@ -106,7 +89,6 @@ public class ShadowDisplayManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void addDisplay_withName_shouldReflectInAddedDisplay() {
int displayId = ShadowDisplayManager.addDisplay("w100dp-h200dp", "VirtualDevice_1");
assertThat(displayId).isGreaterThan(0);
@@ -121,7 +103,6 @@ public class ShadowDisplayManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void addDisplay_shouldNotifyListeners() {
List<String> events = new ArrayList<>();
instance.registerDisplayListener(new MyDisplayListener(events), null);
@@ -130,7 +111,6 @@ public class ShadowDisplayManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void changeDisplay_shouldUpdateSmallestAndLargestNominalWidthAndHeight() {
Point smallest = new Point();
Point largest = new Point();
@@ -150,7 +130,6 @@ public class ShadowDisplayManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void withQualifiers_changeDisplay_shouldUpdateSmallestAndLargestNominalWidthAndHeight() {
Point smallest = new Point();
Point largest = new Point();
@@ -168,7 +147,6 @@ public class ShadowDisplayManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void changeAndRemoveDisplay_shouldNotifyListeners() {
List<String> events = new ArrayList<>();
instance.registerDisplayListener(new MyDisplayListener(events), null);
@@ -188,7 +166,6 @@ public class ShadowDisplayManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void changeDisplay_shouldAllowPartialChanges() {
List<String> events = new ArrayList<>();
instance.registerDisplayListener(new MyDisplayListener(events), null);
@@ -471,7 +448,6 @@ public class ShadowDisplayManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void setNaturallyPortrait_setPortrait_isRotatedWhenLandscape() {
ShadowDisplayManager.setNaturallyPortrait(Display.DEFAULT_DISPLAY, true);
@@ -481,7 +457,6 @@ public class ShadowDisplayManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void setNaturallyPortrait_setPortraitWhenLandscape_isRotated() {
ShadowDisplayManager.changeDisplay(Display.DEFAULT_DISPLAY, "land");
@@ -491,7 +466,6 @@ public class ShadowDisplayManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void setNaturallyPortrait_setLandscape_isNotRotatedWhenLandscape() {
ShadowDisplayManager.setNaturallyPortrait(Display.DEFAULT_DISPLAY, false);
@@ -501,7 +475,6 @@ public class ShadowDisplayManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void setNaturallyPortrait_setLandscape_isRotatedWhenPortrait() {
ShadowDisplayManager.setNaturallyPortrait(Display.DEFAULT_DISPLAY, false);
@@ -511,7 +484,6 @@ public class ShadowDisplayManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void setNaturallyPortrait_setLandscapeWhenLandscape_isNotRotated() {
ShadowDisplayManager.changeDisplay(Display.DEFAULT_DISPLAY, "land");
@@ -520,6 +492,20 @@ public class ShadowDisplayManagerTest {
assertThat(ShadowDisplay.getDefaultDisplay().getRotation()).isEqualTo(Surface.ROTATION_0);
}
+ @Test
+ public void configureDefaultDisplay_calledTwice_showsReasonableException() {
+ IllegalStateException e =
+ Assert.assertThrows(
+ IllegalStateException.class,
+ () -> ShadowDisplayManager.configureDefaultDisplay(null, null));
+
+ assertThat(e).hasMessageThat().contains("configureDefaultDisplay should only be called once");
+ assertThat(e)
+ .hasCauseThat()
+ .hasMessageThat()
+ .contains("configureDefaultDisplay was called a second time");
+ }
+
// because DisplayManagerGlobal don't exist in Jelly Bean,
// and we don't want them resolved as part of the test class.
static class HideFromJB {
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayTest.java
index 5149161ed..361312110 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowDisplayTest.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.N;
@@ -28,7 +27,6 @@ import org.robolectric.Shadows;
import org.robolectric.annotation.Config;
@RunWith(AndroidJUnit4.class)
-@Config(minSdk = JELLY_BEAN_MR1)
public class ShadowDisplayTest {
private Display display;
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowDistanceMeasurementManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowDistanceMeasurementManagerTest.java
new file mode 100644
index 000000000..74459fda3
--- /dev/null
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowDistanceMeasurementManagerTest.java
@@ -0,0 +1,164 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
+import static com.google.common.truth.Truth.assertThat;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothStatusCodes;
+import android.bluetooth.le.DistanceMeasurementManager;
+import android.bluetooth.le.DistanceMeasurementMethod;
+import android.bluetooth.le.DistanceMeasurementParams;
+import android.bluetooth.le.DistanceMeasurementResult;
+import android.bluetooth.le.DistanceMeasurementSession;
+import android.content.Context;
+import androidx.annotation.NonNull;
+import androidx.test.core.app.ApplicationProvider;
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.MoreExecutors;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+/** Unit tests for {@link ShadowDistanceMeasurementManager}. */
+@RunWith(RobolectricTestRunner.class)
+@Config(minSdk = UPSIDE_DOWN_CAKE)
+public class ShadowDistanceMeasurementManagerTest {
+
+ private static final String REMOTE_ADDRESS = "11:22:33:AA:BB:CC";
+
+ private int onStartFailReason;
+ private int onStoppedReason;
+
+ private final Context context = ApplicationProvider.getApplicationContext();
+ private final BluetoothAdapter adapter =
+ context.getSystemService(BluetoothManager.class).getAdapter();
+ private final BluetoothDevice remoteDevice = adapter.getRemoteDevice(REMOTE_ADDRESS);
+ private final Executor executor = MoreExecutors.directExecutor();
+
+ private Object distanceMeasurementManager;
+ private ShadowDistanceMeasurementManager shadowDistanceMeasurementManager;
+
+ private final Object params = new DistanceMeasurementParams.Builder(remoteDevice).build();
+ private Object startedDistanceMeasurementSession;
+ private final List<DistanceMeasurementResult> distanceMeasurementResults = new ArrayList<>();
+ private final Object distanceMeasurementSessionCallback =
+ new DistanceMeasurementSession.Callback() {
+ @Override
+ public void onStarted(@NonNull DistanceMeasurementSession session) {
+ startedDistanceMeasurementSession = session;
+ }
+
+ @Override
+ public void onStartFail(int reason) {
+ onStartFailReason = reason;
+ }
+
+ @Override
+ public void onStopped(@NonNull DistanceMeasurementSession session, int reason) {
+ onStoppedReason = reason;
+ }
+
+ @Override
+ public void onResult(
+ @NonNull BluetoothDevice device, @NonNull DistanceMeasurementResult result) {
+ distanceMeasurementResults.add(result);
+ }
+ };
+
+ @Before
+ public void setUp() {
+ shadowOf(adapter).setDistanceMeasurementSupported(BluetoothStatusCodes.FEATURE_SUPPORTED);
+ distanceMeasurementManager = adapter.getDistanceMeasurementManager();
+ shadowDistanceMeasurementManager = Shadow.extract(distanceMeasurementManager);
+ }
+
+ @Test
+ public void getSupportedMethods_whenMethodsSet_returnsSetMethods() {
+ DistanceMeasurementMethod methodAuto =
+ new DistanceMeasurementMethod.Builder(
+ DistanceMeasurementMethod.DISTANCE_MEASUREMENT_METHOD_AUTO)
+ .build();
+ DistanceMeasurementMethod methodRssi =
+ new DistanceMeasurementMethod.Builder(
+ DistanceMeasurementMethod.DISTANCE_MEASUREMENT_METHOD_RSSI)
+ .build();
+
+ shadowDistanceMeasurementManager.setSupportedMethods(ImmutableList.of(methodAuto, methodRssi));
+
+ assertThat(((DistanceMeasurementManager) distanceMeasurementManager).getSupportedMethods())
+ .containsExactly(methodAuto, methodRssi);
+ }
+
+ @Test
+ public void startMeasurementSession_onSessionSuccess_invokesOnResult() {
+ ((DistanceMeasurementManager) distanceMeasurementManager)
+ .startMeasurementSession(
+ (DistanceMeasurementParams) params,
+ executor,
+ (DistanceMeasurementSession.Callback) distanceMeasurementSessionCallback);
+ shadowDistanceMeasurementManager.simulateOnResult(
+ remoteDevice, new DistanceMeasurementResult.Builder(123, 12).build());
+ shadowDistanceMeasurementManager.simulateOnResult(
+ remoteDevice, new DistanceMeasurementResult.Builder(321, 21).build());
+ shadowDistanceMeasurementManager.simulateSuccessfulTermination(remoteDevice);
+
+ assertThat(startedDistanceMeasurementSession).isNotNull();
+ assertThat(distanceMeasurementResults.get(0).getResultMeters()).isEqualTo(123);
+ assertThat(distanceMeasurementResults.get(0).getErrorMeters()).isEqualTo(12);
+ assertThat(distanceMeasurementResults.get(1).getResultMeters()).isEqualTo(321);
+ assertThat(distanceMeasurementResults.get(1).getErrorMeters()).isEqualTo(21);
+ }
+
+ @Test
+ public void startMeasurementSession_onSessionStartFailed_invokesOnStartFail() {
+ ((DistanceMeasurementManager) distanceMeasurementManager)
+ .startMeasurementSession(
+ (DistanceMeasurementParams) params,
+ executor,
+ (DistanceMeasurementSession.Callback) distanceMeasurementSessionCallback);
+ shadowDistanceMeasurementManager.simulateOnStartFailError(
+ remoteDevice, BluetoothStatusCodes.ERROR_UNKNOWN);
+
+ assertThat(startedDistanceMeasurementSession).isNull();
+ assertThat(onStartFailReason).isEqualTo(BluetoothStatusCodes.ERROR_UNKNOWN);
+ assertThat(distanceMeasurementResults).isEmpty();
+ }
+
+ @Test
+ public void startMeasurementSession_onSessionStopped_invokesOnStopped() {
+ ((DistanceMeasurementManager) distanceMeasurementManager)
+ .startMeasurementSession(
+ (DistanceMeasurementParams) params,
+ executor,
+ (DistanceMeasurementSession.Callback) distanceMeasurementSessionCallback);
+ shadowDistanceMeasurementManager.simulateOnStoppedError(
+ remoteDevice, BluetoothStatusCodes.ERROR_UNKNOWN);
+
+ assertThat(startedDistanceMeasurementSession).isNotNull();
+ assertThat(onStoppedReason).isEqualTo(BluetoothStatusCodes.ERROR_UNKNOWN);
+ assertThat(distanceMeasurementResults).isEmpty();
+ }
+
+ @Test
+ public void startMeasurementSession_onTimeout_invokeOnStoppedWithTimeoutError() {
+ ((DistanceMeasurementManager) distanceMeasurementManager)
+ .startMeasurementSession(
+ (DistanceMeasurementParams) params,
+ executor,
+ (DistanceMeasurementSession.Callback) distanceMeasurementSessionCallback);
+ shadowDistanceMeasurementManager.simulateTimeout(remoteDevice);
+
+ assertThat(startedDistanceMeasurementSession).isNotNull();
+ assertThat(onStoppedReason).isEqualTo(BluetoothStatusCodes.ERROR_TIMEOUT);
+ assertThat(distanceMeasurementResults).isEmpty();
+ }
+}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowDownloadManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowDownloadManagerTest.java
index 914c8eb07..dd0be08e6 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowDownloadManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowDownloadManagerTest.java
@@ -129,6 +129,7 @@ public class ShadowDownloadManagerTest {
assertThat(cursor.getColumnIndex(DownloadManager.COLUMN_TITLE)).isAtLeast(0);
assertThat(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)).isAtLeast(0);
assertThat(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)).isAtLeast(0);
+ assertThat(cursor.getColumnIndex(DownloadManager.COLUMN_ID)).isAtLeast(0);
}
@Test
@@ -192,6 +193,23 @@ public class ShadowDownloadManagerTest {
}
@Test
+ public void query_shouldGetColumnId() {
+ ShadowDownloadManager manager = new ShadowDownloadManager();
+ long firstId = manager.enqueue(request);
+ Request secondRequest = new Request(Uri.parse("http://example.com/foo2.mp4"));
+ long secondId = manager.enqueue(secondRequest);
+
+ try (Cursor cursor = manager.query(new DownloadManager.Query())) {
+ cursor.moveToFirst();
+ assertThat(cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_ID)))
+ .isEqualTo(firstId);
+ cursor.moveToNext();
+ assertThat(cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_ID)))
+ .isEqualTo(secondId);
+ }
+ }
+
+ @Test
public void request_shouldSetDestinationInExternalPublicDir_publicDirectories() throws Exception {
shadow.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "foo.mp4");
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowEnvironmentTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowEnvironmentTest.java
index 7fdf54c6e..3f5d8ea78 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowEnvironmentTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowEnvironmentTest.java
@@ -1,6 +1,5 @@
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;
@@ -241,7 +240,6 @@ public class ShadowEnvironmentTest {
// TODO: failing test
@Ignore
@Test
- @Config(minSdk = KITKAT)
public void getExternalFilesDirs() throws Exception {
ShadowEnvironment.addExternalDir("external_dir_1");
ShadowEnvironment.addExternalDir("external_dir_2");
@@ -261,7 +259,7 @@ public class ShadowEnvironmentTest {
}
@Test
- @Config(minSdk = KITKAT, maxSdk = LOLLIPOP)
+ @Config(maxSdk = LOLLIPOP)
public void getExternalStorageStatePreLollipopMR1() {
File storageDir1 = ShadowEnvironment.addExternalDir("dir1");
File storageDir2 = ShadowEnvironment.addExternalDir("dir2");
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowFloatMathTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowFloatMathTest.java
index 65afb9c00..1470b0241 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowFloatMathTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowFloatMathTest.java
@@ -1,19 +1,16 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static com.google.common.truth.Truth.assertThat;
import android.util.FloatMath;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.annotation.Config;
/**
* Tests for {@link FloatMath}. On SDKs < 23, {@link FloatMath} was implemented using native
* methods.
*/
-@Config(minSdk = JELLY_BEAN)
@RunWith(AndroidJUnit4.class)
public class ShadowFloatMathTest {
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowICUTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowICUTest.java
index 4f32159e2..28a990362 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowICUTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowICUTest.java
@@ -65,6 +65,25 @@ public class ShadowICUTest {
@Test
@Config(minSdk = LOLLIPOP)
+ public void getBestDateTimePattern_returns_yMMMd_ptBR() {
+ assertThat(ICU.getBestDateTimePattern("yMMMd", new Locale("pt", "BR"))).isEqualTo("MMM d, y");
+ }
+
+ @Test
+ @Config(minSdk = LOLLIPOP)
+ public void getBestDateTimePattern_returns_yMMMMEEEEd_ptBR() {
+ assertThat(ICU.getBestDateTimePattern("yMMMMEEEEd", new Locale("pt", "BR")))
+ .isEqualTo("EEEE, MMMM d, y");
+ }
+
+ @Test
+ @Config(minSdk = LOLLIPOP)
+ public void getBestDateTimePattern_returns_yMMMM_ptBR() {
+ assertThat(ICU.getBestDateTimePattern("yMMMM", new Locale("pt", "BR"))).isEqualTo("MMMM y");
+ }
+
+ @Test
+ @Config(minSdk = LOLLIPOP)
public void datePickerShouldNotCrashWhenAskingForBestDateTimePattern() {
ActivityController<DatePickerActivity> activityController =
Robolectric.buildActivity(DatePickerActivity.class);
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowImageReaderTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowImageReaderTest.java
index acc8d8804..b3aee4ca8 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowImageReaderTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowImageReaderTest.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 static org.junit.Assert.assertThrows;
@@ -16,11 +15,9 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.annotation.Config;
/** Tests for {@link ShadowImageReader}. */
@RunWith(AndroidJUnit4.class)
-@Config(minSdk = KITKAT)
public class ShadowImageReaderTest {
private static final int WIDTH = 640;
private static final int HEIGHT = 480;
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowInputDeviceTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowInputDeviceTest.java
index 7ddb3f760..557f1c7ae 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowInputDeviceTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowInputDeviceTest.java
@@ -1,13 +1,11 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static com.google.common.truth.Truth.assertThat;
import android.view.InputDevice;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;
@RunWith(AndroidJUnit4.class)
@@ -19,7 +17,6 @@ public class ShadowInputDeviceTest {
}
@Test
- @Config(minSdk = KITKAT)
public void canChangeProductId() {
InputDevice inputDevice = ShadowInputDevice.makeInputDeviceNamed("foo");
ShadowInputDevice shadowInputDevice = Shadow.extract(inputDevice);
@@ -29,7 +26,6 @@ public class ShadowInputDeviceTest {
}
@Test
- @Config(minSdk = KITKAT)
public void canChangeVendorId() {
InputDevice inputDevice = ShadowInputDevice.makeInputDeviceNamed("foo");
ShadowInputDevice shadowInputDevice = Shadow.extract(inputDevice);
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowInputEventReceiverTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowInputEventReceiverTest.java
index 2a4219b35..f7d97df31 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowInputEventReceiverTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowInputEventReceiverTest.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static com.google.common.truth.Truth.assertThat;
import android.os.Looper;
@@ -11,7 +10,6 @@ import dalvik.system.CloseGuard;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.annotation.Config;
/** Tests for {@link ShadowInputEventReceiver}. */
@RunWith(AndroidJUnit4.class)
@@ -30,7 +28,6 @@ public class ShadowInputEventReceiverTest {
}
@Test
- @Config(minSdk = JELLY_BEAN)
public void closeGuard_autoCloses() throws Throwable {
final AtomicBoolean closeGuardWarned = new AtomicBoolean(false);
CloseGuard.Reporter originalReporter = CloseGuard.getReporter();
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowInputMethodManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowInputMethodManagerTest.java
index 69a0e2f6c..11ccfda69 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowInputMethodManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowInputMethodManagerTest.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 static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -22,7 +21,6 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Shadows;
-import org.robolectric.annotation.Config;
@RunWith(AndroidJUnit4.class)
public class ShadowInputMethodManagerTest {
@@ -125,8 +123,6 @@ public class ShadowInputMethodManagerTest {
assertThat(shadow.getCurrentInputMethodSubtype()).isNull();
}
- /** The builder is only available for 19+. */
- @Config(minSdk = KITKAT)
@Test
public void setCurrentInputMethodSubtype_isReturned() {
InputMethodSubtype inputMethodSubtype = new InputMethodSubtypeBuilder().build();
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowInstrumentationTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowInstrumentationTest.java
index c611a403a..daafcdd19 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowInstrumentationTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowInstrumentationTest.java
@@ -1,7 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.N;
import static android.os.Build.VERSION_CODES.N_MR1;
import static com.google.common.truth.Truth.assertThat;
@@ -36,7 +34,7 @@ import org.robolectric.annotation.Config;
public class ShadowInstrumentationTest {
@Test
- @Config(minSdk = JELLY_BEAN_MR1, maxSdk = N_MR1)
+ @Config(maxSdk = N_MR1)
public void testExecStartActivity_handledProperlyForSDK17to25() throws Exception {
Instrumentation instrumentation =
((ActivityThread) RuntimeEnvironment.getActivityThread()).getInstrumentation();
@@ -145,7 +143,6 @@ public class ShadowInstrumentationTest {
assertThat(decorView.isInTouchMode()).isTrue();
}
- @Config(minSdk = JELLY_BEAN_MR2)
@Test
public void getUiAutomation() {
assertThat(InstrumentationRegistry.getInstrumentation().getUiAutomation()).isNotNull();
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowIsoDepTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowIsoDepTest.java
index 3d7636fd0..ed6f37155 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowIsoDepTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowIsoDepTest.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 static org.junit.Assert.fail;
import static org.robolectric.Shadows.shadowOf;
@@ -12,11 +11,9 @@ import java.util.concurrent.Callable;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.annotation.Config;
/** Unit tests for {@link ShadowIsoDep}. */
@RunWith(AndroidJUnit4.class)
-@Config(minSdk = KITKAT)
public final class ShadowIsoDepTest {
private IsoDep isoDep;
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowLegacyMessageQueueTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowLegacyMessageQueueTest.java
index 89892fa86..c2c41b07a 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowLegacyMessageQueueTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowLegacyMessageQueueTest.java
@@ -9,7 +9,6 @@ import static org.robolectric.util.ReflectionHelpers.callInstanceMethod;
import static org.robolectric.util.ReflectionHelpers.setField;
import static org.robolectric.util.reflector.Reflector.reflector;
-import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -74,7 +73,7 @@ public class ShadowLegacyMessageQueueTest {
scheduler = shadowQueue.getScheduler();
scheduler.pause();
testMessage = handler.obtainMessage();
- quitField = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT ? "mQuitting" : "mQuiting";
+ quitField = "mQuitting";
}
@Test
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowLegacySystemClockTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowLegacySystemClockTest.java
index 529c65689..2ae90cd60 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowLegacySystemClockTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowLegacySystemClockTest.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static android.os.Build.VERSION_CODES.P;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertFalse;
@@ -56,7 +55,7 @@ public class ShadowLegacySystemClockTest {
assertThat(SystemClock.elapsedRealtime()).isEqualTo(1034);
}
- @Test @Config(minSdk = JELLY_BEAN_MR1)
+ @Test
public void testElapsedRealtimeNanos() {
Robolectric.getForegroundThreadScheduler().advanceTo(1000);
assertThat(SystemClock.elapsedRealtimeNanos()).isEqualTo(1000000000);
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowLinuxTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowLinuxTest.java
index 366daf956..aa75f2195 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowLinuxTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowLinuxTest.java
@@ -1,6 +1,7 @@
package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.O;
+import static android.os.Build.VERSION_CODES.R;
import static com.google.common.truth.Truth.assertThat;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -58,6 +59,14 @@ public final class ShadowLinuxTest {
}
@Test
+ @Config(minSdk = R)
+ public void memfdCreate_returnNoneNullFileDescriptor() throws Exception {
+ FileDescriptor arscFile =
+ shadowLinux.memfd_create("remote_views_theme_colors.arsc", /* flags= */ 0);
+ assertThat(arscFile).isNotNull();
+ }
+
+ @Test
public void pread_validateExtractsContentWithOffset() throws Exception {
try (FileInputStream fis = new FileInputStream(file)) {
FileDescriptor fd = fis.getFD();
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowLocaleDataTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowLocaleDataTest.java
index 862d2a623..f039fd76e 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowLocaleDataTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowLocaleDataTest.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
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;
@@ -127,9 +126,7 @@ public class ShadowLocaleDataTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
- public void shouldSupportLocaleEn_US_since_jelly_bean_mr1()
- throws NoSuchFieldException, IllegalAccessException {
+ public void shouldSupportLocaleEn_US_since() throws NoSuchFieldException, IllegalAccessException {
LocaleData localeData = LocaleData.get(Locale.US);
LocaleDataReflector localeDataReflector = reflector(LocaleDataReflector.class, localeData);
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowLocationManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowLocationManagerTest.java
index 5add1c81c..e9e51f909 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowLocationManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowLocationManagerTest.java
@@ -116,7 +116,6 @@ public class ShadowLocationManagerTest {
}
@Test
- @Config(minSdk = VERSION_CODES.KITKAT)
public void testGetProvider() {
LocationProvider p;
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowLogTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowLogTest.java
index 6e351319e..01054fd75 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowLogTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowLogTest.java
@@ -5,10 +5,12 @@ import static com.google.common.truth.Truth.assertThat;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
import android.util.Log;
+import android.util.Log.TerribleFailure;
+import android.util.Log.TerribleFailureHandler;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.common.collect.Iterables;
import java.io.ByteArrayOutputStream;
@@ -16,7 +18,9 @@ import java.io.PrintStream;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowLog.LogItem;
+import org.robolectric.versioning.AndroidVersions.L;
@RunWith(AndroidJUnit4.class)
public class ShadowLogTest {
@@ -129,12 +133,32 @@ public class ShadowLogTest {
ShadowLog.setWtfIsFatal(true);
Throwable throwable = new Throwable();
- try {
- Log.wtf("tag", "msg", throwable);
- fail("TerribleFailure should be thrown");
- } catch (ShadowLog.TerribleFailure e) {
- // pass
- }
+ assertThrows(TerribleFailure.class, () -> Log.wtf("tag", "msg", throwable));
+ assertLogged(Log.ASSERT, "tag", "msg", throwable);
+ }
+
+ @Test
+ @Config(minSdk = L.SDK_INT)
+ public void wtf_shouldLogAppropriately_withNewWtfHandler() {
+ Throwable throwable = new Throwable();
+
+ String[] captured = new String[1];
+
+ TerribleFailureHandler prevWtfHandler =
+ Log.setWtfHandler(
+ new TerribleFailureHandler() {
+ @Override
+ public void onTerribleFailure(String tag, TerribleFailure what, boolean system) {
+ captured[0] = what.getMessage();
+ }
+ });
+
+ Log.wtf("tag", "msg", throwable);
+ // assert that the new handler captures the message
+ assertEquals("msg", captured[0]);
+ // reset the handler
+ Log.setWtfHandler(prevWtfHandler);
+
assertLogged(Log.ASSERT, "tag", "msg", throwable);
}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowLooperResetterTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowLooperResetterTest.java
index 92e1643c5..494882a4a 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowLooperResetterTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowLooperResetterTest.java
@@ -8,8 +8,14 @@ import static org.robolectric.Shadows.shadowOf;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.SystemClock;
+import android.view.Choreographer;
import java.time.Duration;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
@@ -21,6 +27,7 @@ import org.junit.runner.notification.RunNotifier;
import org.junit.runners.JUnit4;
import org.junit.runners.model.InitializationError;
import org.robolectric.RobolectricTestRunner;
+import org.robolectric.util.TimeUtils;
/** A specialized test for verifying that looper state is cleared properly between tests. */
@RunWith(JUnit4.class)
@@ -188,4 +195,142 @@ public class ShadowLooperResetterTest {
// run and assert no failures
runner.run(runNotifier);
}
+
+ /** Test that uses Choreographer. ShadowLooper should clear Choreographer state between tests */
+ public static class ChoreographerResetTest {
+
+ // use a static thread so both tests share the same Looper + Choreographer
+ static HandlerThread handlerThread;
+
+ @Before
+ public void init() {
+ if (handlerThread == null) {
+ handlerThread = new HandlerThread("ChoreographerResetTest");
+ handlerThread.start();
+ }
+ }
+
+ @AfterClass
+ public static void shutDown() throws InterruptedException {
+ handlerThread.quit();
+ handlerThread.join();
+ }
+
+ private void doPostToChoreographerTest() {
+ checkNotNull(handlerThread.getLooper());
+ Handler handler = new Handler(handlerThread.getLooper());
+
+ AtomicLong frameTimeNanosResult = new AtomicLong(-1);
+ // you can only access Choreographer from Looper thread
+ handler.post(() -> Choreographer.getInstance().postFrameCallback(frameTimeNanosResult::set));
+ shadowOf(handlerThread.getLooper()).idle();
+
+ // ensure callback happened and that clock is consistent with Choreographer's frame time
+ // tracking
+ assertThat(frameTimeNanosResult.get())
+ .isEqualTo(SystemClock.uptimeMillis() * TimeUtils.NANOS_PER_MS);
+
+ // Now set Choreographer so it expects there is a pending vsync+frame callback.
+ // Choreographer will ignore vsync+frame requests if there is already one pending.
+ // If Choreographer state isn't clearly properly between tests the next test will fail.
+
+ // Setting Choreographer into paused mode makes this test deterministic, as vsync callbacks
+ // won't occur until clock has been incremented.
+ ShadowChoreographer.setPaused(true);
+ ShadowChoreographer.setFrameDelay(Duration.ofMillis(16));
+
+ handler.post(() -> Choreographer.getInstance().postFrameCallback(frameTimeNanos -> {}));
+ shadowOf(handlerThread.getLooper()).idle();
+ }
+
+ @Test
+ public void postToChoreographerTest() {
+ doPostToChoreographerTest();
+ }
+
+ @Test
+ public void anotherPostToChoreographerTest() {
+ doPostToChoreographerTest();
+ }
+ }
+
+ @Test
+ public void choreographerPost() throws InitializationError {
+ Runner runner = new RobolectricTestRunner(ChoreographerResetTest.class);
+
+ // run and assert no failures
+ runner.run(runNotifier);
+ }
+
+ /** Test that uses Choreographer and asynchronously kills thread between tests */
+ public static class ChoreographerResetQuitTest {
+
+ // use a static thread so both tests share the same Looper + Choreographer
+ private HandlerThread handlerThread;
+ private final Executor executor = Executors.newSingleThreadExecutor();
+
+ @Before
+ public void init() {
+ handlerThread = new HandlerThread("ChoreographerResetQuitTest");
+ handlerThread.start();
+ }
+
+ @After
+ public void shutDown() throws InterruptedException {
+ // asynchronously quit handler thread to try to expose race conditions
+ executor.execute(
+ new Runnable() {
+ @Override
+ public void run() {
+ handlerThread.quit();
+ }
+ });
+ }
+
+ private void doPostToChoreographerTest() {
+ checkNotNull(handlerThread.getLooper());
+ Handler handler = new Handler(handlerThread.getLooper());
+
+ AtomicLong frameTimeNanosResult = new AtomicLong(-1);
+ // you can only access Choreographer from Looper thread
+ handler.post(() -> Choreographer.getInstance().postFrameCallback(frameTimeNanosResult::set));
+ shadowOf(handlerThread.getLooper()).idle();
+
+ // ensure callback happened and that clock is consistent with Choreographer's frame time
+ // tracking
+ assertThat(frameTimeNanosResult.get())
+ .isEqualTo(SystemClock.uptimeMillis() * TimeUtils.NANOS_PER_MS);
+
+ // Now set Choreographer so it expects there is a pending vsync+frame callback.
+ // Choreographer will ignore vsync+frame requests if there is already one pending.
+ // If Choreographer state isn't clearly properly between tests the next test will fail.
+
+ // Setting Choreographer into paused mode makes this test deterministic, as vsync callbacks
+ // won't occur until clock has been incremented.
+ ShadowChoreographer.setPaused(true);
+ ShadowChoreographer.setFrameDelay(Duration.ofMillis(16));
+
+ handler.post(() -> Choreographer.getInstance().postFrameCallback(frameTimeNanos -> {}));
+ shadowOf(handlerThread.getLooper()).idle();
+ }
+
+ @Test
+ public void postToChoreographerTest() {
+ doPostToChoreographerTest();
+ }
+
+ @Test
+ public void anotherPostToChoreographerTest() {
+ doPostToChoreographerTest();
+ }
+ }
+
+ /** Tests for potentially race conditions where Looper is quit asynchrounously at end of test */
+ @Test
+ public void choreographerQuitPost() throws InitializationError {
+ Runner runner = new RobolectricTestRunner(ChoreographerResetQuitTest.class);
+
+ // run and assert no failures
+ runner.run(runNotifier);
+ }
}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaActionSoundTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaActionSoundTest.java
index ad6aeebdb..d58130f14 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaActionSoundTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaActionSoundTest.java
@@ -3,7 +3,6 @@ package org.robolectric.shadows;
import static com.google.common.truth.Truth.assertThat;
import android.media.MediaActionSound;
-import android.os.Build;
import android.os.Build.VERSION_CODES;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
@@ -12,7 +11,6 @@ import org.robolectric.annotation.Config;
/** Unit tests for {@link org.robolectric.shadows.ShadowMediaActionSound}. */
@RunWith(AndroidJUnit4.class)
-@Config(minSdk = Build.VERSION_CODES.JELLY_BEAN)
public final class ShadowMediaActionSoundTest {
@Test
public void getPlayCount_noShutterClickPlayed_zero() {
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaRouterTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaRouterTest.java
index 15f1be229..44b2ee8f6 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaRouterTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaRouterTest.java
@@ -2,8 +2,6 @@ package org.robolectric.shadows;
import static android.media.MediaRouter.ROUTE_TYPE_LIVE_AUDIO;
import static android.media.MediaRouter.ROUTE_TYPE_LIVE_VIDEO;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.N;
import static com.google.common.truth.Truth.assertThat;
import static org.robolectric.Shadows.shadowOf;
@@ -16,7 +14,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
/** Tests for {@link ShadowMediaRouter}. */
@@ -54,7 +51,6 @@ public final class ShadowMediaRouterTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR2)
public void testAddBluetoothRoute_checkBluetoothRouteProperties_apiJbMr2() {
shadowOf(mediaRouter).addBluetoothRoute();
RouteInfo bluetoothRoute = mediaRouter.getRouteAt(1);
@@ -88,7 +84,8 @@ public final class ShadowMediaRouterTest {
shadowOf(mediaRouter).removeBluetoothRoute();
assertThat(mediaRouter.getRouteCount()).isEqualTo(1);
- assertThat(mediaRouter.getSelectedRoute(ROUTE_TYPE_LIVE_AUDIO)).isEqualTo(getDefaultRoute());
+ assertThat(mediaRouter.getSelectedRoute(ROUTE_TYPE_LIVE_AUDIO))
+ .isEqualTo(mediaRouter.getDefaultRoute());
}
@Test
@@ -100,7 +97,8 @@ public final class ShadowMediaRouterTest {
shadowOf(mediaRouter).removeBluetoothRoute();
assertThat(mediaRouter.getRouteCount()).isEqualTo(1);
- assertThat(mediaRouter.getSelectedRoute(ROUTE_TYPE_LIVE_AUDIO)).isEqualTo(getDefaultRoute());
+ assertThat(mediaRouter.getSelectedRoute(ROUTE_TYPE_LIVE_AUDIO))
+ .isEqualTo(mediaRouter.getDefaultRoute());
}
@Test
@@ -108,24 +106,21 @@ public final class ShadowMediaRouterTest {
assertThat(shadowOf(mediaRouter).isBluetoothRouteSelected(ROUTE_TYPE_LIVE_AUDIO)).isFalse();
}
- // Pre-API 18, non-user routes weren't able to be selected.
@Test
- @Config(minSdk = JELLY_BEAN_MR2)
public void testIsBluetoothRouteSelected_bluetoothRouteAddedButNotSelected_returnsFalse() {
shadowOf(mediaRouter).addBluetoothRoute();
- mediaRouter.selectRoute(ROUTE_TYPE_LIVE_AUDIO, getDefaultRoute());
+ mediaRouter.selectRoute(ROUTE_TYPE_LIVE_AUDIO, mediaRouter.getDefaultRoute());
assertThat(shadowOf(mediaRouter).isBluetoothRouteSelected(ROUTE_TYPE_LIVE_AUDIO)).isFalse();
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void testIsBluetoothRouteSelected_bluetoothRouteSelectedForDifferentType_returnsFalse() {
shadowOf(mediaRouter).addBluetoothRoute();
RouteInfo bluetoothRoute = mediaRouter.getRouteAt(1);
// Select the Bluetooth route for AUDIO and the default route for AUDIO.
mediaRouter.selectRoute(ROUTE_TYPE_LIVE_AUDIO, bluetoothRoute);
- mediaRouter.selectRoute(ROUTE_TYPE_LIVE_VIDEO, getDefaultRoute());
+ mediaRouter.selectRoute(ROUTE_TYPE_LIVE_VIDEO, mediaRouter.getDefaultRoute());
assertThat(shadowOf(mediaRouter).isBluetoothRouteSelected(ROUTE_TYPE_LIVE_VIDEO)).isFalse();
}
@@ -137,11 +132,4 @@ public final class ShadowMediaRouterTest {
mediaRouter.selectRoute(ROUTE_TYPE_LIVE_AUDIO, bluetoothRoute);
assertThat(shadowOf(mediaRouter).isBluetoothRouteSelected(ROUTE_TYPE_LIVE_AUDIO)).isTrue();
}
-
- private RouteInfo getDefaultRoute() {
- if (RuntimeEnvironment.getApiLevel() >= JELLY_BEAN_MR2) {
- return mediaRouter.getDefaultRoute();
- }
- return mediaRouter.getRouteAt(0);
- }
}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowNfcAdapterTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowNfcAdapterTest.java
index bd64a107f..cf25dc858 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowNfcAdapterTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowNfcAdapterTest.java
@@ -1,6 +1,7 @@
package org.robolectric.shadows;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -16,9 +17,7 @@ import android.nfc.Tag;
import android.os.Build;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
-import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RuntimeEnvironment;
@@ -27,12 +26,10 @@ import org.robolectric.annotation.Config;
@RunWith(AndroidJUnit4.class)
public class ShadowNfcAdapterTest {
- @Rule public ExpectedException expectedException = ExpectedException.none();
- private Application context;
+ private final Application context = RuntimeEnvironment.getApplication();
@Before
public void setUp() throws Exception {
- context = RuntimeEnvironment.getApplication();
shadowOf(context.getPackageManager())
.setSystemFeature(PackageManager.FEATURE_NFC, /* supported= */ true);
}
@@ -67,9 +64,11 @@ public class ShadowNfcAdapterTest {
final Activity nullActivity = null;
final NfcAdapter adapter = NfcAdapter.getDefaultAdapter(activity);
- expectedException.expect(NullPointerException.class);
- expectedException.expectMessage("activity cannot be null");
- adapter.setOnNdefPushCompleteCallback(callback, nullActivity);
+ NullPointerException exception =
+ assertThrows(
+ NullPointerException.class,
+ () -> adapter.setOnNdefPushCompleteCallback(callback, nullActivity));
+ assertThat(exception).hasMessageThat().contains("activity cannot be null");
}
@Test
@@ -80,10 +79,11 @@ public class ShadowNfcAdapterTest {
final Activity nullActivity = null;
final NfcAdapter adapter = NfcAdapter.getDefaultAdapter(activity);
- expectedException.expect(NullPointerException.class);
- expectedException.expectMessage("activities cannot contain null");
-
- adapter.setOnNdefPushCompleteCallback(callback, activity, nullActivity);
+ NullPointerException exception =
+ assertThrows(
+ NullPointerException.class,
+ () -> adapter.setOnNdefPushCompleteCallback(callback, activity, nullActivity));
+ assertThat(exception).hasMessageThat().contains("activities cannot contain null");
}
@Test
@@ -99,22 +99,59 @@ public class ShadowNfcAdapterTest {
}
@Test
+ @Config(minSdk = Build.VERSION_CODES.Q)
+ public void isSecureNfcSupported_shouldReturnSupportedState() {
+ NfcAdapter adapter = NfcAdapter.getDefaultAdapter(context);
+ assertThat(adapter.isSecureNfcSupported()).isFalse();
+
+ shadowOf(adapter).setSecureNfcSupported(true);
+ assertThat(adapter.isSecureNfcSupported()).isTrue();
+
+ shadowOf(adapter).setSecureNfcSupported(false);
+ assertThat(adapter.isSecureNfcSupported()).isFalse();
+ }
+
+ @Test
+ @Config(minSdk = Build.VERSION_CODES.Q)
+ public void isSecureNfcEnabled_shouldReturnEnabledState() {
+ NfcAdapter adapter = NfcAdapter.getDefaultAdapter(context);
+ assertThat(adapter.isSecureNfcEnabled()).isFalse();
+
+ adapter.enableSecureNfc(true);
+ assertThat(adapter.isSecureNfcEnabled()).isTrue();
+
+ adapter.enableSecureNfc(false);
+ assertThat(adapter.isSecureNfcEnabled()).isFalse();
+ }
+
+ @Test
public void getNfcAdapter_returnsNonNull() {
NfcAdapter adapter = NfcAdapter.getDefaultAdapter(context);
+
assertThat(adapter).isNotNull();
+
+ // This is checked twice to prevent a regression where attempting to acquire the
+ // `getDefaultAdapter` twice in a test would cause a `null` value to be returned on UDC+.
+ NfcAdapter adapterAgain = NfcAdapter.getDefaultAdapter(context);
+
+ assertThat(adapterAgain).isNotNull();
}
@Test
public void getNfcAdapter_hardwareExists_returnsNonNull() {
ShadowNfcAdapter.setNfcHardwareExists(true);
+
NfcAdapter adapter = NfcAdapter.getDefaultAdapter(context);
+
assertThat(adapter).isNotNull();
}
@Test
public void getNfcAdapter_hardwareDoesNotExist_returnsNull() {
ShadowNfcAdapter.setNfcHardwareExists(false);
+
NfcAdapter adapter = NfcAdapter.getDefaultAdapter(context);
+
assertThat(adapter).isNull();
}
@@ -145,13 +182,10 @@ public class ShadowNfcAdapterTest {
final Activity activity = Robolectric.setupActivity(Activity.class);
final NfcAdapter adapter = NfcAdapter.getDefaultAdapter(activity);
- expectedException.expect(IllegalStateException.class);
-
- shadowOf(adapter).getNdefPushMessage();
+ assertThrows(IllegalStateException.class, () -> shadowOf(adapter).getNdefPushMessage());
}
@Test
- @Config(minSdk = Build.VERSION_CODES.KITKAT)
public void isInReaderMode_beforeEnableReaderMode_shouldReturnFalse() {
final Activity activity = Robolectric.setupActivity(Activity.class);
@@ -160,7 +194,6 @@ public class ShadowNfcAdapterTest {
}
@Test
- @Config(minSdk = Build.VERSION_CODES.KITKAT)
public void isInReaderMode_afterEnableReaderMode_shouldReturnTrue() {
final Activity activity = Robolectric.setupActivity(Activity.class);
NfcAdapter adapter = NfcAdapter.getDefaultAdapter(activity);
@@ -175,7 +208,6 @@ public class ShadowNfcAdapterTest {
}
@Test
- @Config(minSdk = Build.VERSION_CODES.KITKAT)
public void isInReaderMode_afterDisableReaderMode_shouldReturnFalse() {
final Activity activity = Robolectric.setupActivity(Activity.class);
NfcAdapter adapter = NfcAdapter.getDefaultAdapter(activity);
@@ -191,7 +223,6 @@ public class ShadowNfcAdapterTest {
}
@Test
- @Config(minSdk = Build.VERSION_CODES.KITKAT)
public void dispatchTagDiscovered_shouldDispatchTagToCallback() {
final Activity activity = Robolectric.setupActivity(Activity.class);
NfcAdapter adapter = NfcAdapter.getDefaultAdapter(activity);
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowNotificationBuilderTestBase.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowNotificationBuilderTestBase.java
index 6274b67ac..fe3483e2c 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowNotificationBuilderTestBase.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowNotificationBuilderTestBase.java
@@ -1,7 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.N;
import static com.google.common.truth.Truth.assertThat;
@@ -44,7 +42,6 @@ public abstract class ShadowNotificationBuilderTestBase {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void build_whenShowWhenNotSet_setsShowWhenOnNotificationToTrue() {
Notification notification = builder.setWhen(100).setShowWhen(true).build();
@@ -52,7 +49,6 @@ public abstract class ShadowNotificationBuilderTestBase {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void build_setShowWhenOnNotification() {
Notification notification = builder.setShowWhen(false).build();
@@ -112,7 +108,6 @@ public abstract class ShadowNotificationBuilderTestBase {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void build_setsUsesChronometerOnNotification_true() {
Notification notification = builder.setUsesChronometer(true).setWhen(10).setShowWhen(true).build();
@@ -120,7 +115,6 @@ public abstract class ShadowNotificationBuilderTestBase {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void build_setsUsesChronometerOnNotification_false() {
Notification notification = builder.setUsesChronometer(false).setWhen(10).setShowWhen(true).build();
@@ -209,7 +203,6 @@ public abstract class ShadowNotificationBuilderTestBase {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR2)
public void build_addsActionToNotification() throws Exception {
PendingIntent action =
PendingIntent.getBroadcast(ApplicationProvider.getApplicationContext(), 0, null, 0);
@@ -247,4 +240,22 @@ public abstract class ShadowNotificationBuilderTestBase {
assertThat(shadowOf(notification).getBigPicture().sameAs(bigPicture)).isTrue();
}
+
+ @Test
+ public void withInboxStyle() {
+ Notification notification =
+ builder
+ .setStyle(
+ new Notification.InboxStyle(builder)
+ .addLine("Line1")
+ .addLine("Line2")
+ .setBigContentTitle("Title")
+ .setSummaryText("Summary"))
+ .build();
+
+ assertThat(shadowOf(notification).getBigContentTitle().toString()).isEqualTo("Title");
+ assertThat(shadowOf(notification).getBigContentText().toString()).isEqualTo("Summary");
+ assertThat(shadowOf(notification).getBigPicture()).isNull();
+ assertThat(shadowOf(notification).getTextLines()).containsExactly("Line1", "Line2");
+ }
}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowOpenGLMatrixTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowOpenGLMatrixTest.java
index a129547a9..8784162c0 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowOpenGLMatrixTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowOpenGLMatrixTest.java
@@ -1,13 +1,11 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static com.google.common.truth.Truth.assertThat;
import android.opengl.Matrix;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.annotation.Config;
@RunWith(AndroidJUnit4.class)
public class ShadowOpenGLMatrixTest {
@@ -356,8 +354,7 @@ public class ShadowOpenGLMatrixTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
- public void testFrustumJB_MR1() {
+ public void testFrustum() {
float[] expected = new float[]{
0.005f, 0, 0, 0,
0, 0.02f, 0, 0,
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowPackageManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowPackageManagerTest.java
index e2ac826e4..720a01d2f 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowPackageManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowPackageManagerTest.java
@@ -32,8 +32,6 @@ import static android.content.pm.PackageManager.SIGNATURE_SECOND_NOT_SIGNED;
import static android.content.pm.PackageManager.SIGNATURE_UNKNOWN_PACKAGE;
import static android.content.pm.PackageManager.VERIFICATION_ALLOW;
import static android.content.pm.PackageManager.VERIFICATION_REJECT;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
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;
@@ -1179,7 +1177,6 @@ public class ShadowPackageManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void queryIntentActivitiesAsUser_EmptyResult() {
Intent i = new Intent(Intent.ACTION_APP_ERROR, null);
i.addCategory(Intent.CATEGORY_APP_BROWSER);
@@ -1209,7 +1206,6 @@ public class ShadowPackageManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void queryIntentActivitiesAsUser_Match() {
Intent i = new Intent(Intent.ACTION_MAIN, null);
i.addCategory(Intent.CATEGORY_LAUNCHER);
@@ -1509,7 +1505,6 @@ public class ShadowPackageManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void resolveActivityAsUser_Match() {
Intent i = new Intent(Intent.ACTION_MAIN, null).addCategory(Intent.CATEGORY_LAUNCHER);
ResolveInfo info = new ResolveInfo();
@@ -1533,7 +1528,6 @@ public class ShadowPackageManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void resolveActivityAsUser_NoMatch() {
Intent i = new Intent();
i.setComponent(new ComponentName("foo.bar", "No Activity"));
@@ -1706,7 +1700,6 @@ public class ShadowPackageManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void queryIntentServicesAsUser() {
Intent i = new Intent("org.robolectric.ACTION_DIFFERENT_PACKAGE");
i.addCategory(Intent.CATEGORY_LAUNCHER);
@@ -2112,6 +2105,16 @@ public class ShadowPackageManagerTest {
}
@Test
+ @Config(minSdk = TIRAMISU)
+ public void testReceiverInfo_withComponentInfoFlags() throws Exception {
+ ActivityInfo info =
+ packageManager.getReceiverInfo(
+ new ComponentName(context, "org.robolectric.test.ConfigTestReceiver"),
+ PackageManager.ComponentInfoFlags.of(PackageManager.GET_META_DATA));
+ assertThat(info.metaData.getInt("numberOfSheep")).isEqualTo(42);
+ }
+
+ @Test
public void testGetPackageInfo_ForReceiversIncorrectPackage() {
try {
packageManager.getPackageInfo("unknown_package", PackageManager.GET_RECEIVERS);
@@ -2598,6 +2601,74 @@ public class ShadowPackageManagerTest {
}
@Test
+ @Config(minSdk = TIRAMISU)
+ public void getServiceInfo_withComponentInfoFlags_shouldReturnServiceInfoIfExists()
+ throws Exception {
+ ServiceInfo serviceInfo =
+ packageManager.getServiceInfo(
+ new ComponentName("org.robolectric", "com.foo.Service"),
+ PackageManager.ComponentInfoFlags.of(0));
+ assertThat(serviceInfo.packageName).isEqualTo("org.robolectric");
+ assertThat(serviceInfo.name).isEqualTo("com.foo.Service");
+ assertThat(serviceInfo.permission).isEqualTo("com.foo.MY_PERMISSION");
+ assertThat(serviceInfo.applicationInfo).isNotNull();
+ }
+
+ @Test
+ @Config(minSdk = TIRAMISU)
+ public void
+ getServiceInfo_withComponentInfoFlags_shouldReturnServiceInfoWithMetaDataWhenFlagsSet()
+ throws Exception {
+ ServiceInfo serviceInfo =
+ packageManager.getServiceInfo(
+ new ComponentName("org.robolectric", "com.foo.Service"),
+ PackageManager.ComponentInfoFlags.of(PackageManager.GET_META_DATA));
+ assertThat(serviceInfo.metaData).isNotNull();
+ }
+
+ @Test
+ @Config(minSdk = TIRAMISU)
+ public void
+ getServiceInfo_withComponentInfoFlags_shouldReturnServiceInfoWithoutMetaDataWhenFlagsNotSet()
+ throws Exception {
+ ComponentName component = new ComponentName("org.robolectric", "com.foo.Service");
+ ServiceInfo serviceInfo =
+ packageManager.getServiceInfo(component, PackageManager.ComponentInfoFlags.of(0));
+ assertThat(serviceInfo.metaData).isNull();
+ }
+
+ @Test
+ @Config(minSdk = TIRAMISU)
+ public void getServiceInfo_withComponentInfoFlags_shouldThrowNameNotFoundExceptionIfNotExist() {
+ ComponentName nonExistComponent =
+ new ComponentName("org.robolectric", "com.foo.NonExistService");
+ try {
+ packageManager.getServiceInfo(
+ nonExistComponent, PackageManager.ComponentInfoFlags.of(PackageManager.GET_SERVICES));
+ fail("should have thrown NameNotFoundException");
+ } catch (PackageManager.NameNotFoundException e) {
+ assertThat(e.getMessage()).contains("com.foo.NonExistService");
+ }
+ }
+
+ @Test
+ @Config(minSdk = TIRAMISU)
+ public void getServiceInfo_withComponentInfoFlags_shouldFindServiceIfAddedInResolveInfo()
+ throws Exception {
+ ComponentName componentName = new ComponentName("com.test", "com.test.ServiceName");
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.serviceInfo = new ServiceInfo();
+ resolveInfo.serviceInfo.name = componentName.getClassName();
+ resolveInfo.serviceInfo.applicationInfo = new ApplicationInfo();
+ resolveInfo.serviceInfo.applicationInfo.packageName = componentName.getPackageName();
+ shadowOf(packageManager).addResolveInfoForIntent(new Intent("RANDOM_ACTION"), resolveInfo);
+
+ ServiceInfo serviceInfo =
+ packageManager.getServiceInfo(componentName, PackageManager.ComponentInfoFlags.of(0));
+ assertThat(serviceInfo).isNotNull();
+ }
+
+ @Test
public void getNameForUid() {
assertThat(packageManager.getNameForUid(10)).isNull();
@@ -2770,7 +2841,6 @@ public class ShadowPackageManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void
extendPendingInstallTimeout_verificationRejectAtTimeout_extendsPendingInstallTimeoutAndsetsCodeAtTimeoutToReject() {
packageManager.extendVerificationTimeout(
@@ -2786,7 +2856,6 @@ public class ShadowPackageManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void
extendPendingInstallTimeout_verificationAllowAtTimeout_extendsPendingInstallTimeoutAndsetsCodeAtTimeoutToAllow() {
packageManager.extendVerificationTimeout(
@@ -2802,7 +2871,6 @@ public class ShadowPackageManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void whenVerificationTimeOutNotExtended_verificationCodeAtTimeoutIsAllow() {
assertThat(shadowOf(packageManager).getVerificationExtendedTimeout(INSTALL_VERIFICATION_ID))
.isEqualTo(0);
@@ -2813,7 +2881,6 @@ public class ShadowPackageManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void triggerInstallVerificationTimeout_broadcastsPackageVerifiedIntent() {
ShadowPackageManager shadowPackageManagerMock =
mock(ShadowPackageManager.class, Mockito.CALLS_REAL_METHODS);
@@ -3288,6 +3355,14 @@ public class ShadowPackageManagerTest {
}
@Test
+ @Config(minSdk = VERSION_CODES.R)
+ public void getInstallerSourceInfo_notExists_throwsException() throws Exception {
+ assertThrows(
+ NameNotFoundException.class,
+ () -> packageManager.getInstallSourceInfo("nonExistTarget.package"));
+ }
+
+ @Test
public void getXml() {
XmlResourceParser in =
packageManager.getXml(
@@ -3380,6 +3455,21 @@ public class ShadowPackageManagerTest {
}
@Test
+ @Config(minSdk = TIRAMISU)
+ public void addService_withComponentInfoFlags() throws Exception {
+ ServiceInfo serviceInfo = new ServiceInfo();
+ serviceInfo.name = "name";
+ serviceInfo.packageName = "package";
+
+ shadowOf(packageManager).addOrUpdateService(serviceInfo);
+
+ assertThat(
+ packageManager.getServiceInfo(
+ new ComponentName("package", "name"), PackageManager.ComponentInfoFlags.of(0)))
+ .isNotNull();
+ }
+
+ @Test
public void addProvider() throws Exception {
ProviderInfo providerInfo = new ProviderInfo();
providerInfo.name = "name";
@@ -3402,6 +3492,21 @@ public class ShadowPackageManagerTest {
}
@Test
+ @Config(minSdk = TIRAMISU)
+ public void addReceiver_withComponentInfoFlags() throws Exception {
+ ActivityInfo receiverInfo = new ActivityInfo();
+ receiverInfo.name = "name";
+ receiverInfo.packageName = "package";
+
+ shadowOf(packageManager).addOrUpdateReceiver(receiverInfo);
+
+ assertThat(
+ packageManager.getReceiverInfo(
+ new ComponentName("package", "name"), PackageManager.ComponentInfoFlags.of(0)))
+ .isNotNull();
+ }
+
+ @Test
public void addActivity_addsNewPackage() throws Exception {
ActivityInfo activityInfo = new ActivityInfo();
activityInfo.name = "name";
@@ -3476,6 +3581,22 @@ public class ShadowPackageManagerTest {
}
@Test
+ @Config(minSdk = TIRAMISU)
+ public void removeService_withComponentInfoFlags() {
+ ComponentName componentName = new ComponentName(context, "com.foo.Service");
+
+ ServiceInfo removed = shadowOf(packageManager).removeService(componentName);
+
+ assertThat(removed).isNotNull();
+ try {
+ packageManager.getServiceInfo(componentName, PackageManager.ComponentInfoFlags.of(0));
+ fail();
+ } catch (NameNotFoundException e) {
+ // expected
+ }
+ }
+
+ @Test
public void removeProvider() {
ComponentName componentName =
new ComponentName(context, "org.robolectric.shadows.testing.TestContentProvider1");
@@ -3508,6 +3629,23 @@ public class ShadowPackageManagerTest {
}
@Test
+ @Config(minSdk = TIRAMISU)
+ public void removeReceiver_withComponentInfoFlags() {
+ ComponentName componentName =
+ new ComponentName(context, "org.robolectric.fakes.ConfigTestReceiver");
+
+ ActivityInfo removed = shadowOf(packageManager).removeReceiver(componentName);
+
+ assertThat(removed).isNotNull();
+ try {
+ packageManager.getReceiverInfo(componentName, PackageManager.ComponentInfoFlags.of(0));
+ fail();
+ } catch (NameNotFoundException e) {
+ // expected
+ }
+ }
+
+ @Test
public void removeNonExistingComponent() {
ComponentName componentName = new ComponentName(context, "org.robolectric.DoesnExist");
@@ -4392,7 +4530,6 @@ public class ShadowPackageManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR2)
public void getPackagesHoldingPermissions_returnPackages() {
String permissionA = "com.android.providers.permission.test.a";
String permissionB = "com.android.providers.permission.test.b";
@@ -4419,7 +4556,6 @@ public class ShadowPackageManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR2)
public void getPackagesHoldingPermissions_returnsEmpty() {
String permissionA = "com.android.providers.permission.test.a";
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowPaintDrawableTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowPaintDrawableTest.java
new file mode 100644
index 000000000..b8041a8b6
--- /dev/null
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowPaintDrawableTest.java
@@ -0,0 +1,28 @@
+package org.robolectric.shadows;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.graphics.drawable.PaintDrawable;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public final class ShadowPaintDrawableTest {
+ @Test
+ public void noCornerRadii_returnsNull() {
+ PaintDrawable paintDrawable = new PaintDrawable();
+ assertThat(shadowOf(paintDrawable).getCornerRadii()).isEqualTo(null);
+ }
+
+ @Test
+ public void getCornerRadii_returnsCornerRadii() {
+ PaintDrawable paintDrawable = new PaintDrawable();
+
+ float[] radii = {10f, 20f, 30f, 40f, 50f, 60f, 70f, 80f};
+ paintDrawable.setCornerRadii(radii);
+
+ assertThat(shadowOf(paintDrawable).getCornerRadii()).isEqualTo(radii);
+ }
+}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowParcelFileDescriptorTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowParcelFileDescriptorTest.java
index 4cb7c06c9..c14709fde 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowParcelFileDescriptorTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowParcelFileDescriptorTest.java
@@ -1,9 +1,9 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE;
import static com.google.common.truth.Truth.assertThat;
import static java.nio.charset.Charset.defaultCharset;
+import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertThrows;
import static org.junit.Assume.assumeThat;
import static org.robolectric.Shadows.shadowOf;
@@ -22,13 +22,14 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
import java.util.concurrent.atomic.AtomicBoolean;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowParcelFileDescriptor.FileDescriptorFromParcelUnavailableException;
import org.robolectric.util.ReflectionHelpers;
@@ -88,7 +89,6 @@ public class ShadowParcelFileDescriptorTest {
}
@Test
- @Config(minSdk = KITKAT)
public void testOpenWithOnCloseListener_nullHandler() throws Exception {
final AtomicBoolean onCloseCalled = new AtomicBoolean(false);
ParcelFileDescriptor.OnCloseListener onCloseListener =
@@ -106,7 +106,6 @@ public class ShadowParcelFileDescriptorTest {
}
@Test
- @Config(minSdk = KITKAT)
public void testOpenWithOnCloseListener_nullOnCloseListener() throws Exception {
HandlerThread handlerThread = new HandlerThread("test");
handlerThread.start();
@@ -118,7 +117,6 @@ public class ShadowParcelFileDescriptorTest {
}
@Test
- @Config(minSdk = KITKAT)
public void testOpenWithOnCloseListener_callsListenerOnClose() throws Exception {
HandlerThread handlerThread = new HandlerThread("test");
handlerThread.start();
@@ -428,6 +426,92 @@ public class ShadowParcelFileDescriptorTest {
assertThat(dupFile.valid()).isTrue();
}
+ @Test
+ public void testStaticDup_returnsFd() throws Exception {
+ RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
+ FileDescriptor fd = randomAccessFile.getFD();
+ ParcelFileDescriptor dupFd = ParcelFileDescriptor.dup(fd);
+
+ assertThat(fd.valid()).isTrue();
+ assertThat(dupFd.getFileDescriptor().valid()).isTrue();
+ }
+
+ @Test
+ public void testStaticDup_sameContent() throws Exception {
+ ParcelFileDescriptor pfd = null;
+ File tempFile = File.createTempFile("testFile", ".txt");
+ String content = "abc123";
+ Files.asCharSink(tempFile, UTF_8).write(content);
+
+ try (FileInputStream fis = new FileInputStream(tempFile)) {
+ FileDescriptor fd = fis.getFD();
+ pfd = ParcelFileDescriptor.dup(fd);
+ assertThat(readLine(pfd.getFileDescriptor())).isEqualTo(content);
+ assertThat(readLine(fd)).isEqualTo(content);
+ } finally {
+ if (pfd != null) {
+ pfd.close();
+ }
+ }
+
+ tempFile.delete();
+ }
+
+ @Test
+ public void testStaticDup_oldFilePositionDoesNotChange() throws Exception {
+ ParcelFileDescriptor pfd = null;
+ File tempFile = File.createTempFile("testFile", ".txt");
+ String content = "abc123";
+ Files.asCharSink(tempFile, UTF_8).write(content);
+
+ try (FileInputStream fis = new FileInputStream(tempFile)) {
+ FileDescriptor fd = fis.getFD();
+ long oldFilePosition = getCurrentFilePosition(fd);
+ pfd = ParcelFileDescriptor.dup(fd);
+ long newFilePosition = getCurrentFilePosition(fd);
+
+ assertThat(newFilePosition).isEqualTo(oldFilePosition);
+ } finally {
+ if (pfd != null) {
+ pfd.close();
+ }
+ }
+
+ tempFile.delete();
+ }
+
+ @Test
+ public void testStaticDup_afterWrite() throws Exception {
+ File tempFile = File.createTempFile("testFile", ".txt");
+ RandomAccessFile randomAccessFile = new RandomAccessFile(tempFile, "rw");
+ FileDescriptor fd = randomAccessFile.getFD();
+ String content = "abc123";
+ OutputStream writer = new FileOutputStream(fd);
+
+ writer.write(content.getBytes(UTF_8));
+ try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(fd)) {
+ assertThat(readLine(pfd.getFileDescriptor())).isEqualTo(content);
+ } finally {
+ writer.close();
+ }
+ }
+
+ @Test
+ public void testClose_afterDup_doesNotCloseOriginalFd() throws Exception {
+ ParcelFileDescriptor pfd = null;
+ File tempFile = File.createTempFile("testFile", ".txt");
+ String content = "abc123";
+ Files.asCharSink(tempFile, UTF_8).write(content);
+
+ try (FileInputStream fis = new FileInputStream(tempFile)) {
+ FileDescriptor fd = fis.getFD();
+ pfd = ParcelFileDescriptor.dup(fd);
+ pfd.close();
+ assertThat(fd.valid()).isTrue();
+ }
+ tempFile.delete();
+ }
+
private static String readLine(FileDescriptor fd) throws IOException {
try (BufferedReader reader =
new BufferedReader(new InputStreamReader(new FileInputStream(fd), defaultCharset()))) {
@@ -445,4 +529,9 @@ public class ShadowParcelFileDescriptorTest {
parcel.recycle();
}
}
+
+ private static long getCurrentFilePosition(FileDescriptor fd) throws IOException {
+ FileInputStream is = new FileInputStream(fd);
+ return is.getChannel().position();
+ }
}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowPausedAsyncTaskLoaderTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowPausedAsyncTaskLoaderTest.java
index f8d1de1df..b524f382d 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowPausedAsyncTaskLoaderTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowPausedAsyncTaskLoaderTest.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.content.AsyncTaskLoader;
@@ -12,12 +11,10 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.android.util.concurrent.PausedExecutorService;
-import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;
/** Tests for {@link ShadowPausedAsyncTaskLoader}. */
@RunWith(AndroidJUnit4.class)
-@Config(minSdk = KITKAT)
public class ShadowPausedAsyncTaskLoaderTest {
private final List<String> taskRecord = new ArrayList<>();
private TestLoader testLoader;
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowPausedLooperTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowPausedLooperTest.java
index 71e85c9b6..ebb9e02d8 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowPausedLooperTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowPausedLooperTest.java
@@ -13,6 +13,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;
import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
+import static org.robolectric.util.reflector.Reflector.reflector;
import android.os.Build.VERSION_CODES;
import android.os.Handler;
@@ -21,6 +22,7 @@ import android.os.Looper;
import android.os.MessageQueue.IdleHandler;
import android.os.SystemClock;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.common.util.concurrent.SettableFuture;
import java.time.Duration;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
@@ -28,16 +30,20 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.LooperMode;
import org.robolectric.res.android.Ref;
import org.robolectric.shadow.api.Shadow;
+import org.robolectric.util.reflector.Direct;
+import org.robolectric.util.reflector.ForType;
@RunWith(AndroidJUnit4.class)
@LooperMode(LooperMode.Mode.PAUSED)
@@ -643,6 +649,42 @@ public class ShadowPausedLooperTest {
assertThat(wasRun.get()).isTrue();
}
+ /**
+ * Tests a race condition that could occur if a paused background Looper was quit but the thread
+ * was still alive. The resetter would attempt to unpause it, but the message would never run
+ * because the looper was quit. This caused a deadlock.
+ */
+ @Test
+ public void looper_customThread_unPauseAfterQuit() throws Exception {
+ for (int i = 0; i < 100; i++) {
+ final SettableFuture<Looper> future = SettableFuture.create();
+ final CountDownLatch countDownLatch = new CountDownLatch(1);
+
+ Thread t =
+ new Thread(
+ () -> {
+ try {
+ Looper.prepare();
+ } finally {
+ future.set(Looper.myLooper());
+ }
+ Looper.loop();
+ try {
+ countDownLatch.await();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ });
+ t.start();
+ Looper looper = future.get();
+ shadowOf(looper).pause();
+ new Handler(looper).post(() -> looper.quitSafely());
+ shadowOf(looper).idle();
+ ((ShadowPausedLooper) shadowOf(looper)).resetLooperToInitialState();
+ countDownLatch.countDown();
+ }
+ }
+
@Test
@Config(minSdk = VERSION_CODES.P)
public void runOneTask_ignoreSyncBarrier_with_async() {
@@ -660,6 +702,73 @@ public class ShadowPausedLooperTest {
Looper.getMainLooper().getQueue().removeSyncBarrier(barrier);
}
+ /**
+ * Verify that calling a control operation like idle while a sync barrier is being held doesn't
+ * deadlock the looper
+ */
+ @Test
+ public void idle_paused_onSyncBarrier() {
+
+ Handler handler = new Handler(handlerThread.getLooper());
+ Handler asyncHandler = ShadowPausedLooper.createAsyncHandler(handlerThread.getLooper());
+ ShadowPausedLooper shadowLooper = Shadow.extract(handlerThread.getLooper());
+ shadowLooper.pause();
+
+ AtomicInteger token = new AtomicInteger(-1);
+ AtomicBoolean wasRun = new AtomicBoolean(false);
+ handler.post(
+ () -> {
+ token.set(postSyncBarrierCompat(handlerThread.getLooper()));
+ handler.post(
+ () -> {
+ wasRun.set(true);
+ });
+ });
+ shadowLooper.idle();
+ assertThat(token.get()).isNotEqualTo(-1);
+ assertThat(wasRun.get()).isEqualTo(false);
+ // should be effectively a no-op and not deadlock
+ shadowLooper.idle();
+ // remove sync barriers messages need to get posted as async
+ asyncHandler.post(
+ () -> {
+ removeSyncBarrierCompat(handlerThread.getLooper(), token.get());
+ });
+ shadowLooper.idle();
+ assertThat(wasRun.get()).isEqualTo(true);
+ }
+
+ /** Similar to previous test but with a running aka unpaused looper. */
+ @Test
+ public void idle_running_onSyncBarrier() {
+ Handler handler = new Handler(handlerThread.getLooper());
+ Handler asyncHandler = ShadowPausedLooper.createAsyncHandler(handlerThread.getLooper());
+ ShadowPausedLooper shadowLooper = Shadow.extract(handlerThread.getLooper());
+
+ AtomicInteger token = new AtomicInteger(-1);
+ AtomicBoolean wasRun = new AtomicBoolean(false);
+ handler.post(
+ () -> {
+ token.set(postSyncBarrierCompat(handlerThread.getLooper()));
+ handler.post(
+ () -> {
+ wasRun.set(true);
+ });
+ });
+ shadowLooper.idle();
+ assertThat(token.get()).isNotEqualTo(-1);
+ assertThat(wasRun.get()).isEqualTo(false);
+ // should be effectively a no-op and not deadlock
+ shadowLooper.idle();
+ // remove sync barriers messages need to get posted as async
+ asyncHandler.post(
+ () -> {
+ removeSyncBarrierCompat(handlerThread.getLooper(), token.get());
+ });
+ shadowLooper.idle();
+ assertThat(wasRun.get()).isEqualTo(true);
+ }
+
private static class BlockingRunnable implements Runnable {
CountDownLatch latch = new CountDownLatch(1);
@@ -675,7 +784,31 @@ public class ShadowPausedLooperTest {
private void postToMainLooper() {
// just post a runnable and rely on setUp to check
Handler handler = new Handler(getMainLooper());
- Runnable mockRunnable = mock(Runnable.class);
- handler.post(mockRunnable);
+ handler.post(() -> {});
+ }
+
+ private static int postSyncBarrierCompat(Looper looper) {
+ if (RuntimeEnvironment.getApiLevel() >= 23) {
+ return looper.getQueue().postSyncBarrier();
+ } else {
+ return reflector(LooperReflector.class, looper).postSyncBarrier();
+ }
+ }
+
+ private static void removeSyncBarrierCompat(Looper looper, int token) {
+ if (RuntimeEnvironment.getApiLevel() >= 23) {
+ looper.getQueue().removeSyncBarrier(token);
+ } else {
+ reflector(LooperReflector.class, looper).removeSyncBarrier(token);
+ }
+ }
+
+ @ForType(Looper.class)
+ private interface LooperReflector {
+ @Direct
+ int postSyncBarrier();
+
+ @Direct
+ void removeSyncBarrier(int token);
}
}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowPausedSystemClockTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowPausedSystemClockTest.java
index b0261527d..7eaa98896 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowPausedSystemClockTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowPausedSystemClockTest.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static android.os.Build.VERSION_CODES.P;
import static android.os.Build.VERSION_CODES.Q;
import static com.google.common.truth.Truth.assertThat;
@@ -12,6 +11,7 @@ import static org.robolectric.annotation.LooperMode.Mode.PAUSED;
import android.os.SystemClock;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.time.DateTimeException;
+import java.time.Duration;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -32,6 +32,7 @@ public class ShadowPausedSystemClockTest {
assertTrue(SystemClock.setCurrentTimeMillis(1000));
SystemClock.sleep(34);
assertThat(SystemClock.uptimeMillis()).isEqualTo(1034);
+ assertThat(SystemClock.elapsedRealtime()).isEqualTo(1034);
}
@Test
@@ -69,11 +70,58 @@ public class ShadowPausedSystemClockTest {
}
@Test
+ public void deepSleep_advancesOnlyRealtime() {
+ assertTrue(SystemClock.setCurrentTimeMillis(1000));
+
+ ShadowPausedSystemClock.deepSleep(34);
+
+ assertThat(SystemClock.uptimeMillis()).isEqualTo(1000);
+ assertThat(SystemClock.elapsedRealtime()).isEqualTo(1034);
+ }
+
+ @Test
+ public void deepSleep_notifiesListener() {
+ AtomicBoolean listenerCalled = new AtomicBoolean();
+ ShadowPausedSystemClock.addListener(() -> listenerCalled.set(true));
+
+ ShadowPausedSystemClock.deepSleep(100);
+
+ assertThat(listenerCalled.get()).isTrue();
+ }
+
+ @SuppressWarnings("FutureReturnValueIgnored")
+ @Test
+ public void deepSleep_concurrentAccess_doesNotCorruptData() throws Exception {
+ SystemClock.setCurrentTimeMillis(100);
+ ExecutorService executor = Executors.newFixedThreadPool(2);
+ try {
+ int numToExecute = 10000;
+ CountDownLatch latch = new CountDownLatch(numToExecute);
+
+ for (int i = 0; i < numToExecute; i++) {
+ executor.submit(
+ () -> {
+ ShadowPausedSystemClock.deepSleep(100);
+ latch.countDown();
+ });
+ }
+ latch.await();
+
+ assertThat(SystemClock.uptimeMillis()).isEqualTo(100);
+ assertThat(SystemClock.elapsedRealtime()).isEqualTo(100 + numToExecute * 100);
+ } finally {
+ executor.shutdown();
+ }
+ }
+
+ @Test
public void testSetCurrentTime() {
assertTrue(SystemClock.setCurrentTimeMillis(1034));
+ assertThat(SystemClock.elapsedRealtime()).isEqualTo(1034);
assertThat(SystemClock.uptimeMillis()).isEqualTo(1034);
assertThat(SystemClock.currentThreadTimeMillis()).isEqualTo(1034);
assertFalse(SystemClock.setCurrentTimeMillis(1000));
+ assertThat(SystemClock.elapsedRealtime()).isEqualTo(1034);
assertThat(SystemClock.uptimeMillis()).isEqualTo(1034);
}
@@ -104,7 +152,7 @@ public class ShadowPausedSystemClockTest {
latch.countDown();
});
latch.await();
-
+ assertThat(SystemClock.elapsedRealtime()).isEqualTo(300);
assertThat(SystemClock.uptimeMillis()).isEqualTo(300);
} finally {
executor.shutdown();
@@ -112,13 +160,34 @@ public class ShadowPausedSystemClockTest {
}
@Test
+ public void advanceTimeBy_shouldAdvanceBothElapsedRealtimeAndUptimeMillis() {
+ SystemClock.setCurrentTimeMillis(1000);
+
+ ShadowPausedSystemClock.advanceBy(Duration.ofMillis(100));
+
+ assertThat(SystemClock.uptimeMillis()).isEqualTo(1100);
+ assertThat(SystemClock.elapsedRealtime()).isEqualTo(1100);
+ assertThat(SystemClock.currentThreadTimeMillis()).isEqualTo(1100);
+ }
+
+ @Test
+ public void simulateDeepSleep_shouldOnlyAdvanceElapsedRealtime() {
+ SystemClock.setCurrentTimeMillis(1000);
+
+ ShadowPausedSystemClock.simulateDeepSleep(Duration.ofMillis(100));
+
+ assertThat(SystemClock.uptimeMillis()).isEqualTo(1000);
+ assertThat(SystemClock.currentThreadTimeMillis()).isEqualTo(1000);
+ assertThat(SystemClock.elapsedRealtime()).isEqualTo(1100);
+ }
+
+ @Test
public void testElapsedRealtime() {
SystemClock.setCurrentTimeMillis(1000);
assertThat(SystemClock.elapsedRealtime()).isEqualTo(1000);
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void testElapsedRealtimeNanos() {
SystemClock.setCurrentTimeMillis(1000);
assertThat(SystemClock.elapsedRealtimeNanos()).isEqualTo(1000000000);
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowPendingIntentTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowPendingIntentTest.java
index 58d219cf7..61cbb18c1 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowPendingIntentTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowPendingIntentTest.java
@@ -894,7 +894,6 @@ public class ShadowPendingIntentTest {
}
@Test
- @Config(minSdk = Build.VERSION_CODES.JELLY_BEAN_MR1)
public void testGetCreatorPackage_nothingSet() {
PendingIntent pendingIntent = PendingIntent.getActivity(context, 99, new Intent("activity"), 0);
assertThat(pendingIntent.getCreatorPackage()).isEqualTo(context.getPackageName());
@@ -902,7 +901,6 @@ public class ShadowPendingIntentTest {
}
@Test
- @Config(minSdk = Build.VERSION_CODES.JELLY_BEAN_MR1)
public void testGetCreatorPackage_explicitlySetPackage() {
String fakePackage = "some.fake.package";
PendingIntent pendingIntent = PendingIntent.getActivity(context, 99, new Intent("activity"), 0);
@@ -912,7 +910,6 @@ public class ShadowPendingIntentTest {
}
@Test
- @Config(minSdk = Build.VERSION_CODES.JELLY_BEAN_MR1)
public void testGetCreatorUid() {
int fakeUid = 123;
PendingIntent pendingIntent = PendingIntent.getActivity(context, 99, new Intent("activity"), 0);
@@ -972,7 +969,6 @@ public class ShadowPendingIntentTest {
}
@Test
- @Config(minSdk = Build.VERSION_CODES.JELLY_BEAN)
public void toString_doesNotNPE() {
assertThat(
PendingIntent.getBroadcast(
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowPosixTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowPosixTest.java
index 05fe13a16..437c313b7 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowPosixTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowPosixTest.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static com.google.common.truth.Truth.assertThat;
@@ -52,7 +51,6 @@ public final class ShadowPosixTest {
}
@Test
- @Config(maxSdk = KITKAT)
public void getStatBelowLollipop_returnCorrectMode() throws Exception {
Object stat = ShadowPosix.stat(path);
int mode = ReflectionHelpers.getField(stat, "st_mode");
@@ -60,7 +58,6 @@ public final class ShadowPosixTest {
}
@Test
- @Config(minSdk = KITKAT)
public void getStatBelowLollipop_returnCorrectSize() throws Exception {
Object stat = ShadowPosix.stat(path);
long size = ReflectionHelpers.getField(stat, "st_size");
@@ -68,7 +65,6 @@ public final class ShadowPosixTest {
}
@Test
- @Config(minSdk = KITKAT)
public void getStatBelowtLollipop_returnCorrectModifiedTime() throws Exception {
Object stat = ShadowPosix.stat(path);
long modifiedTime = ReflectionHelpers.getField(stat, "st_mtime");
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowPreferenceGroupTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowPreferenceGroupTest.java
index f70bcd251..f21c21fda 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowPreferenceGroupTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowPreferenceGroupTest.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 static org.robolectric.Robolectric.buildActivity;
import static org.robolectric.Shadows.shadowOf;
@@ -16,10 +15,6 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.Config;
-import org.robolectric.util.ReflectionHelpers;
-import org.robolectric.util.ReflectionHelpers.ClassParameter;
@RunWith(AndroidJUnit4.class)
public class ShadowPreferenceGroupTest {
@@ -37,7 +32,7 @@ public class ShadowPreferenceGroupTest {
group = new TestPreferenceGroup(activity, attrs);
shadow = shadowOf(group);
- shadow.callOnAttachedToHierarchy(newPreferenceManager(activity, 0));
+ shadow.callOnAttachedToHierarchy(new PreferenceManager(activity, 0));
pref1 = new Preference(activity);
pref1.setKey("pref1");
@@ -46,18 +41,6 @@ public class ShadowPreferenceGroupTest {
pref2.setKey("pref2");
}
- private static PreferenceManager newPreferenceManager(Activity activity, int firstRequestCode) {
- if (RuntimeEnvironment.getApiLevel() >= KITKAT) {
- return new PreferenceManager(activity, 0);
- } else {
- // Constructor was not public before KITKAT.
- return ReflectionHelpers.callConstructor(
- PreferenceManager.class,
- ClassParameter.from(Activity.class, activity),
- ClassParameter.from(int.class, 0));
- }
- }
-
@Test
public void shouldInheritFromPreference() {
assertThat(shadow).isInstanceOf(ShadowPreference.class);
@@ -153,7 +136,6 @@ public class ShadowPreferenceGroupTest {
assertThat(group.findPreference(pref2.getKey())).isSameInstanceAs(pref2);
}
- @Config(minSdk = KITKAT)
@Test
public void shouldFindPreferenceRecursively() {
TestPreferenceGroup group2 = new TestPreferenceGroup(activity, attrs);
@@ -166,7 +148,6 @@ public class ShadowPreferenceGroupTest {
assertThat(group.findPreference(pref2.getKey())).isSameInstanceAs(pref2);
}
- @Config(minSdk = KITKAT)
@Test
public void shouldSetEnabledRecursively() {
boolean[] values = {false, true};
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowRelativeLayoutTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowRelativeLayoutTest.java
index f741b638b..6cc90a3b9 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowRelativeLayoutTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowRelativeLayoutTest.java
@@ -1,7 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static com.google.common.truth.Truth.assertThat;
import android.view.ViewGroup;
@@ -11,13 +9,11 @@ import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.annotation.Config;
@RunWith(AndroidJUnit4.class)
public class ShadowRelativeLayoutTest {
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void getRules_shouldShowAddRuleData_sinceApiLevel17() {
ImageView imageView = new ImageView(ApplicationProvider.getApplicationContext());
RelativeLayout layout = new RelativeLayout(ApplicationProvider.getApplicationContext());
@@ -28,17 +24,4 @@ public class ShadowRelativeLayoutTest {
int[] rules = layoutParams.getRules();
assertThat(rules).isEqualTo(new int[]{0, 0, 0, 0, 0, 0, 1234, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0});
}
-
- @Test
- @Config(maxSdk = JELLY_BEAN)
- public void getRules_shouldShowAddRuleData_uptoApiLevel16() {
- ImageView imageView = new ImageView(ApplicationProvider.getApplicationContext());
- RelativeLayout layout = new RelativeLayout(ApplicationProvider.getApplicationContext());
- layout.addView(imageView, new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
- RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) imageView.getLayoutParams();
- layoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
- layoutParams.addRule(RelativeLayout.ALIGN_TOP, 1234);
- int[] rules = layoutParams.getRules();
- assertThat(rules).isEqualTo(new int[]{0, 0, 0, 0, 0, 0, 1234, 0, 0, 0, 0, -1, 0, 0, 0, 0});
- }
}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowResourcesTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowResourcesTest.java
index a4732b920..9a71b9f87 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowResourcesTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowResourcesTest.java
@@ -1,11 +1,14 @@
package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.N_MR1;
+import static android.os.Build.VERSION_CODES.Q;
+import static android.os.Build.VERSION_CODES.S;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.TruthJUnit.assume;
import static org.robolectric.Shadows.shadowOf;
import static org.robolectric.shadows.ShadowAssetManager.useLegacy;
+import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -15,13 +18,18 @@ import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.util.AttributeSet;
+import android.util.SparseIntArray;
import android.util.TypedValue;
+import android.widget.RemoteViews;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.common.collect.Range;
import java.io.InputStream;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.IntStream;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.R;
@@ -31,6 +39,23 @@ import org.robolectric.annotation.Config;
@RunWith(AndroidJUnit4.class)
public class ShadowResourcesTest {
+ private static final int FIRST_RESOURCE_COLOR_ID = android.R.color.system_neutral1_0;
+ private static final int LAST_RESOURCE_COLOR_ID = android.R.color.system_accent3_1000;
+
+ /**
+ * Some green/blue colors, from system_neutral1_0 to system_accent3_1000, extracted using 0xB1EBFF
+ * as seed color and "FRUIT_SALAD" as theme style.
+ */
+ private static final int[] greenBlueColorBase = {
+ -1, -393729, -1641480, -2562838, -4405043, -6181454, -7892073, -9668483, -11181979, -12760755,
+ -14208458, -15590111, -16777216, -1, -393729, -2296322, -3217680, -4994349, -6770760, -8547171,
+ -10257790, -11836822, -13350318, -14863301, -16376283, -16777216, -1, -720905, -4456478,
+ -8128307, -10036302, -12075112, -14638210, -16742810, -16749487, -16756420, -16762839,
+ -16768746, -16777216, -1, -720905, -4456478, -5901613, -7678281, -9454947, -11231613, -13139095,
+ -15111342, -16756420, -16762839, -16768746, -16777216, -1, -393729, -2361857, -5051393,
+ -7941655, -9783603, -11625551, -13729642, -16750723, -16757153, -16763326, -16769241, -16777216
+ };
+
private Resources resources;
@Before
@@ -295,4 +320,43 @@ public class ShadowResourcesTest {
assertThat(resourcesSubclass).isNotNull();
}
+
+ @Test
+ @Config(minSdk = S)
+ public void getColor_shouldReturnCorrectMaterialYouColor() throws Exception {
+ SparseIntArray sparseArray =
+ new SparseIntArray(LAST_RESOURCE_COLOR_ID - FIRST_RESOURCE_COLOR_ID + 1);
+ IntStream.range(0, greenBlueColorBase.length)
+ .forEach(i -> sparseArray.put(FIRST_RESOURCE_COLOR_ID + i, greenBlueColorBase[i]));
+ int basicColor = android.R.color.system_neutral1_10;
+ Context context = ApplicationProvider.getApplicationContext();
+ RemoteViews.ColorResources colorResources =
+ RemoteViews.ColorResources.create(context, sparseArray);
+ assertThat(colorResources).isNotNull();
+
+ colorResources.apply(context);
+
+ assertThat(basicColor).isNotEqualTo(0);
+ assertThat(resources.getColor(basicColor, /* theme= */ null)).isEqualTo(-393729);
+ }
+
+ @Ignore("Re-enable when performing benchmarks")
+ @Test
+ @Config(sdk = Q)
+ public void benchmarkUpdateConfiguration() {
+ long startTime = System.nanoTime();
+ Resources systemResources = Resources.getSystem();
+ for (int i = 0; i < 10_000; i++) {
+ Configuration oldConfig = resources.getConfiguration();
+ Configuration newConfig = new Configuration(oldConfig);
+ // This change triggers RebuildFilterList in CppAssetManager2
+ newConfig.colorMode = 3;
+ systemResources.updateConfiguration(newConfig, resources.getDisplayMetrics());
+ systemResources.updateConfiguration(oldConfig, resources.getDisplayMetrics());
+ }
+ long endTime = System.nanoTime();
+ long elapsedNs = endTime - startTime;
+ System.err.println(
+ "updateConfiguration benchmark took " + TimeUnit.NANOSECONDS.toMillis(elapsedNs) + " ms");
+ }
}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowRoleManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowRoleManagerTest.java
index 86109b095..df472eb36 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowRoleManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowRoleManagerTest.java
@@ -1,6 +1,7 @@
package org.robolectric.shadows;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import static org.junit.Assert.assertThrows;
import static org.robolectric.RuntimeEnvironment.getApplication;
import static org.robolectric.Shadows.shadowOf;
@@ -8,7 +9,9 @@ import static org.robolectric.Shadows.shadowOf;
import android.app.role.RoleManager;
import android.content.Context;
import android.os.Build;
+import androidx.test.core.content.pm.PackageInfoBuilder;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -25,25 +28,25 @@ public final class ShadowRoleManagerTest {
roleManager = (RoleManager) getApplication().getSystemService(Context.ROLE_SERVICE);
}
- @Test(expected = IllegalArgumentException.class)
+ @Test
public void isRoleHeld_shouldThrowWithNullArgument() {
- shadowOf(roleManager).isRoleHeld(null);
+ assertThrows(IllegalArgumentException.class, () -> shadowOf(roleManager).isRoleHeld(null));
}
- @Test()
+ @Test
public void addHeldRole_isPresentInIsRoleHeld() {
shadowOf(roleManager).addHeldRole(RoleManager.ROLE_SMS);
assertThat(roleManager.isRoleHeld(RoleManager.ROLE_SMS)).isTrue();
}
- @Test()
+ @Test
public void removeHeldRole_notPresentInIsRoleHeld() {
shadowOf(roleManager).addHeldRole(RoleManager.ROLE_SMS);
shadowOf(roleManager).removeHeldRole(RoleManager.ROLE_SMS);
assertThat(roleManager.isRoleHeld(RoleManager.ROLE_SMS)).isFalse();
}
- @Test()
+ @Test
public void isRoleHeld_noValueByDefault() {
assertThat(roleManager.isRoleHeld(RoleManager.ROLE_SMS)).isFalse();
}
@@ -53,26 +56,75 @@ public final class ShadowRoleManagerTest {
assertThrows(IllegalArgumentException.class, () -> shadowOf(roleManager).isRoleAvailable(null));
}
- @Test()
+ @Test
public void addAvailableRole_isPresentInIsRoleAvailable() {
- shadowOf(roleManager).addAvailableRole(RoleManager.ROLE_SMS);
- assertThat(roleManager.isRoleAvailable(RoleManager.ROLE_SMS)).isTrue();
+ shadowOf(roleManager).addAvailableRole("some.weird.role");
+ assertThat(roleManager.isRoleAvailable("some.weird.role")).isTrue();
}
- @Test()
+ @Test
public void addAvailableRole_shouldThrowWithEmptyArgument() {
assertThrows(IllegalArgumentException.class, () -> shadowOf(roleManager).addAvailableRole(""));
}
- @Test()
+ @Test
public void removeAvailableRole_notPresentInIsRoleAvailable() {
shadowOf(roleManager).addAvailableRole(RoleManager.ROLE_SMS);
shadowOf(roleManager).removeAvailableRole(RoleManager.ROLE_SMS);
assertThat(roleManager.isRoleAvailable(RoleManager.ROLE_SMS)).isFalse();
}
- @Test()
- public void isRoleAvailable_noValueByDefault() {
- assertThat(roleManager.isRoleAvailable(RoleManager.ROLE_SMS)).isFalse();
+ @Test
+ @Config(minSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public void getDefaultApplication_shouldReturnRoleOwner() {
+ shadowOf(roleManager).addHeldRole(RoleManager.ROLE_SMS);
+ assertThat(roleManager.getDefaultApplication(RoleManager.ROLE_SMS))
+ .isEqualTo(getApplication().getPackageName());
+ }
+
+ @Test
+ @Config(minSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public void getDefaultApplication_shouldReturnPackageSet() {
+ shadowOf(roleManager).addAvailableRole(RoleManager.ROLE_SMS);
+ shadowOf(getApplication().getPackageManager())
+ .installPackage(PackageInfoBuilder.newBuilder().setPackageName("test.app").build());
+ AtomicBoolean resultHolder = new AtomicBoolean(false);
+ shadowOf(roleManager)
+ .setDefaultApplication(
+ RoleManager.ROLE_SMS,
+ "test.app",
+ 0,
+ directExecutor(),
+ result -> resultHolder.set(result));
+ assertThat(roleManager.getDefaultApplication(RoleManager.ROLE_SMS)).isEqualTo("test.app");
+ assertThat(resultHolder.get()).isTrue();
+ }
+
+ @Test
+ @Config(minSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public void setDefaultApplication_checksAppInstalled() {
+ AtomicBoolean resultHolder = new AtomicBoolean(true);
+ shadowOf(roleManager)
+ .setDefaultApplication(
+ RoleManager.ROLE_SMS,
+ "test.app",
+ 0,
+ directExecutor(),
+ result -> resultHolder.set(result));
+ assertThat(resultHolder.get()).isFalse();
+ assertThat(roleManager.getDefaultApplication(RoleManager.ROLE_SMS)).isNull();
+ }
+
+ @Test
+ @Config(minSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public void setDefaultApplication_checksRoleAllowed() {
+ shadowOf(getApplication().getPackageManager())
+ .installPackage(PackageInfoBuilder.newBuilder().setPackageName("test.app").build());
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ shadowOf(roleManager)
+ .setDefaultApplication(
+ "bogus.role", "test.app", 0, directExecutor(), result -> {}));
}
}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowSafetyCenterManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowSafetyCenterManagerTest.java
index b9977262b..d0e3e61bb 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowSafetyCenterManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowSafetyCenterManagerTest.java
@@ -2,6 +2,7 @@ package org.robolectric.shadows;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
import android.os.Build.VERSION_CODES;
import android.safetycenter.SafetyCenterManager;
@@ -10,6 +11,7 @@ import android.safetycenter.SafetySourceData;
import android.safetycenter.SafetySourceErrorDetails;
import org.junit.Before;
import org.junit.Test;
+import org.junit.function.ThrowingRunnable;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
@@ -385,4 +387,107 @@ public final class ShadowSafetyCenterManagerTest {
assertThat(shadowSafetyCenterManager.getLastSafetySourceError("id2"))
.isSameInstanceAs(errorDetails2);
}
+
+ @Test
+ public void throwOnSafetySourceId_safetyCenterDisabled_doesntThrowForAllIds() {
+ SafetyCenterManager safetyCenterManager =
+ getApplicationContext().getSystemService(SafetyCenterManager.class);
+ ShadowSafetyCenterManager shadowSafetyCenterManager =
+ Shadow.extract(getApplicationContext().getSystemService(SafetyCenterManager.class));
+
+ shadowSafetyCenterManager.throwOnSafetySourceId("id");
+
+ shadowSafetyCenterManager.setSafetyCenterEnabled(false);
+ safetyCenterManager.setSafetySourceData(
+ "id",
+ new SafetySourceData.Builder().build(),
+ new SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_REFRESH_REQUESTED)
+ .setRefreshBroadcastId("id")
+ .build());
+ SafetySourceData unused = safetyCenterManager.getSafetySourceData("id");
+ safetyCenterManager.reportSafetySourceError(
+ "id",
+ new SafetySourceErrorDetails(
+ new SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_REFRESH_REQUESTED)
+ .setRefreshBroadcastId("id")
+ .build()));
+ }
+
+ @Test
+ public void throwOnSafetySourceId_safetyCenterEnabled_doesntThrowForOtherIds() {
+ SafetyCenterManager safetyCenterManager =
+ getApplicationContext().getSystemService(SafetyCenterManager.class);
+ ShadowSafetyCenterManager shadowSafetyCenterManager =
+ Shadow.extract(getApplicationContext().getSystemService(SafetyCenterManager.class));
+
+ shadowSafetyCenterManager.throwOnSafetySourceId("unrelated_id");
+
+ shadowSafetyCenterManager.setSafetyCenterEnabled(true);
+ safetyCenterManager.setSafetySourceData(
+ "id",
+ new SafetySourceData.Builder().build(),
+ new SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_REFRESH_REQUESTED)
+ .setRefreshBroadcastId("id")
+ .build());
+ SafetySourceData unused = safetyCenterManager.getSafetySourceData("id");
+ safetyCenterManager.reportSafetySourceError(
+ "id",
+ new SafetySourceErrorDetails(
+ new SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_REFRESH_REQUESTED)
+ .setRefreshBroadcastId("id")
+ .build()));
+ }
+
+ @Test
+ public void throwOnSafetySourceId_safetyCenterEnabled_throwsForGivenIds() {
+ SafetyCenterManager safetyCenterManager =
+ getApplicationContext().getSystemService(SafetyCenterManager.class);
+ ShadowSafetyCenterManager shadowSafetyCenterManager =
+ Shadow.extract(getApplicationContext().getSystemService(SafetyCenterManager.class));
+
+ shadowSafetyCenterManager.throwOnSafetySourceId("id1");
+ shadowSafetyCenterManager.throwOnSafetySourceId("id2");
+
+ shadowSafetyCenterManager.setSafetyCenterEnabled(true);
+ assertThrowsIllegalArgumentExceptionForSource(
+ "id1",
+ new ThrowingRunnable() {
+ @Override
+ public void run() {
+ safetyCenterManager.setSafetySourceData(
+ "id1",
+ new SafetySourceData.Builder().build(),
+ new SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_REFRESH_REQUESTED)
+ .setRefreshBroadcastId("id")
+ .build());
+ }
+ });
+ assertThrowsIllegalArgumentExceptionForSource(
+ "id2",
+ new ThrowingRunnable() {
+ @Override
+ public void run() {
+ SafetySourceData unused = safetyCenterManager.getSafetySourceData("id2");
+ }
+ });
+ assertThrowsIllegalArgumentExceptionForSource(
+ "id1",
+ new ThrowingRunnable() {
+ @Override
+ public void run() {
+ safetyCenterManager.reportSafetySourceError(
+ "id1",
+ new SafetySourceErrorDetails(
+ new SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_REFRESH_REQUESTED)
+ .setRefreshBroadcastId("id")
+ .build()));
+ }
+ });
+ }
+
+ private static void assertThrowsIllegalArgumentExceptionForSource(
+ String safetySourceId, ThrowingRunnable runnable) {
+ IllegalArgumentException e = assertThrows(IllegalArgumentException.class, runnable);
+ assertThat(e).hasMessageThat().contains(safetySourceId);
+ }
}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowScrollViewTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowScrollViewTest.java
index 536e6dc21..6c5c6ac2d 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowScrollViewTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowScrollViewTest.java
@@ -16,21 +16,32 @@ import org.robolectric.Robolectric;
public class ShadowScrollViewTest {
@Test
public void shouldSmoothScrollTo() {
- ScrollView scrollView = new ScrollView(ApplicationProvider.getApplicationContext());
- scrollView.smoothScrollTo(7, 6);
+ // This test depends on broken scrolling behavior.
+ System.setProperty("robolectric.useRealScrolling", "false");
+ try {
+ ScrollView scrollView = new ScrollView(ApplicationProvider.getApplicationContext());
+ scrollView.smoothScrollTo(7, 6);
- assertEquals(7, scrollView.getScrollX());
- assertEquals(6, scrollView.getScrollY());
+ assertEquals(7, scrollView.getScrollX());
+ assertEquals(6, scrollView.getScrollY());
+ } finally {
+ System.clearProperty("robolectric.useRealScrolling");
+ }
}
@Test
public void shouldSmoothScrollBy() {
- ScrollView scrollView = new ScrollView(ApplicationProvider.getApplicationContext());
- scrollView.smoothScrollTo(7, 6);
- scrollView.smoothScrollBy(10, 20);
-
- assertEquals(17, scrollView.getScrollX());
- assertEquals(26, scrollView.getScrollY());
+ // This test depends on broken scrolling behavior.
+ System.setProperty("robolectric.useRealScrolling", "false");
+ try {
+ ScrollView scrollView = new ScrollView(ApplicationProvider.getApplicationContext());
+ scrollView.smoothScrollTo(7, 6);
+ scrollView.smoothScrollBy(10, 20);
+ assertEquals(17, scrollView.getScrollX());
+ assertEquals(26, scrollView.getScrollY());
+ } finally {
+ System.clearProperty("robolectric.useRealScrolling");
+ }
}
@Test
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowSensorManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowSensorManagerTest.java
index 784bbcd3f..a1c949aa6 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowSensorManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowSensorManagerTest.java
@@ -260,7 +260,6 @@ public class ShadowSensorManagerTest {
}
@Test
- @Config(minSdk = Build.VERSION_CODES.KITKAT)
public void flush_shouldCallOnFlushCompleted() {
Sensor accelSensor = ShadowSensor.newInstance(TYPE_ACCELEROMETER);
Sensor gyroSensor = ShadowSensor.newInstance(TYPE_GYROSCOPE);
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowServiceManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowServiceManagerTest.java
index 68778c584..8c172775c 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowServiceManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowServiceManagerTest.java
@@ -19,11 +19,6 @@ import org.robolectric.annotation.Config;
/** Tests for {@link ShadowServiceManager}. */
@RunWith(AndroidJUnit4.class)
public final class ShadowServiceManagerTest {
-
- @Test
- public void getService_available_shouldReturnNonNull() {
- assertThat(ServiceManager.getService(Context.INPUT_METHOD_SERVICE)).isNotNull();
- }
@Test
@Config(sdk = VERSION_CODES.S)
@@ -32,12 +27,30 @@ public final class ShadowServiceManagerTest {
}
@Test
- public void getService_unavailableService_shouldReturnNull() {
+ public void getService_available_shouldReturnNonNull() {
+ assertThat(ServiceManager.getService(Context.INPUT_METHOD_SERVICE)).isNotNull();
+ }
+
+ @Test
+ public void getService_unavailable_shouldReturnNull() {
ShadowServiceManager.setServiceAvailability(Context.INPUT_METHOD_SERVICE, false);
+
assertThat(ServiceManager.getService(Context.INPUT_METHOD_SERVICE)).isNull();
}
@Test
+ public void checkService_available_shouldReturnNonNull() {
+ assertThat(ServiceManager.checkService(Context.INPUT_METHOD_SERVICE)).isNotNull();
+ }
+
+ @Test
+ public void checkService_unavailable_shouldReturnNull() {
+ ShadowServiceManager.setServiceAvailability(Context.INPUT_METHOD_SERVICE, false);
+
+ assertThat(ServiceManager.checkService(Context.INPUT_METHOD_SERVICE)).isNull();
+ }
+
+ @Test
public void getService_multipleThreads_binderRace() throws Exception {
ExecutorService e = Executors.newFixedThreadPool(4);
final AtomicReference<Exception> thrownException = new AtomicReference<>();
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowSettingsTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowSettingsTest.java
index 44da6104c..ae2737ccb 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowSettingsTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowSettingsTest.java
@@ -2,7 +2,6 @@ package org.robolectric.shadows;
import static android.location.LocationManager.GPS_PROVIDER;
import static android.location.LocationManager.NETWORK_PROVIDER;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.O;
@@ -62,7 +61,6 @@ public class ShadowSettingsTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void testGlobalGetInt() {
assertThat(Settings.Global.getInt(contentResolver, "property", 0)).isEqualTo(0);
assertThat(Settings.Global.getInt(contentResolver, "property", 2)).isEqualTo(2);
@@ -135,15 +133,13 @@ public class ShadowSettingsTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
- public void testSetAdbEnabled_sinceJBMR1_settingsGlobal_true() {
+ public void testSetAdbEnabled_settingsGlobal_true() {
ShadowSettings.setAdbEnabled(true);
assertThat(Global.getInt(context.getContentResolver(), Global.ADB_ENABLED, 0)).isEqualTo(1);
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
- public void testSetAdbEnabled_sinceJBMR1_settingsGlobal_false() {
+ public void testSetAdbEnabled_settingsGlobal_false() {
ShadowSettings.setAdbEnabled(false);
assertThat(Global.getInt(context.getContentResolver(), Global.ADB_ENABLED, 1)).isEqualTo(0);
}
@@ -163,16 +159,14 @@ public class ShadowSettingsTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
- public void testSetInstallNonMarketApps_sinceJBMR1_settingsGlobal_true() {
+ public void testSetInstallNonMarketApps_settingsGlobal_true() {
ShadowSettings.setInstallNonMarketApps(true);
assertThat(Global.getInt(context.getContentResolver(), Global.INSTALL_NON_MARKET_APPS, 0))
.isEqualTo(1);
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
- public void testSetInstallNonMarketApps_sinceJBMR1_settingsGlobal_false() {
+ public void testSetInstallNonMarketApps_settingsGlobal_false() {
ShadowSettings.setInstallNonMarketApps(false);
assertThat(Global.getInt(context.getContentResolver(), Global.INSTALL_NON_MARKET_APPS, 1))
.isEqualTo(0);
@@ -266,7 +260,6 @@ public class ShadowSettingsTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void testGlobalGetFloat() {
float durationScale =
Global.getFloat(
@@ -281,7 +274,6 @@ public class ShadowSettingsTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void differentContentResolver() {
Context context = ApplicationProvider.getApplicationContext();
ContentResolver contentResolver1 =
@@ -300,7 +292,6 @@ public class ShadowSettingsTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void global_animatorDurationScale() {
long startTime = SystemClock.uptimeMillis();
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowSmsManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowSmsManagerTest.java
index 9f9b521cc..29d5dfd60 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowSmsManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowSmsManagerTest.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1;
import static android.os.Build.VERSION_CODES.R;
@@ -26,7 +25,6 @@ import org.robolectric.shadows.ShadowSmsManager.TextSmsParams;
import org.robolectric.util.ReflectionHelpers;
@RunWith(AndroidJUnit4.class)
-@Config(minSdk = JELLY_BEAN_MR2)
public class ShadowSmsManagerTest {
private SmsManager smsManager = SmsManager.getDefault();
private final String scAddress = "serviceCenterAddress";
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowSoundPoolTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowSoundPoolTest.java
index 184fadf88..855b782fd 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowSoundPoolTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowSoundPoolTest.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.mock;
@@ -32,13 +31,6 @@ public class ShadowSoundPoolTest {
}
@Test
- @Config(maxSdk = JELLY_BEAN_MR2)
- public void shouldCreateSoundPool_JellyBean() {
- SoundPool soundPool = new SoundPool(1, AudioManager.STREAM_MUSIC, 0);
- assertThat(soundPool).isNotNull();
- }
-
- @Test
public void playedSoundsFromResourcesAreRecorded() {
SoundPool soundPool = createSoundPool();
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowStatFsTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowStatFsTest.java
index 6ae22e0e2..6f793e36a 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowStatFsTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowStatFsTest.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static com.google.common.truth.Truth.assertThat;
import android.os.StatFs;
@@ -8,7 +7,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.io.File;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.annotation.Config;
@RunWith(AndroidJUnit4.class)
public class ShadowStatFsTest {
@@ -77,7 +75,6 @@ public class ShadowStatFsTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR2)
public void withApi18_shouldRegisterStats() {
ShadowStatFs.registerStats("/tmp", 100, 20, 10);
StatFs statsFs = new StatFs("/tmp");
@@ -91,7 +88,6 @@ public class ShadowStatFsTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR2)
public void withApi18_shouldRegisterStatsWithFile() {
ShadowStatFs.registerStats(new File("/tmp"), 100, 20, 10);
StatFs statsFs = new StatFs(new File("/tmp").getAbsolutePath());
@@ -105,7 +101,6 @@ public class ShadowStatFsTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR2)
public void withApi18_shouldResetStateBetweenTests() {
StatFs statsFs = new StatFs("/tmp");
assertThat(statsFs.getBlockCountLong()).isEqualTo(0L);
@@ -134,7 +129,6 @@ public class ShadowStatFsTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR2)
public void withApi18_shouldRestat() {
ShadowStatFs.registerStats("/tmp", 100, 20, 10);
StatFs statsFs = new StatFs("/tmp");
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowStorageManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowStorageManagerTest.java
index ad410660e..cd7841598 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowStorageManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowStorageManagerTest.java
@@ -105,6 +105,24 @@ public class ShadowStorageManagerTest {
.isTrue();
}
+ @Test
+ @Config(minSdk = N)
+ public void getStorageVolumeFromAnUserContext() {
+ File file1 = new File(internalStorage);
+ shadowOf(storageManager).addStorageVolume(buildAndGetStorageVolume(file1, "internal"));
+ Context userContext = getApplication();
+
+ try {
+ userContext =
+ getApplication().createPackageContextAsUser("system", 0, UserHandle.of(0 /* userId */));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+
+ StorageManager anotherStorageManager = userContext.getSystemService(StorageManager.class);
+ assertThat(shadowOf(anotherStorageManager).getStorageVolume(file1)).isNotNull();
+ }
+
private StorageVolume buildAndGetStorageVolume(File file, String description) {
Parcel parcel = Parcel.obtain();
parcel.writeInt(0);
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowSubscriptionManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowSubscriptionManagerTest.java
index e3fd11c48..8ebde3424 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowSubscriptionManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowSubscriptionManagerTest.java
@@ -2,6 +2,7 @@ package org.robolectric.shadows;
import static android.content.Context.TELEPHONY_SUBSCRIPTION_SERVICE;
import static android.os.Build.VERSION_CODES.N;
+import static android.os.Build.VERSION_CODES.O_MR1;
import static android.os.Build.VERSION_CODES.P;
import static android.os.Build.VERSION_CODES.Q;
import static android.os.Build.VERSION_CODES.R;
@@ -286,6 +287,43 @@ public class ShadowSubscriptionManagerTest {
}
@Test
+ @Config(minSdk = O_MR1)
+ public void getAccessibleSubscriptionInfoList() {
+ SubscriptionInfo expectedSubscriptionInfo =
+ SubscriptionInfoBuilder.newBuilder().setId(123).setIsEmbedded(true).buildSubscriptionInfo();
+
+ // Default
+ assertThat(shadowOf(subscriptionManager).getAccessibleSubscriptionInfoList()).isEmpty();
+
+ // Null vararg
+ shadowOf(subscriptionManager).setAccessibleSubscriptionInfos();
+ assertThat(shadowOf(subscriptionManager).getAccessibleSubscriptionInfoList()).isEmpty();
+
+ // A specific subscription
+ shadowOf(subscriptionManager).setAccessibleSubscriptionInfos(expectedSubscriptionInfo);
+ assertThat(shadowOf(subscriptionManager).getAccessibleSubscriptionInfoList())
+ .containsExactly(expectedSubscriptionInfo);
+ }
+
+ @Test
+ @Config(minSdk = O_MR1)
+ public void setAccessibleSubscriptionInfoList_triggersSubscriptionsChanged() {
+ DummySubscriptionsChangedListener listener = new DummySubscriptionsChangedListener();
+ subscriptionManager.addOnSubscriptionsChangedListener(listener);
+ // Invoked upon registration, but that's not important for this test
+ int initialInvocationCount = listener.subscriptionChangedCount;
+
+ shadowOf(subscriptionManager)
+ .setAccessibleSubscriptionInfos(
+ SubscriptionInfoBuilder.newBuilder()
+ .setId(123)
+ .setIsEmbedded(true)
+ .buildSubscriptionInfo());
+
+ assertThat(listener.subscriptionChangedCount - initialInvocationCount).isEqualTo(1);
+ }
+
+ @Test
public void getAvailableSubscriptionInfoList() {
SubscriptionInfo expectedSubscriptionInfo =
SubscriptionInfoBuilder.newBuilder().setId(123).buildSubscriptionInfo();
@@ -305,6 +343,20 @@ public class ShadowSubscriptionManagerTest {
}
@Test
+ public void setAvailableSubscriptionInfoList_triggersSubscriptionsChanged() {
+ DummySubscriptionsChangedListener listener = new DummySubscriptionsChangedListener();
+ subscriptionManager.addOnSubscriptionsChangedListener(listener);
+ // Invoked upon registration, but that's not important for this test
+ int initialInvocationCount = listener.subscriptionChangedCount;
+
+ shadowOf(subscriptionManager)
+ .setAvailableSubscriptionInfos(
+ SubscriptionInfoBuilder.newBuilder().setId(123).buildSubscriptionInfo());
+
+ assertThat(listener.subscriptionChangedCount - initialInvocationCount).isEqualTo(1);
+ }
+
+ @Test
@Config(maxSdk = P)
public void getPhoneId_shouldReturnPhoneIdIfSet() {
ShadowSubscriptionManager.putPhoneId(123, 456);
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowSurfaceControlTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowSurfaceControlTest.java
index 6bf42f48a..8754395ed 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowSurfaceControlTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowSurfaceControlTest.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.P;
import static android.os.Build.VERSION_CODES.Q;
@@ -18,7 +17,6 @@ import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.ReflectionHelpers.ClassParameter;
/** Tests for {@link org.robolectric.shadows.ShadowSurfaceControl} */
-@Config(minSdk = JELLY_BEAN_MR2)
@RunWith(AndroidJUnit4.class)
public class ShadowSurfaceControlTest {
// The spurious CloseGuard warnings happens in Q, where the CloseGuard is always opened.
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowSystemHealthManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowSystemHealthManagerTest.java
new file mode 100644
index 000000000..6a95c9229
--- /dev/null
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowSystemHealthManagerTest.java
@@ -0,0 +1,75 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.N;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.Process;
+import android.os.health.HealthStats;
+import android.os.health.SystemHealthManager;
+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;
+import org.robolectric.shadow.api.Shadow;
+
+@RunWith(AndroidJUnit4.class)
+@Config(minSdk = N)
+public final class ShadowSystemHealthManagerTest {
+
+ private static final int MY_UID = Process.myUid();
+ private static final int OTHER_UID_1 = MY_UID + 1;
+ private static final int OTHER_UID_2 = MY_UID + 2;
+
+ private static final HealthStats MY_UID_HEALTH_STATS =
+ HealthStatsBuilder.newBuilder().setDataType("my_uid_stats").build();
+ private static final HealthStats OTHER_UID_1_HEALTH_STATS =
+ HealthStatsBuilder.newBuilder().setDataType("other_uid_1_stats").build();
+ private static final HealthStats OTHER_UID_2_HEALTH_STATS =
+ HealthStatsBuilder.newBuilder().setDataType("other_uid_2_stats").build();
+
+ private SystemHealthManager systemHealthManager;
+ private ShadowSystemHealthManager shadowSystemHealthManager;
+
+ @Before
+ public void setUp() {
+ systemHealthManager =
+ (SystemHealthManager)
+ ApplicationProvider.getApplicationContext()
+ .getSystemService(Context.SYSTEM_HEALTH_SERVICE);
+ shadowSystemHealthManager = Shadow.extract(systemHealthManager);
+
+ shadowSystemHealthManager.addHealthStats(MY_UID_HEALTH_STATS);
+ shadowSystemHealthManager.addHealthStatsForUid(OTHER_UID_1, OTHER_UID_1_HEALTH_STATS);
+ shadowSystemHealthManager.addHealthStatsForUid(OTHER_UID_2, OTHER_UID_2_HEALTH_STATS);
+ }
+
+ @Test
+ public void snapshotForMyUid_expectedResult() throws Exception {
+ HealthStats stats = systemHealthManager.takeMyUidSnapshot();
+
+ assertThat(stats).isEqualTo(MY_UID_HEALTH_STATS);
+ }
+
+ @Test
+ public void snapshotForOtherUids_expectedResult() throws Exception {
+ HealthStats stats1 = systemHealthManager.takeUidSnapshot(OTHER_UID_1);
+ HealthStats stats2 = systemHealthManager.takeUidSnapshot(OTHER_UID_2);
+
+ assertThat(stats1).isEqualTo(OTHER_UID_1_HEALTH_STATS);
+ assertThat(stats2).isEqualTo(OTHER_UID_2_HEALTH_STATS);
+ }
+
+ @Test
+ public void snapshotForAllUids_expectedResult() throws Exception {
+ int[] uids = {OTHER_UID_1, MY_UID, OTHER_UID_2};
+
+ HealthStats[] stats = systemHealthManager.takeUidSnapshots(uids);
+
+ assertThat(stats[0]).isEqualTo(OTHER_UID_1_HEALTH_STATS);
+ assertThat(stats[1]).isEqualTo(MY_UID_HEALTH_STATS);
+ assertThat(stats[2]).isEqualTo(OTHER_UID_2_HEALTH_STATS);
+ }
+}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowTelecomManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowTelecomManagerTest.java
index 826d36391..04e7b6e01 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowTelecomManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowTelecomManagerTest.java
@@ -7,6 +7,7 @@ import static android.os.Build.VERSION_CODES.N;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.Q;
import static android.os.Build.VERSION_CODES.R;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.eq;
@@ -96,6 +97,20 @@ public class ShadowTelecomManagerTest {
}
@Test
+ @Config(minSdk = UPSIDE_DOWN_CAKE)
+ public void registerWithTransactionalCapabilites_addsSelfManagedCapability() {
+ PhoneAccountHandle handle = createHandle("id");
+ PhoneAccount phoneAccount =
+ PhoneAccount.builder(handle, "main_account")
+ // Transactional, but not explicitly self-managed.
+ .setCapabilities(PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS)
+ .build();
+ telecomService.registerPhoneAccount(phoneAccount);
+
+ assertThat(telecomService.getSelfManagedPhoneAccounts()).contains(handle);
+ }
+
+ @Test
public void getPhoneAccount_noPermission_throwsSecurityException() {
shadowOf(telecomService).setReadPhoneStatePermission(false);
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyManagerTest.java
index 90c74d105..afabf1db0 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyManagerTest.java
@@ -1,9 +1,6 @@
package org.robolectric.shadows;
import static android.content.Context.TELEPHONY_SERVICE;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.N;
@@ -53,7 +50,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
-import android.os.Build.VERSION;
+import android.os.Build;
import android.os.PersistableBundle;
import android.telecom.PhoneAccountHandle;
import android.telephony.CellInfo;
@@ -98,11 +95,11 @@ import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.ReflectionHelpers.ClassParameter;
@RunWith(AndroidJUnit4.class)
-@Config(minSdk = JELLY_BEAN)
public class ShadowTelephonyManagerTest {
private TelephonyManager telephonyManager;
private ShadowTelephonyManager shadowTelephonyManager;
+ private TelephonyManager tmForSub5;
@Before
public void setUp() throws Exception {
@@ -110,6 +107,25 @@ public class ShadowTelephonyManagerTest {
shadowTelephonyManager = Shadow.extract(telephonyManager);
shadowOf((Application) ApplicationProvider.getApplicationContext())
.grantPermissions(permission.READ_PRIVILEGED_PHONE_STATE);
+ tmForSub5 = newTelephonyManager(5);
+ }
+
+ private TelephonyManager newTelephonyManager(Integer subId) {
+ Class<?>[] parameters;
+ Object[] arguments;
+ if (subId == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
+ parameters = new Class<?>[] {Context.class};
+ arguments = new Object[] {ApplicationProvider.getApplicationContext()};
+ } else {
+ parameters = new Class<?>[] {Context.class, int.class};
+ arguments = new Object[] {ApplicationProvider.getApplicationContext(), subId};
+ }
+ TelephonyManager newInstance =
+ Shadow.newInstance(TelephonyManager.class, parameters, arguments);
+ if (subId != null) {
+ shadowTelephonyManager.setTelephonyManagerForSubscriptionId(subId, newInstance);
+ }
+ return newInstance;
}
@Test
@@ -119,9 +135,7 @@ public class ShadowTelephonyManagerTest {
verify(listener).onCallStateChanged(CALL_STATE_IDLE, null);
verify(listener).onCellLocationChanged(null);
- if (VERSION.SDK_INT >= JELLY_BEAN_MR1) {
- verify(listener).onCellInfoChanged(Collections.emptyList());
- }
+ verify(listener).onCellInfoChanged(Collections.emptyList());
}
@Test
@@ -164,6 +178,15 @@ public class ShadowTelephonyManagerTest {
@Test
@Config(minSdk = M)
+ public void setDeviceId_withSlot_doesNotAffectCallingInstance() {
+ String testId = "TESTING123";
+ shadowOf(telephonyManager).setDeviceId(123, testId);
+ assertThat(telephonyManager.getDeviceId()).isNull();
+ assertThat(telephonyManager.getDeviceId(123)).isEqualTo(testId);
+ }
+
+ @Test
+ @Config(minSdk = M)
public void shouldGiveDeviceIdForSlot() {
shadowOf(telephonyManager).setDeviceId(1, "device in slot 1");
shadowOf(telephonyManager).setDeviceId(2, "device in slot 2");
@@ -199,6 +222,14 @@ public class ShadowTelephonyManagerTest {
@Test
@Config(minSdk = O)
+ public void setImei_withSlotId_acceptsNull() {
+ shadowOf(telephonyManager).setImei(0, "imei0");
+ shadowOf(telephonyManager).setImei(0, null);
+ assertEquals(null, telephonyManager.getImei(0));
+ }
+
+ @Test
+ @Config(minSdk = O)
public void getMeid() {
String testMeid = "4test meid";
shadowOf(telephonyManager).setMeid(testMeid);
@@ -213,6 +244,7 @@ public class ShadowTelephonyManagerTest {
shadowOf(telephonyManager).setMeid(1, "meid1");
assertEquals("meid0", telephonyManager.getMeid(0));
assertEquals("meid1", telephonyManager.getMeid(1));
+ assertEquals("defaultMeid", telephonyManager.getMeid());
}
@Test
@@ -263,7 +295,6 @@ public class ShadowTelephonyManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void shouldGiveAllCellInfo() {
PhoneStateListener listener = mock(PhoneStateListener.class);
telephonyManager.listen(listener, LISTEN_CELL_INFO);
@@ -347,7 +378,6 @@ public class ShadowTelephonyManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR2)
public void shouldGiveGroupIdLevel1() {
shadowOf(telephonyManager).setGroupIdLevel1("SomeGroupId");
assertEquals("SomeGroupId", telephonyManager.getGroupIdLevel1());
@@ -386,6 +416,14 @@ public class ShadowTelephonyManagerTest {
}
@Test
+ @Config(minSdk = M)
+ public void setCurrentPhoneType_fromPhoneId_canBeReadFromAnyInstance() {
+ shadowOf(telephonyManager).setCurrentPhoneType(123, TelephonyManager.PHONE_TYPE_CDMA);
+
+ assertEquals(TelephonyManager.PHONE_TYPE_CDMA, tmForSub5.getCurrentPhoneType(123));
+ }
+
+ @Test
public void shouldGiveCellLocation() {
PhoneStateListener listener = mock(PhoneStateListener.class);
telephonyManager.listen(listener, LISTEN_CELL_LOCATION);
@@ -462,6 +500,14 @@ public class ShadowTelephonyManagerTest {
}
@Test
+ @Config(minSdk = R)
+ public void setSmsCapable_modifiesAllInstances() {
+ shadowOf(telephonyManager).setIsSmsCapable(false);
+ newTelephonyManager(123);
+ assertThat(telephonyManager.createForSubscriptionId(123).isSmsCapable()).isFalse();
+ }
+
+ @Test
@Config(minSdk = O)
public void shouldGiveCarrierConfigIfSet() {
PersistableBundle bundle = new PersistableBundle();
@@ -543,6 +589,18 @@ public class ShadowTelephonyManagerTest {
@Test
@Config(minSdk = N)
+ public void setVoicemailVibrationEnabled_accessibleFromAllTelephonyManagers() {
+ PhoneAccountHandle phoneAccountHandle =
+ new PhoneAccountHandle(
+ new ComponentName(ApplicationProvider.getApplicationContext(), Object.class), "handle");
+
+ shadowOf(telephonyManager).setVoicemailVibrationEnabled(phoneAccountHandle, true);
+
+ assertTrue(telephonyManager.isVoicemailVibrationEnabled(phoneAccountHandle));
+ }
+
+ @Test
+ @Config(minSdk = N)
public void shouldGiveVoicemailRingtoneUri() {
PhoneAccountHandle phoneAccountHandle =
new PhoneAccountHandle(
@@ -569,6 +627,19 @@ public class ShadowTelephonyManagerTest {
}
@Test
+ @Config(minSdk = N)
+ public void setVoicemailRingtoneUri_accessibleFromAllTelephonyManagers() {
+ PhoneAccountHandle phoneAccountHandle =
+ new PhoneAccountHandle(
+ new ComponentName(ApplicationProvider.getApplicationContext(), Object.class), "handle");
+ Uri ringtoneUri = Uri.fromParts("file", "ringtone.mp3", /* fragment= */ null);
+
+ shadowOf(telephonyManager).setVoicemailRingtoneUri(phoneAccountHandle, ringtoneUri);
+
+ assertEquals(ringtoneUri, telephonyManager.getVoicemailRingtoneUri(phoneAccountHandle));
+ }
+
+ @Test
@Config(minSdk = O)
public void shouldCreateForPhoneAccountHandle() {
PhoneAccountHandle phoneAccountHandle =
@@ -584,6 +655,20 @@ public class ShadowTelephonyManagerTest {
}
@Test
+ @Config(minSdk = O)
+ public void shouldCreateForPhoneAccountHandle_fromAllInstances() {
+ PhoneAccountHandle phoneAccountHandle =
+ new PhoneAccountHandle(
+ new ComponentName(ApplicationProvider.getApplicationContext(), Object.class), "handle");
+ TelephonyManager mockTelephonyManager = mock(TelephonyManager.class);
+
+ shadowOf(telephonyManager)
+ .setTelephonyManagerForHandle(phoneAccountHandle, mockTelephonyManager);
+
+ assertEquals(mockTelephonyManager, tmForSub5.createForPhoneAccountHandle(phoneAccountHandle));
+ }
+
+ @Test
@Config(minSdk = N)
public void shouldCreateForSubscriptionId() {
int subscriptionId = 42;
@@ -596,6 +681,18 @@ public class ShadowTelephonyManagerTest {
}
@Test
+ @Config(minSdk = N)
+ public void shouldCreateForSubscriptionId_fromAllInstances() {
+ int subscriptionId = 42;
+ TelephonyManager mockTelephonyManager = mock(TelephonyManager.class);
+
+ shadowOf(telephonyManager)
+ .setTelephonyManagerForSubscriptionId(subscriptionId, mockTelephonyManager);
+
+ assertEquals(mockTelephonyManager, tmForSub5.createForSubscriptionId(subscriptionId));
+ }
+
+ @Test
@Config(minSdk = O)
public void shouldSetServiceState() {
PhoneStateListener listener = mock(PhoneStateListener.class);
@@ -686,6 +783,13 @@ public class ShadowTelephonyManagerTest {
@Test
@Config(minSdk = O)
+ public void getSimState_defaultForZeroSpecial() {
+ assertThat(telephonyManager.getSimState(1)).isEqualTo(TelephonyManager.SIM_STATE_UNKNOWN);
+ assertThat(telephonyManager.getSimState(0)).isEqualTo(TelephonyManager.SIM_STATE_READY);
+ }
+
+ @Test
+ @Config(minSdk = O)
public void shouldGetSimStateUsingSlotNumber() {
int expectedSimState = TelephonyManager.SIM_STATE_ABSENT;
int slotNumber = 3;
@@ -695,22 +799,62 @@ public class ShadowTelephonyManagerTest {
}
@Test
+ @Config(minSdk = O)
+ public void setSimState_withSlotParameter_doesNotAffectCaller() {
+ int expectedSimState = TelephonyManager.SIM_STATE_ABSENT;
+ int slotNumber = 3;
+ shadowOf(telephonyManager).setSimState(slotNumber, expectedSimState);
+
+ assertThat(telephonyManager.getSimState()).isEqualTo(TelephonyManager.SIM_STATE_READY);
+ }
+
+ @Test
public void shouldGetSimIso() {
assertThat(telephonyManager.getSimCountryIso()).isEmpty();
}
@Test
@Config(minSdk = N, maxSdk = Q)
+ public void shouldGetSimIso_resetsZeroSpecial() {
+ assertThat(callGetSimCountryIso(telephonyManager, 1)).isNull();
+ assertThat(callGetSimCountryIso(telephonyManager, 0)).isEmpty();
+ }
+
+ private String callGetSimCountryIso(TelephonyManager telephonyManager, int subId) {
+ return (String)
+ ReflectionHelpers.callInstanceMethod(
+ telephonyManager, "getSimCountryIso", ClassParameter.from(int.class, subId));
+ }
+
+ @Test
+ @Config(minSdk = N, maxSdk = Q)
public void shouldGetSimIsoWhenSetUsingSlotNumber() {
String expectedSimIso = "usa";
int subId = 2;
shadowOf(telephonyManager).setSimCountryIso(subId, expectedSimIso);
- assertThat(
- (String)
- ReflectionHelpers.callInstanceMethod(
- telephonyManager, "getSimCountryIso", ClassParameter.from(int.class, subId)))
- .isEqualTo(expectedSimIso);
+ assertThat(callGetSimCountryIso(telephonyManager, subId)).isEqualTo(expectedSimIso);
+ }
+
+ @Test
+ @Config(minSdk = N, maxSdk = Q)
+ public void setSimIso_withSlotParameter_doesNotAffectCaller() {
+ String expectedSimIso = "usa";
+ int subId = 2;
+ shadowOf(telephonyManager).setSimCountryIso(subId, expectedSimIso);
+
+ assertThat(telephonyManager.getSimCountryIso()).isEqualTo("");
+ }
+
+ @Test
+ @Config(minSdk = N, maxSdk = Q)
+ public void setSimIso_withSlotParameter_acceptsNull() {
+ String expectedSimIso = "usa";
+ int subId = 2;
+ shadowOf(telephonyManager).setSimCountryIso(subId, expectedSimIso);
+ shadowOf(telephonyManager).setSimCountryIso(subId, null);
+
+ assertThat(callGetSimCountryIso(telephonyManager, subId)).isEqualTo(null);
}
@Test
@@ -743,6 +887,24 @@ public class ShadowTelephonyManagerTest {
@Test
@Config(minSdk = M)
+ public void shouldGetCurrentPhoneTypeGivenSubId_fromAllInstances() {
+ int subId = 1;
+ int expectedPhoneType = TelephonyManager.PHONE_TYPE_GSM;
+ shadowOf(telephonyManager).setCurrentPhoneType(subId, expectedPhoneType);
+
+ assertThat(tmForSub5.getCurrentPhoneType(subId)).isEqualTo(expectedPhoneType);
+ }
+
+ @Test
+ @Config(minSdk = M)
+ public void clearPhoneTypes_setsStartingState() {
+ ShadowTelephonyManager.clearPhoneTypes();
+ assertEquals(TelephonyManager.PHONE_TYPE_NONE, telephonyManager.getCurrentPhoneType(0));
+ assertEquals(TelephonyManager.PHONE_TYPE_NONE, telephonyManager.getCurrentPhoneType(1));
+ }
+
+ @Test
+ @Config(minSdk = M)
public void shouldGetCarrierPackageNamesForIntentAndPhone() {
List<String> packages = Collections.singletonList("package1");
int phoneId = 123;
@@ -754,6 +916,39 @@ public class ShadowTelephonyManagerTest {
@Test
@Config(minSdk = M)
+ public void setCarrierPackageNamesForPhone_acceptsNull() {
+ List<String> packages = Collections.singletonList("package1");
+ int phoneId = 123;
+ shadowOf(telephonyManager).setCarrierPackageNamesForPhone(phoneId, packages);
+ shadowOf(telephonyManager).setCarrierPackageNamesForPhone(phoneId, null);
+
+ assertThat(telephonyManager.getCarrierPackageNamesForIntentAndPhone(new Intent(), phoneId))
+ .isEqualTo(null);
+ }
+
+ @Test
+ @Config(minSdk = M)
+ public void shouldGetCarrierPackageNamesForIntentAndPhone_fromAllInstances() {
+ List<String> packages = Collections.singletonList("package1");
+ int phoneId = 123;
+ shadowOf(telephonyManager).setCarrierPackageNamesForPhone(phoneId, packages);
+
+ assertThat(tmForSub5.getCarrierPackageNamesForIntentAndPhone(new Intent(), phoneId))
+ .isEqualTo(packages);
+ }
+
+ @Test
+ @Config(minSdk = M)
+ public void shouldGetCarrierPackageNamesForIntentAndPhone_doesNotAffectCaller() {
+ List<String> packages = Collections.singletonList("package1");
+ int phoneId = 123;
+ shadowOf(telephonyManager).setCarrierPackageNamesForPhone(phoneId, packages);
+
+ assertThat(telephonyManager.getCarrierPackageNamesForIntent(new Intent())).isNull();
+ }
+
+ @Test
+ @Config(minSdk = M)
public void shouldGetCarrierPackageNamesForIntent() {
List<String> packages = Collections.singletonList("package1");
shadowOf(telephonyManager)
@@ -763,10 +958,13 @@ public class ShadowTelephonyManagerTest {
}
@Test
+ @Config(minSdk = O)
public void resetSimStates_shouldRetainDefaultState() {
shadowOf(telephonyManager).resetSimStates();
assertThat(telephonyManager.getSimState()).isEqualTo(TelephonyManager.SIM_STATE_READY);
+ assertThat(telephonyManager.getSimState(1)).isEqualTo(TelephonyManager.SIM_STATE_UNKNOWN);
+ assertThat(telephonyManager.getSimState(0)).isEqualTo(TelephonyManager.SIM_STATE_READY);
}
@Test
@@ -778,6 +976,14 @@ public class ShadowTelephonyManagerTest {
}
@Test
+ @Config(minSdk = N, maxSdk = Q)
+ public void resetSimCountryIsos_resetZeroSpecial() {
+ shadowOf(telephonyManager).resetSimCountryIsos();
+ assertThat(callGetSimCountryIso(telephonyManager, 1)).isNull();
+ assertThat(callGetSimCountryIso(telephonyManager, 0)).isEmpty();
+ }
+
+ @Test
public void shouldSetSubscriberId() {
String subscriberId = "123451234512345";
shadowOf(telephonyManager).setSubscriberId(subscriberId);
@@ -803,6 +1009,23 @@ public class ShadowTelephonyManagerTest {
}
@Test
+ @Config(minSdk = Q)
+ public void getUiccCardsInfo() {
+ Object /*UiccCardInfo*/ cardsInfo1 = "new UiccCardInfo(true, true, null, 0, 0, true)";
+ Object /*UiccCardInfo*/ cardsInfo2 = "new UiccCardInfo(true, true, null, 0, 1, true)";
+ List<Object /*UiccCardInfo*/> cardInfos = ImmutableList.of(cardsInfo1, cardsInfo2);
+ shadowOf(telephonyManager).setUiccCardsInfo(cardInfos);
+
+ assertThat(telephonyManager.getUiccCardsInfo()).isEqualTo(cardInfos);
+ }
+
+ @Test
+ @Config(minSdk = Q)
+ public void getUiccCardsInfo_returnsListAfterReset() {
+ assertThat(telephonyManager.getUiccCardsInfo()).isEmpty();
+ }
+
+ @Test
@Config(minSdk = O)
public void shouldSetVisualVoicemailPackage() {
shadowOf(telephonyManager).setVisualVoicemailPackageName("org.foo");
@@ -1227,4 +1450,39 @@ public class ShadowTelephonyManagerTest {
.setPhoneAccountHandleSubscriptionId(phoneAccountHandle, subscriptionId);
assertEquals(subscriptionId, telephonyManager.getSubscriptionId(phoneAccountHandle));
}
+
+ @Test
+ @Config(minSdk = R)
+ public void getSubscriptionIdForPhoneAccountHandle_affectsAllInstances() {
+ int subscriptionId = 123;
+ PhoneAccountHandle phoneAccountHandle =
+ new PhoneAccountHandle(
+ new ComponentName(ApplicationProvider.getApplicationContext(), Object.class), "handle");
+ shadowOf(telephonyManager)
+ .setPhoneAccountHandleSubscriptionId(phoneAccountHandle, subscriptionId);
+ assertEquals(subscriptionId, tmForSub5.getSubscriptionId(phoneAccountHandle));
+ }
+
+ @Test
+ @Config(minSdk = R)
+ public void getSubscriptionIdForPhoneAccountHandle_doesNotModifyCaller() {
+ int subscriptionId = 123;
+ PhoneAccountHandle phoneAccountHandle =
+ new PhoneAccountHandle(
+ new ComponentName(ApplicationProvider.getApplicationContext(), Object.class), "handle");
+ shadowOf(telephonyManager)
+ .setPhoneAccountHandleSubscriptionId(phoneAccountHandle, subscriptionId);
+ assertEquals(5, tmForSub5.getSubscriptionId());
+ }
+
+ @Test
+ @Config(minSdk = R)
+ public void newInstance_alreadyKnowsSubId() {
+ Context context = ApplicationProvider.getApplicationContext();
+ Class<?>[] parameters = new Class<?>[] {Context.class, int.class};
+ Object[] arguments = new Object[] {context, 123};
+ TelephonyManager tm = Shadow.newInstance(TelephonyManager.class, parameters, arguments);
+
+ assertThat(tm.getSubscriptionId()).isEqualTo(123);
+ }
}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyTest.java
index 7846f0307..d0ed68329 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyTest.java
@@ -3,19 +3,16 @@ package org.robolectric.shadows;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
-import android.os.Build.VERSION_CODES;
import android.provider.Telephony.Sms;
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;
import org.robolectric.shadows.ShadowTelephony.ShadowSms;
/** Unit tests for {@link ShadowTelephony}. */
@RunWith(AndroidJUnit4.class)
-@Config(minSdk = VERSION_CODES.KITKAT)
public class ShadowTelephonyTest {
private static final String TEST_PACKAGE_NAME = "test.package.name";
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowTimeTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowTimeTest.java
index 809437af7..239d1c665 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowTimeTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowTimeTest.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.KITKAT_WATCH;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
@@ -13,7 +12,6 @@ import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
@RunWith(AndroidJUnit4.class)
-@Config(minSdk = JELLY_BEAN_MR2)
public class ShadowTimeTest {
@Test
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowTraceTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowTraceTest.java
index 07b57f9cb..a0d5b35de 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowTraceTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowTraceTest.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.Q;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
@@ -21,7 +20,6 @@ import org.robolectric.shadows.ShadowTrace.Counter;
/** Test for {@link ShadowTrace}. */
@RunWith(AndroidJUnit4.class)
-@Config(minSdk = JELLY_BEAN_MR2)
public class ShadowTraceTest {
private static final String VERY_LONG_TAG_NAME = String.format(String.format("%%%ds", 128), "A");
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowTransportControlsTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowTransportControlsTest.java
new file mode 100644
index 000000000..243428b7f
--- /dev/null
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowTransportControlsTest.java
@@ -0,0 +1,163 @@
+package org.robolectric.shadows;
+
+import static android.media.session.PlaybackState.ACTION_PAUSE;
+import static android.media.session.PlaybackState.ACTION_PLAY;
+import static android.media.session.PlaybackState.ACTION_PLAY_FROM_SEARCH;
+import static android.media.session.PlaybackState.ACTION_PLAY_FROM_URI;
+import static android.media.session.PlaybackState.ACTION_PREPARE_FROM_SEARCH;
+import static android.media.session.PlaybackState.ACTION_PREPARE_FROM_URI;
+import static android.media.session.PlaybackState.ACTION_SEEK_TO;
+import static android.media.session.PlaybackState.ACTION_SET_RATING;
+import static android.media.session.PlaybackState.ACTION_SKIP_TO_NEXT;
+import static android.media.session.PlaybackState.ACTION_SKIP_TO_PREVIOUS;
+import static android.media.session.PlaybackState.ACTION_SKIP_TO_QUEUE_ITEM;
+import static android.media.session.PlaybackState.ACTION_STOP;
+import static android.os.Build.VERSION_CODES.LOLLIPOP;
+import static android.os.Build.VERSION_CODES.M;
+import static android.os.Build.VERSION_CODES.N;
+import static com.google.common.truth.Truth.assertThat;
+import static org.robolectric.shadow.api.Shadow.extract;
+
+import android.media.Rating;
+import android.media.session.MediaController.TransportControls;
+import android.media.session.MediaSession;
+import android.net.Uri;
+import android.os.Bundle;
+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;
+
+/** Tests for {@link ShadowTransportControls}. */
+@RunWith(AndroidJUnit4.class)
+public class ShadowTransportControlsTest {
+ TransportControls transportControls;
+ private ShadowTransportControls shadowTransportControls;
+
+ @Before
+ public void setup() {
+ MediaSession mediaSession =
+ new MediaSession(ApplicationProvider.getApplicationContext(), "TestMediaSession");
+ transportControls = mediaSession.getController().getTransportControls();
+ shadowTransportControls = extract(transportControls);
+ }
+
+ @Test
+ @Config(minSdk = LOLLIPOP)
+ public void testPause_lastPerformedActionIsPause() {
+ transportControls.pause();
+
+ assertThat(shadowTransportControls.getLastPerformedAction()).isEqualTo(ACTION_PAUSE);
+ }
+
+ @Test
+ @Config(minSdk = LOLLIPOP)
+ public void testPlay_lastPerformedActionIsPlay() {
+ transportControls.play();
+
+ assertThat(shadowTransportControls.getLastPerformedAction()).isEqualTo(ACTION_PLAY);
+ }
+
+ @Test
+ @Config(minSdk = LOLLIPOP)
+ public void testPlayFromSearch_lastPerformedActionIsPlayFromSearch() {
+ transportControls.playFromSearch("query", new Bundle());
+
+ assertThat(shadowTransportControls.getLastPerformedAction()).isEqualTo(ACTION_PLAY_FROM_SEARCH);
+ }
+
+ @Test
+ @Config(minSdk = M)
+ public void testPlayFromUri_lastPerformedActionIsPlayFromUri() {
+ Uri uri = Uri.parse("test://address");
+ transportControls.playFromUri(uri, new Bundle());
+
+ assertThat(shadowTransportControls.getLastPerformedAction()).isEqualTo(ACTION_PLAY_FROM_URI);
+ assertThat(shadowTransportControls.getUri()).isEqualTo(uri);
+ }
+
+ @Test
+ @Config(minSdk = N)
+ public void testPrepareFromSearch_lastPerformedActionIsPrepareFromSearch() {
+ transportControls.prepareFromSearch("query", new Bundle());
+
+ assertThat(shadowTransportControls.getLastPerformedAction())
+ .isEqualTo(ACTION_PREPARE_FROM_SEARCH);
+ }
+
+ @Test
+ @Config(minSdk = N)
+ public void testPrepareFromUri_lastPerformedActionIsPrepareFromUri() {
+ Uri uri = Uri.parse("test://address");
+ transportControls.prepareFromUri(uri, new Bundle());
+
+ assertThat(shadowTransportControls.getLastPerformedAction()).isEqualTo(ACTION_PREPARE_FROM_URI);
+ assertThat(shadowTransportControls.getUri()).isEqualTo(uri);
+ }
+
+ @Test
+ @Config(minSdk = LOLLIPOP)
+ public void testSeekTo_lastPerformedActionIsSeekTo() {
+ transportControls.seekTo(50);
+
+ assertThat(shadowTransportControls.getLastPerformedAction()).isEqualTo(ACTION_SEEK_TO);
+ assertThat(shadowTransportControls.getSeekToPositionMs()).isEqualTo(50);
+ }
+
+ @Test
+ @Config(minSdk = LOLLIPOP)
+ public void testSendCustomAction_customActionAndArgsAreRecorded() {
+ Bundle customActionArgs = new Bundle();
+ customActionArgs.putInt("test", 5);
+ transportControls.sendCustomAction("action", customActionArgs);
+
+ assertThat(shadowTransportControls.getCustomAction()).isEqualTo("action");
+ assertThat(shadowTransportControls.getCustomActionArgs()).isEqualTo(customActionArgs);
+ }
+
+ @Test
+ @Config(minSdk = LOLLIPOP)
+ public void testSetRating_lastPerformedActionIsSetRating() {
+ Rating rating = Rating.newPercentageRating(30F);
+ transportControls.setRating(rating);
+
+ assertThat(shadowTransportControls.getLastPerformedAction()).isEqualTo(ACTION_SET_RATING);
+ assertThat(shadowTransportControls.getRating()).isEqualTo(rating);
+ }
+
+ @Test
+ @Config(minSdk = LOLLIPOP)
+ public void testSkipToNext_lastPerformedActionIsSkipToNext() {
+ transportControls.skipToNext();
+
+ assertThat(shadowTransportControls.getLastPerformedAction()).isEqualTo(ACTION_SKIP_TO_NEXT);
+ }
+
+ @Test
+ @Config(minSdk = LOLLIPOP)
+ public void testSkipToPrevious_lastPerformedActionIsSkipToPrevious() {
+ transportControls.skipToPrevious();
+
+ assertThat(shadowTransportControls.getLastPerformedAction()).isEqualTo(ACTION_SKIP_TO_PREVIOUS);
+ }
+
+ @Test
+ @Config(minSdk = LOLLIPOP)
+ public void testSkipToPrevious_lastPerformedActionIsSkipToQueueItem() {
+ transportControls.skipToQueueItem(5);
+
+ assertThat(shadowTransportControls.getLastPerformedAction())
+ .isEqualTo(ACTION_SKIP_TO_QUEUE_ITEM);
+ assertThat(shadowTransportControls.getQueueItemId()).isEqualTo(5);
+ }
+
+ @Test
+ @Config(minSdk = LOLLIPOP)
+ public void testStop_lastPerformedActionIsStop() {
+ transportControls.stop();
+
+ assertThat(shadowTransportControls.getLastPerformedAction()).isEqualTo(ACTION_STOP);
+ }
+}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowUIModeManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowUIModeManagerTest.java
index 0fb9bc605..72c5e52e7 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowUIModeManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowUIModeManagerTest.java
@@ -19,6 +19,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;
@@ -275,6 +276,32 @@ public class ShadowUIModeManagerTest {
.isEqualTo(UiModeManager.MODE_NIGHT_CUSTOM_TYPE_SCHEDULE);
}
+ @Test
+ @Config(minSdk = S)
+ public void getProjectingPackages_noProjectingPackages_returnsEmpty() {
+ assertThat(uiModeManager.getProjectingPackages(UiModeManager.PROJECTION_TYPE_ALL)).isEmpty();
+ }
+
+ @Test
+ @Config(minSdk = S)
+ public void getProjectingPackages_projecting_returnsNotEmpty() {
+ setPermissions(android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION);
+ uiModeManager.requestProjection(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE);
+
+ assertThat(uiModeManager.getProjectingPackages(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE))
+ .contains(RuntimeEnvironment.getApplication().getPackageName());
+ }
+
+ @Test
+ @Config(minSdk = S)
+ public void getProjectingPackages_projecting_allTypes_returnsNotEmpty() {
+ setPermissions(android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION);
+ uiModeManager.requestProjection(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE);
+
+ assertThat(uiModeManager.getProjectingPackages(UiModeManager.PROJECTION_TYPE_ALL))
+ .contains(RuntimeEnvironment.getApplication().getPackageName());
+ }
+
private void setPermissions(String... permissions) {
PackageInfo pi = new PackageInfo();
pi.packageName = context.getPackageName();
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowUiAutomationTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowUiAutomationTest.java
index 79a9c68a9..4a3d5d527 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.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.KITKAT;
import static com.google.common.truth.Truth.assertThat;
@@ -19,7 +18,6 @@ import org.robolectric.annotation.Config;
import org.robolectric.annotation.LooperMode;
/** Test for {@link ShadowUiAutomation}. */
-@Config(minSdk = JELLY_BEAN_MR2)
@RunWith(AndroidJUnit4.class)
public class ShadowUiAutomationTest {
@Config(sdk = KITKAT)
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowUserManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowUserManagerTest.java
index 2e81f2203..9da42710a 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowUserManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowUserManagerTest.java
@@ -1,7 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
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;
@@ -95,7 +93,6 @@ public class ShadowUserManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR2)
public void testGetApplicationRestrictions() {
String packageName = context.getPackageName();
assertThat(userManager.getApplicationRestrictions(packageName).size()).isEqualTo(0);
@@ -132,7 +129,6 @@ public class ShadowUserManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR2)
public void setUserRestriction_forGivenUserHandle_setsTheRestriction() {
assertThat(userManager.hasUserRestriction(UserManager.ENSURE_VERIFY_APPS)).isFalse();
@@ -143,7 +139,6 @@ public class ShadowUserManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR2)
public void setUserRestriction_forCurrentUser_setsTheRestriction() {
assertThat(userManager.hasUserRestriction(UserManager.ENSURE_VERIFY_APPS)).isFalse();
@@ -153,7 +148,6 @@ public class ShadowUserManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR2)
public void getUserRestrictions() {
assertThat(userManager.getUserRestrictions().size()).isEqualTo(0);
@@ -175,7 +169,6 @@ public class ShadowUserManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR2)
public void clearUserRestrictions() {
assertThat(userManager.getUserRestrictions().size()).isEqualTo(0);
shadowOf(userManager)
@@ -282,7 +275,6 @@ public class ShadowUserManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void shouldGetSerialNumberForUser() {
long serialNumberInvalid = -1L;
@@ -299,7 +291,6 @@ public class ShadowUserManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void getUserForNonExistSerialNumber() {
long nonExistSerialNumber = 121;
assertThat(userManager.getUserForSerialNumber(nonExistSerialNumber)).isNull();
@@ -307,7 +298,6 @@ public class ShadowUserManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void shouldGetSerialNumberForProfile() {
long serialNumberInvalid = -1L;
@@ -317,7 +307,6 @@ public class ShadowUserManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void shouldGetUserHandleFromSerialNumberForProfile() {
long serialNumberInvalid = -1L;
@@ -328,7 +317,6 @@ public class ShadowUserManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void getSerialNumberForUser_returnsSetSerialNumberForUser() {
UserHandle userHandle = newUserHandle(0);
shadowOf(userManager).setSerialNumberForUser(userHandle, 123L);
@@ -336,7 +324,6 @@ public class ShadowUserManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void getUserHandle() {
UserHandle expectedUserHandle = shadowOf(userManager).addUser(10, "secondary_user", 0);
@@ -396,7 +383,6 @@ public class ShadowUserManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR2)
public void isLinkedUser() {
assertThat(userManager.isLinkedUser()).isFalse();
@@ -478,7 +464,6 @@ public class ShadowUserManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void isUserRunning() {
UserHandle userHandle = newUserHandle(0);
@@ -504,7 +489,6 @@ public class ShadowUserManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void isUserRunningOrStopping() {
UserHandle userHandle = newUserHandle(0);
@@ -582,7 +566,6 @@ public class ShadowUserManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void addSecondaryUser() {
assertThat(userManager.getUserCount()).isEqualTo(1);
UserHandle userHandle = shadowOf(userManager).addUser(10, "secondary_user", 0);
@@ -591,7 +574,6 @@ public class ShadowUserManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void removeSecondaryUser() {
shadowOf(userManager).addUser(10, "secondary_user", 0);
assertThat(shadowOf(userManager).removeUser(10)).isTrue();
@@ -635,7 +617,6 @@ public class ShadowUserManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void switchToSecondaryUser() {
shadowOf(userManager).addUser(10, "secondary_user", 0);
shadowOf(userManager).switchUser(10);
@@ -677,7 +658,6 @@ public class ShadowUserManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void getUsers() {
assertThat(userManager.getUsers()).hasSize(1);
shadowOf(userManager).addUser(10, "secondary_user", 0);
@@ -687,7 +667,6 @@ public class ShadowUserManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void getUserInfo() {
shadowOf(userManager).addUser(10, "secondary_user", 0);
assertThat(userManager.getUserInfo(10)).isNotNull();
@@ -695,7 +674,6 @@ public class ShadowUserManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void getUserInfoOfProfile() {
shadowOf(userManager).addProfile(10, 11, "profile_user", 0);
shadowOf(userManager).addProfile(10, 12, "profile_user_2", 0);
@@ -897,7 +875,6 @@ public class ShadowUserManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void getMaxSupportedUsers() {
assertThat(UserManager.getMaxSupportedUsers()).isEqualTo(1);
shadowOf(userManager).setMaxSupportedUsers(5);
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowUwbManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowUwbManagerTest.java
index ecc5237db..bbea33710 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowUwbManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowUwbManagerTest.java
@@ -6,14 +6,17 @@ import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.os.PersistableBundle;
import android.uwb.RangingSession;
import android.uwb.UwbManager;
+import android.uwb.UwbManager.AdapterStateCallback;
import com.google.common.collect.ImmutableList;
import java.util.List;
import org.junit.Before;
@@ -29,14 +32,46 @@ import org.robolectric.shadow.api.Shadow;
@Config(minSdk = S)
public class ShadowUwbManagerTest {
private /* RangingSession.Callback */ Object callbackObject;
+ private /* AdapterStateCallback */ Object adapterStateCallbackObject;
private /* UwbManager */ Object uwbManagerObject;
private ShadowRangingSession.Adapter adapter;
@Before
public void setUp() {
callbackObject = mock(RangingSession.Callback.class);
+ adapterStateCallbackObject = mock(AdapterStateCallback.class);
adapter = mock(ShadowRangingSession.Adapter.class);
uwbManagerObject = getApplicationContext().getSystemService(UwbManager.class);
+ ((UwbManager) uwbManagerObject)
+ .unregisterAdapterStateCallback((AdapterStateCallback) adapterStateCallbackObject);
+ }
+
+ @Test
+ public void registerAdapterStateCallback_invokesCallbackOnceInitially() {
+ UwbManager manager = (UwbManager) uwbManagerObject;
+ AdapterStateCallback adapterStateCallback = (AdapterStateCallback) adapterStateCallbackObject;
+
+ Shadow.<ShadowUwbManager>extract(manager)
+ .registerAdapterStateCallback(directExecutor(), adapterStateCallback);
+
+ verify(adapterStateCallback).onStateChanged(anyInt(), anyInt());
+ }
+
+ @Test
+ public void simulateAdapterStateChange_invokesCallbackWithGivenStateAndReason() {
+ UwbManager manager = (UwbManager) uwbManagerObject;
+ AdapterStateCallback adapterStateCallback = (AdapterStateCallback) adapterStateCallbackObject;
+ manager.registerAdapterStateCallback(directExecutor(), adapterStateCallback);
+
+ Shadow.<ShadowUwbManager>extract(manager)
+ .simulateAdapterStateChange(
+ AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_REGULATION,
+ AdapterStateCallback.STATE_DISABLED);
+
+ verify(adapterStateCallback)
+ .onStateChanged(
+ AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_REGULATION,
+ AdapterStateCallback.STATE_DISABLED);
}
@Test
@@ -73,6 +108,72 @@ public class ShadowUwbManagerTest {
@Config(minSdk = TIRAMISU)
@Test
+ public void setUwbEnabled_setToTrue_enablesUwbAndInvokesCallback() {
+ UwbManager manager = (UwbManager) uwbManagerObject;
+ AdapterStateCallback adapterStateCallback = (AdapterStateCallback) adapterStateCallbackObject;
+ manager.registerAdapterStateCallback(directExecutor(), adapterStateCallback);
+
+ Shadow.<ShadowUwbManager>extract(manager).setUwbEnabled(false);
+ Shadow.<ShadowUwbManager>extract(manager).setUwbEnabled(true);
+
+ assertThat(manager.isUwbEnabled()).isTrue();
+ // Invoked once when the callback is initially registered
+ verify(adapterStateCallback, times(2))
+ .onStateChanged(
+ AdapterStateCallback.STATE_ENABLED_INACTIVE,
+ AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_POLICY);
+ }
+
+ @Config(minSdk = TIRAMISU)
+ @Test
+ public void setUwbEnabled_setToFalse_disablesUwbAndInvokesCallback() {
+ UwbManager manager = (UwbManager) uwbManagerObject;
+ AdapterStateCallback adapterStateCallback = (AdapterStateCallback) adapterStateCallbackObject;
+ manager.registerAdapterStateCallback(directExecutor(), adapterStateCallback);
+
+ Shadow.<ShadowUwbManager>extract(manager).setUwbEnabled(false);
+
+ assertThat(manager.isUwbEnabled()).isFalse();
+ verify(adapterStateCallback)
+ .onStateChanged(
+ AdapterStateCallback.STATE_DISABLED,
+ AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_POLICY);
+ }
+
+ @Config(minSdk = TIRAMISU)
+ @Test
+ public void setUwbEnabled_enabledStateNotChanged_doesNothing() {
+ UwbManager manager = (UwbManager) uwbManagerObject;
+ AdapterStateCallback adapterStateCallback = (AdapterStateCallback) adapterStateCallbackObject;
+ manager.registerAdapterStateCallback(directExecutor(), adapterStateCallback);
+
+ Shadow.<ShadowUwbManager>extract(manager).setUwbEnabled(true);
+
+ assertThat(manager.isUwbEnabled()).isTrue();
+ // Invoked once when the callback is initially registered
+ verify(adapterStateCallback).onStateChanged(anyInt(), anyInt());
+ }
+
+ @Config(minSdk = TIRAMISU)
+ @Test
+ public void setUwbEnabled_disabledStateNotChanged_doesNothing() {
+ UwbManager manager = (UwbManager) uwbManagerObject;
+ AdapterStateCallback adapterStateCallback = (AdapterStateCallback) adapterStateCallbackObject;
+ manager.registerAdapterStateCallback(directExecutor(), adapterStateCallback);
+
+ Shadow.<ShadowUwbManager>extract(manager).setUwbEnabled(false);
+ Shadow.<ShadowUwbManager>extract(manager).setUwbEnabled(false);
+
+ assertThat(manager.isUwbEnabled()).isFalse();
+ // Invoked only once when UWB is initially disabled
+ verify(adapterStateCallback)
+ .onStateChanged(
+ AdapterStateCallback.STATE_DISABLED,
+ AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_POLICY);
+ }
+
+ @Config(minSdk = TIRAMISU)
+ @Test
public void getChipInfos_expectedValue() {
UwbManager manager = (UwbManager) uwbManagerObject;
Shadow.<ShadowUwbManager>extract(manager).setChipInfos(ImmutableList.of(genParams("chipInfo")));
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowValueAnimatorTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowValueAnimatorTest.java
index b6f7fe616..64d3f4c9c 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowValueAnimatorTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowValueAnimatorTest.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static com.google.common.truth.Truth.assertThat;
import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
@@ -13,10 +12,8 @@ import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Shadows;
-import org.robolectric.annotation.Config;
@RunWith(AndroidJUnit4.class)
-@Config(minSdk = JELLY_BEAN)
public class ShadowValueAnimatorTest {
@Test
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowViewTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowViewTest.java
index 432eec66c..336342ed9 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowViewTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowViewTest.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
@@ -426,8 +425,14 @@ public class ShadowViewTest {
@Test
public void scrollTo_shouldStoreTheScrolledCoordinates() throws Exception {
- view.scrollTo(1, 2);
- assertThat(shadowOf(view).scrollToCoordinates).isEqualTo(new Point(1, 2));
+ // This test depends on broken scrolling behavior.
+ System.setProperty("robolectric.useRealScrolling", "false");
+ try {
+ view.scrollTo(1, 2);
+ assertThat(shadowOf(view).scrollToCoordinates).isEqualTo(new Point(1, 2));
+ } finally {
+ System.clearProperty("robolectric.useRealScrolling");
+ }
}
@Test
@@ -440,12 +445,18 @@ public class ShadowViewTest {
@Test
public void scrollBy_shouldStoreTheScrolledCoordinates() throws Exception {
- view.scrollTo(4, 5);
- view.scrollBy(10, 20);
- assertThat(shadowOf(view).scrollToCoordinates).isEqualTo(new Point(14, 25));
-
- assertThat(view.getScrollX()).isEqualTo(14);
- assertThat(view.getScrollY()).isEqualTo(25);
+ // This test depends on broken scrolling behavior.
+ System.setProperty("robolectric.useRealScrolling", "false");
+ try {
+ view.scrollTo(4, 5);
+ view.scrollBy(10, 20);
+ assertThat(shadowOf(view).scrollToCoordinates).isEqualTo(new Point(14, 25));
+
+ assertThat(view.getScrollX()).isEqualTo(14);
+ assertThat(view.getScrollY()).isEqualTo(25);
+ } finally {
+ System.clearProperty("robolectric.useRealScrolling");
+ }
}
@Test
@@ -866,7 +877,7 @@ public class ShadowViewTest {
assertFalse(shadowOf(temporaryChild).isAttachedToWindow());
}
- @Test @Config(minSdk = JELLY_BEAN_MR2)
+ @Test
public void getWindowId_shouldReturnValidObjectWhenAttached() throws Exception {
MyView parent = new MyView("parent", transcript);
MyView child = new MyView("child", transcript);
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowVirtualDeviceManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowVirtualDeviceManagerTest.java
index f11cf763e..dcc5c4a1c 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowVirtualDeviceManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowVirtualDeviceManagerTest.java
@@ -1,6 +1,7 @@
package org.robolectric.shadows;
import static android.hardware.Sensor.TYPE_ACCELEROMETER;
+import static android.hardware.input.VirtualKeyEvent.ACTION_DOWN;
import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static com.google.common.truth.Truth.assertThat;
@@ -17,6 +18,19 @@ import android.companion.virtual.sensor.VirtualSensorCallback;
import android.companion.virtual.sensor.VirtualSensorConfig;
import android.content.Context;
import android.content.Intent;
+import android.hardware.input.VirtualKeyEvent;
+import android.hardware.input.VirtualKeyboard;
+import android.hardware.input.VirtualKeyboardConfig;
+import android.hardware.input.VirtualMouse;
+import android.hardware.input.VirtualMouseButtonEvent;
+import android.hardware.input.VirtualMouseConfig;
+import android.hardware.input.VirtualMouseRelativeEvent;
+import android.hardware.input.VirtualMouseScrollEvent;
+import android.hardware.input.VirtualTouchEvent;
+import android.hardware.input.VirtualTouchscreen;
+import android.hardware.input.VirtualTouchscreenConfig;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
import java.time.Duration;
import java.util.function.IntConsumer;
import org.junit.Before;
@@ -43,6 +57,9 @@ public class ShadowVirtualDeviceManagerTest {
private VirtualDeviceManager virtualDeviceManager;
@Mock private IntConsumer mockCallback;
+ private static final int DISPLAY_WIDTH = 720;
+ private static final int DISPLAY_HEIGHT = 1280;
+
@Before
public void setUp() throws Exception {
virtualDeviceManager =
@@ -167,4 +184,111 @@ public class ShadowVirtualDeviceManagerTest {
assertThat(retrievedCallback).isNotNull();
verify(mockVirtualSensorCallback).onConfigurationChanged(any(), eq(true), any(), any());
}
+
+ @Test
+ public void testCreateVirtualMouse() {
+ VirtualDevice virtualDevice =
+ virtualDeviceManager.createVirtualDevice(
+ 0, new VirtualDeviceParams.Builder().setName("foo").build());
+ VirtualMouseButtonEvent buttonDownEvent =
+ new VirtualMouseButtonEvent.Builder()
+ .setButtonCode(VirtualMouseButtonEvent.BUTTON_PRIMARY)
+ .setAction(VirtualMouseButtonEvent.ACTION_BUTTON_PRESS)
+ .build();
+ VirtualMouseButtonEvent buttonUpEvent =
+ new VirtualMouseButtonEvent.Builder()
+ .setButtonCode(VirtualMouseButtonEvent.BUTTON_PRIMARY)
+ .setAction(VirtualMouseButtonEvent.ACTION_BUTTON_RELEASE)
+ .build();
+ VirtualMouseScrollEvent scrollEvent =
+ new VirtualMouseScrollEvent.Builder().setXAxisMovement(0.5f).setYAxisMovement(0.5f).build();
+ VirtualMouseRelativeEvent relativeEvent =
+ new VirtualMouseRelativeEvent.Builder().setRelativeX(0.1f).setRelativeY(0.1f).build();
+
+ VirtualMouse virtualMouse =
+ virtualDevice.createVirtualMouse(new VirtualMouseConfig.Builder().build());
+ virtualMouse.sendButtonEvent(buttonDownEvent);
+ virtualMouse.sendButtonEvent(buttonUpEvent);
+ virtualMouse.sendScrollEvent(scrollEvent);
+ virtualMouse.sendRelativeEvent(relativeEvent);
+
+ assertThat(virtualMouse).isNotNull();
+ ShadowVirtualMouse shadowVirtualMouse = Shadow.extract(virtualMouse);
+ assertThat(shadowVirtualMouse.getSentButtonEvents())
+ .containsExactly(buttonDownEvent, buttonUpEvent);
+ assertThat(shadowVirtualMouse.getSentScrollEvents()).containsExactly(scrollEvent);
+ assertThat(shadowVirtualMouse.getSentRelativeEvents()).containsExactly(relativeEvent);
+ }
+
+ @Test
+ public void testCreateVirtualTouchscreen() {
+ VirtualDevice virtualDevice =
+ virtualDeviceManager.createVirtualDevice(
+ 0, new VirtualDeviceParams.Builder().setName("foo").build());
+ VirtualTouchEvent virtualTouchEvent =
+ new VirtualTouchEvent.Builder()
+ .setToolType(MotionEvent.TOOL_TYPE_FINGER)
+ .setAction(MotionEvent.ACTION_DOWN)
+ .setPointerId(1)
+ .setX(1.0f)
+ .setY(1.0f)
+ .build();
+
+ VirtualTouchscreen virtualTouchscreen =
+ virtualDevice.createVirtualTouchscreen(
+ new VirtualTouchscreenConfig.Builder(DISPLAY_WIDTH, DISPLAY_HEIGHT).build());
+ virtualTouchscreen.sendTouchEvent(virtualTouchEvent);
+
+ assertThat(virtualTouchscreen).isNotNull();
+ ShadowVirtualTouchscreen shadowVirtualTouchscreen = Shadow.extract(virtualTouchscreen);
+ assertThat(shadowVirtualTouchscreen.getSentEvents()).containsExactly(virtualTouchEvent);
+ }
+
+ @Test
+ public void testCreateVirtualKeyboard() {
+ VirtualDevice virtualDevice =
+ virtualDeviceManager.createVirtualDevice(
+ 0, new VirtualDeviceParams.Builder().setName("foo").build());
+ VirtualKeyEvent keyEvent1 =
+ new VirtualKeyEvent.Builder().setAction(ACTION_DOWN).setKeyCode(KeyEvent.KEYCODE_A).build();
+ VirtualKeyEvent keyEvent2 =
+ new VirtualKeyEvent.Builder()
+ .setAction(ACTION_DOWN)
+ .setKeyCode(KeyEvent.KEYCODE_ENTER)
+ .build();
+
+ VirtualKeyboard virtualKeyboard =
+ virtualDevice.createVirtualKeyboard(new VirtualKeyboardConfig.Builder().build());
+ virtualKeyboard.sendKeyEvent(keyEvent1);
+ virtualKeyboard.sendKeyEvent(keyEvent2);
+
+ assertThat(virtualKeyboard).isNotNull();
+ ShadowVirtualKeyboard shadowVirtualKeyboard = Shadow.extract(virtualKeyboard);
+ assertThat(shadowVirtualKeyboard.getSentEvents()).containsExactly(keyEvent1, keyEvent2);
+ }
+
+ @Test
+ public void testCloseVirtualInputDevices() {
+ VirtualDevice virtualDevice =
+ virtualDeviceManager.createVirtualDevice(
+ 0, new VirtualDeviceParams.Builder().setName("foo").build());
+ VirtualKeyboard virtualKeyboard =
+ virtualDevice.createVirtualKeyboard(new VirtualKeyboardConfig.Builder().build());
+ VirtualTouchscreen virtualTouchscreen =
+ virtualDevice.createVirtualTouchscreen(
+ new VirtualTouchscreenConfig.Builder(DISPLAY_WIDTH, DISPLAY_HEIGHT).build());
+ VirtualMouse virtualMouse =
+ virtualDevice.createVirtualMouse(new VirtualMouseConfig.Builder().build());
+
+ virtualKeyboard.close();
+ virtualTouchscreen.close();
+ virtualMouse.close();
+
+ ShadowVirtualKeyboard shadowVirtualKeyboard = Shadow.extract(virtualKeyboard);
+ ShadowVirtualTouchscreen shadowVirtualTouchscreen = Shadow.extract(virtualTouchscreen);
+ ShadowVirtualMouse shadowVirtualMouse = Shadow.extract(virtualMouse);
+ assertThat(shadowVirtualKeyboard.isClosed()).isTrue();
+ assertThat(shadowVirtualTouchscreen.isClosed()).isTrue();
+ assertThat(shadowVirtualMouse.isClosed()).isTrue();
+ }
}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowVisualizerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowVisualizerTest.java
index f62941f42..d4393b33f 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowVisualizerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowVisualizerTest.java
@@ -1,7 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.GINGERBREAD;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import static org.robolectric.Shadows.shadowOf;
@@ -16,12 +14,10 @@ import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowVisualizer.VisualizerSource;
/** Tests for {@link ShadowVisualizer}. */
@RunWith(AndroidJUnit4.class)
-@Config(minSdk = GINGERBREAD)
public class ShadowVisualizerTest {
private Visualizer visualizer;
@@ -98,7 +94,6 @@ public class ShadowVisualizerTest {
assertThat(visualizer.getCaptureSize()).isEqualTo(2000);
}
- @Config(minSdk = KITKAT)
@Test
public void getMeasurementPeakRms_returnsRmsFromSource() {
int peak = -500;
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowWallpaperManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowWallpaperManagerTest.java
index 675612cc0..7bafc91b3 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowWallpaperManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowWallpaperManagerTest.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.N;
import static android.os.Build.VERSION_CODES.TIRAMISU;
@@ -96,14 +95,12 @@ public class ShadowWallpaperManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void hasResourceWallpaper_wallpaperResourceNotSet_returnsFalse() {
assertThat(manager.hasResourceWallpaper(1)).isFalse();
assertThat(manager.hasResourceWallpaper(5)).isFalse();
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void hasResourceWallpaper_wallpaperResourceSet_returnsTrue() throws IOException {
int resid = 5;
manager.setResource(resid);
@@ -113,7 +110,6 @@ public class ShadowWallpaperManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void setResource_multipleTimes_hasResourceWallpaperReturnsTrueForLastValue()
throws IOException {
manager.setResource(1);
@@ -557,6 +553,16 @@ public class ShadowWallpaperManagerTest {
.isEqualTo(testImageBytes);
}
+ @Test
+ @Config(minSdk = TIRAMISU)
+ public void getAllWallpaperDimAmounts_returnsFullListOfAllDimAmountsSet() {
+ assertThat(shadowOf(manager).getAllWallpaperDimAmounts()).isEmpty();
+ manager.setWallpaperDimAmount(0.5f);
+ assertThat(shadowOf(manager).getAllWallpaperDimAmounts()).containsExactly(0.5f);
+ manager.setWallpaperDimAmount(0f);
+ assertThat(shadowOf(manager).getAllWallpaperDimAmounts()).containsExactly(0.5f, 0f);
+ }
+
private static byte[] getBytesFromFileDescriptor(FileDescriptor fileDescriptor)
throws IOException {
InputStream inputStream = new FileInputStream(fileDescriptor);
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowWebSettingsTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowWebSettingsTest.java
index 33280417c..aeda81d2b 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowWebSettingsTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowWebSettingsTest.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
@@ -10,7 +9,6 @@ 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;
/** Tests for {@link ShadowWebSettings} */
@RunWith(AndroidJUnit4.class)
@@ -24,7 +22,6 @@ public final class ShadowWebSettingsTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR2)
public void setDefaultUserAgent() {
ShadowWebSettings.setDefaultUserAgent("Chrome/71.0.143.1");
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowWifiConfigurationTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowWifiConfigurationTest.java
index 0b30d7e03..1576c8564 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowWifiConfigurationTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowWifiConfigurationTest.java
@@ -63,7 +63,6 @@ public class ShadowWifiConfigurationTest {
assertThat(copy.wepKeys[3]).isEqualTo("3");
}
- @Config(minSdk = Build.VERSION_CODES.JELLY_BEAN_MR2)
@Test
public void shouldCopy_sdk18() {
WifiConfiguration wifiConfiguration = new WifiConfiguration();
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowWifiManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowWifiManagerTest.java
index 6563db768..bfcd80e50 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowWifiManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowWifiManagerTest.java
@@ -1,7 +1,6 @@
package org.robolectric.shadows;
import static android.net.wifi.WifiManager.SCAN_RESULTS_AVAILABLE_ACTION;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.Q;
import static android.os.Build.VERSION_CODES.R;
@@ -156,7 +155,6 @@ public class ShadowWifiManagerTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR2)
public void getIsScanAlwaysAvailable() {
shadowOf(wifiManager).setIsScanAlwaysAvailable(true);
assertThat(wifiManager.isScanAlwaysAvailable()).isEqualTo(true);
@@ -607,7 +605,6 @@ public class ShadowWifiManagerTest {
}
@Test
- @Config(minSdk = Build.VERSION_CODES.KITKAT)
public void connect_setsNetworkId_shouldHasNetworkId() {
// WHEN
wifiManager.connect(123, null);
@@ -617,7 +614,6 @@ public class ShadowWifiManagerTest {
}
@Test
- @Config(minSdk = Build.VERSION_CODES.KITKAT)
public void connect_setsConnectionInfo() {
// GIVEN
WifiConfiguration wifiConfiguration = new WifiConfiguration();
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowWindowManagerGlobalTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowWindowManagerGlobalTest.java
index 2ffab72e8..8bc4a049d 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowWindowManagerGlobalTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowWindowManagerGlobalTest.java
@@ -29,7 +29,6 @@ public class ShadowWindowManagerGlobalTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void getWindowSession_shouldReturnSession() {
assertThat(ShadowWindowManagerGlobal.getWindowSession()).isNotNull();
}
@@ -54,7 +53,6 @@ public class ShadowWindowManagerGlobalTest {
}
@Test
- @Config(minSdk = JELLY_BEAN_MR1)
public void windowIsVisible() {
View decorView =
Robolectric.buildActivity(DragActivity.class).setup().get().getWindow().getDecorView();
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowWindowTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowWindowTest.java
index 2a6ab49ad..924cf82a9 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowWindowTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowWindowTest.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.N;
@@ -81,7 +80,7 @@ public class ShadowWindowTest {
}
@Test
- @Config(minSdk = KITKAT, maxSdk = VERSION_CODES.R)
+ @Config(maxSdk = VERSION_CODES.R)
public void getSystemFlag_shouldReturnFlagsSetViaAddPrivateFlags() throws Exception {
Activity activity = Robolectric.buildActivity(Activity.class).create().get();
Window window = activity.getWindow();
@@ -93,7 +92,7 @@ public class ShadowWindowTest {
}
@Test
- @Config(minSdk = KITKAT, maxSdk = VERSION_CODES.R)
+ @Config(maxSdk = VERSION_CODES.R)
public void getSystemFlag_callingAddPrivateFlagsShouldNotOverrideExistingFlags()
throws Exception {
Activity activity = Robolectric.buildActivity(Activity.class).create().get();
diff --git a/robolectric/src/test/resources/AndroidManifest.xml b/robolectric/src/test/resources/AndroidManifest.xml
index f5e755d73..b677954f2 100644
--- a/robolectric/src/test/resources/AndroidManifest.xml
+++ b/robolectric/src/test/resources/AndroidManifest.xml
@@ -39,7 +39,6 @@
android:allowBackup="true"
android:allowClearUserData="true"
android:allowTaskReparenting="true"
- android:debuggable="true"
android:hasCode="true"
android:killAfterRestore="true"
android:persistent="true"
@@ -91,7 +90,7 @@
<activity android:name=".android.controller.ActivityControllerTest$ConfigAwareActivity"
android:configChanges="fontScale|smallestScreenSize" />
- <activity android:name="org.robolectric.shadows.TestActivity">
+ <activity android:name="org.robolectric.shadows.TestActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
@@ -198,32 +197,32 @@
</intent-filter>
</receiver>
- <receiver android:name="org.robolectric.fakes.ConfigTestReceiver">
+ <receiver android:name="org.robolectric.fakes.ConfigTestReceiver" android:exported="true">
<intent-filter>
<action android:name="org.robolectric.ACTION_SUPERSET_PACKAGE"/>
</intent-filter>
</receiver>
- <receiver android:name="org.robolectric.ConfigTestReceiver">
+ <receiver android:name="org.robolectric.ConfigTestReceiver" android:exported="true">
<intent-filter>
<action android:name="org.robolectric.ACTION_SUBSET_PACKAGE"/>
</intent-filter>
</receiver>
- <receiver android:name=".DotConfigTestReceiver">
+ <receiver android:name=".DotConfigTestReceiver" android:exported="true">
<intent-filter>
<action android:name="org.robolectric.ACTION_DOT_PACKAGE"/>
</intent-filter>
</receiver>
- <receiver android:name=".test.ConfigTestReceiver">
+ <receiver android:name=".test.ConfigTestReceiver" android:exported="true">
<intent-filter>
<action android:name="org.robolectric.ACTION_DOT_SUBPACKAGE"/>
</intent-filter>
<meta-data android:name="numberOfSheep" android:value="42" />
</receiver>
- <receiver android:name="com.foo.Receiver">
+ <receiver android:name="com.foo.Receiver" android:exported="true">
<intent-filter>
<action android:name="org.robolectric.ACTION_DIFFERENT_PACKAGE"/>
</intent-filter>
diff --git a/robolectric/src/test/resources/TestAndroidManifestWithAppComponentFactory.xml b/robolectric/src/test/resources/TestAndroidManifestWithAppComponentFactory.xml
index 8741436a8..2bc4c0d74 100644
--- a/robolectric/src/test/resources/TestAndroidManifestWithAppComponentFactory.xml
+++ b/robolectric/src/test/resources/TestAndroidManifestWithAppComponentFactory.xml
@@ -12,5 +12,19 @@
</receiver>
<receiver
android:name=".CustomConstructorReceiverWrapper$CustomConstructorWithEmptyActionReceiver" />
+
+ <service
+ android:name=".CustomConstructorServices$CustomConstructorService" />
+ <service
+ android:name=".CustomConstructorServices$CustomConstructorIntentService" />
+ <service
+ android:name=".CustomConstructorServices$CustomConstructorJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE"/>
+ <provider
+ android:name=".CustomConstructorContentProvider"
+ android:authorities="org.robolectric.authority" />
+ <activity
+ android:name=".CustomConstructorContentProvider"
+ android:authorities="org.robolectric.authority" />
</application>
</manifest>
diff --git a/robolectric/src/test/resources/TestAndroidManifestWithFlags.xml b/robolectric/src/test/resources/TestAndroidManifestWithFlags.xml
index 540d337a3..95aae7374 100644
--- a/robolectric/src/test/resources/TestAndroidManifestWithFlags.xml
+++ b/robolectric/src/test/resources/TestAndroidManifestWithFlags.xml
@@ -5,7 +5,6 @@
android:allowBackup="true"
android:allowClearUserData="true"
android:allowTaskReparenting="true"
- android:debuggable="true"
android:hasCode="true"
android:killAfterRestore="true"
android:persistent="true"
diff --git a/robolectric/src/test/resources/TestAndroidManifestWithReceivers.xml b/robolectric/src/test/resources/TestAndroidManifestWithReceivers.xml
index d5b5103c6..be7ff8f76 100644
--- a/robolectric/src/test/resources/TestAndroidManifestWithReceivers.xml
+++ b/robolectric/src/test/resources/TestAndroidManifestWithReceivers.xml
@@ -21,7 +21,7 @@
</intent-filter>
</receiver>
- <receiver android:name="org.robolectric.ConfigTestReceiver">
+ <receiver android:name="org.robolectric.ConfigTestReceiver" android:exported="true">
<intent-filter>
<action android:name="org.robolectric.ACTION_SUBSET_PACKAGE"/>
</intent-filter>
diff --git a/sandbox/src/main/java/org/robolectric/internal/bytecode/ClassHandler.java b/sandbox/src/main/java/org/robolectric/internal/bytecode/ClassHandler.java
index 73fc9e876..5fae33362 100644
--- a/sandbox/src/main/java/org/robolectric/internal/bytecode/ClassHandler.java
+++ b/sandbox/src/main/java/org/robolectric/internal/bytecode/ClassHandler.java
@@ -69,7 +69,7 @@ public interface ClassHandler {
* @see ShadowInvalidator for invalidating the returned {@link MethodHandle}
*/
MethodHandle findShadowMethodHandle(
- Class<?> theClass, String name, MethodType methodType, boolean isStatic)
+ Class<?> theClass, String name, MethodType methodType, boolean isStatic, boolean isNative)
throws IllegalAccessException;
/**
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 5c77b9964..a9b532a26 100644
--- a/sandbox/src/main/java/org/robolectric/internal/bytecode/ClassInstrumentor.java
+++ b/sandbox/src/main/java/org/robolectric/internal/bytecode/ClassInstrumentor.java
@@ -38,7 +38,6 @@ import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.VarInsnNode;
-import org.robolectric.sandbox.NativeMethodNotFoundException;
import org.robolectric.util.PerfStatsCollector;
/**
@@ -54,15 +53,23 @@ public class ClassInstrumentor {
protected static final Type OBJECT_TYPE = Type.getType(Object.class);
private static final ShadowImpl SHADOW_IMPL = new ShadowImpl();
final Decorator decorator;
- private NativeCallHandler nativeCallHandler;
static {
String className = Type.getInternalName(InvokeDynamicSupport.class);
MethodType bootstrap =
methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class);
+
+ /*
+ * There is an additional int.class argument to the invokedynamic bootstrap method. This conveys
+ * whether or not the method invocation represents a native method. A one means the original
+ * method was a native method, and a zero means it was not. It should be boolean.class, but
+ * that is nt possible due to https://bugs.java.com/bugdatabase/view_bug?bug_id=JDK-8322510.
+ */
String bootstrapMethod =
- bootstrap.appendParameterTypes(MethodHandle.class).toMethodDescriptorString();
+ bootstrap
+ .appendParameterTypes(MethodHandle.class, /* isNative */ int.class)
+ .toMethodDescriptorString();
String bootstrapIntrinsic =
bootstrap.appendParameterTypes(String.class).toMethodDescriptorString();
@@ -397,7 +404,7 @@ public class ClassInstrumentor {
generator.loadThis();
generator.invokeVirtual(mutableClass.classType, new Method(ROBO_INIT_METHOD_NAME, "()V"));
generateClassHandlerCall(
- mutableClass, method, ShadowConstants.CONSTRUCTOR_METHOD_NAME, generator);
+ mutableClass, method, ShadowConstants.CONSTRUCTOR_METHOD_NAME, generator, false);
generator.endMethod();
@@ -526,7 +533,7 @@ public class ClassInstrumentor {
makeMethodPrivate(method);
RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(delegatorMethodNode);
- generateClassHandlerCall(mutableClass, method, originalName, generator);
+ generateClassHandlerCall(mutableClass, method, originalName, generator, isNativeMethod);
generator.endMethod();
mutableClass.addMethod(delegatorMethodNode);
}
@@ -537,21 +544,26 @@ public class ClassInstrumentor {
* @param method Method to be instrumented, must be native
*/
protected void instrumentNativeMethod(MutableClass mutableClass, MethodNode method) {
+
+ String nativeBindingMethodName =
+ SHADOW_IMPL.directNativeMethodName(mutableClass.getName(), method.name);
+
+ // Generate native binding method
+ MethodNode nativeBindingMethod =
+ new MethodNode(
+ Opcodes.ASM4,
+ nativeBindingMethodName,
+ method.desc,
+ method.signature,
+ exceptionArray(method));
+ nativeBindingMethod.access = method.access | Opcodes.ACC_SYNTHETIC;
+ makeMethodPrivate(nativeBindingMethod);
+ mutableClass.addMethod(nativeBindingMethod);
+
method.access = method.access & ~Opcodes.ACC_NATIVE;
RobolectricGeneratorAdapter generator = new RobolectricGeneratorAdapter(method);
- if (nativeCallHandler != null) {
- String descriptor =
- String.format("%s#%s%s", mutableClass.getName(), method.name, method.desc);
- nativeCallHandler.logNativeCall(descriptor);
- if (nativeCallHandler.shouldThrow(descriptor)) {
- String message =
- nativeCallHandler.getExceptionMessage(descriptor, mutableClass.getName(), method.name);
- generator.throwException(Type.getType(NativeMethodNotFoundException.class), message);
- }
- }
-
Type returnType = generator.getReturnType();
generator.pushDefaultReturnValueToStack(returnType);
generator.returnValue();
@@ -719,7 +731,8 @@ public class ClassInstrumentor {
MutableClass mutableClass,
MethodNode originalMethod,
String originalMethodName,
- RobolectricGeneratorAdapter generator) {
+ RobolectricGeneratorAdapter generator,
+ boolean isNativeMethod) {
Handle original =
new Handle(
getTag(originalMethod),
@@ -730,12 +743,13 @@ public class ClassInstrumentor {
if (generator.isStatic()) {
generator.loadArgs();
- generator.invokeDynamic(originalMethodName, originalMethod.desc, BOOTSTRAP_STATIC, original);
+ generator.invokeDynamic(
+ originalMethodName, originalMethod.desc, BOOTSTRAP_STATIC, original, isNativeMethod);
} else {
String desc = "(" + mutableClass.classType.getDescriptor() + originalMethod.desc.substring(1);
generator.loadThis();
generator.loadArgs();
- generator.invokeDynamic(originalMethodName, desc, BOOTSTRAP, original);
+ generator.invokeDynamic(originalMethodName, desc, BOOTSTRAP, original, isNativeMethod);
}
generator.returnValue();
@@ -753,10 +767,6 @@ public class ClassInstrumentor {
return -1;
}
- public void setNativeCallHandler(NativeCallHandler nativeCallHandler) {
- this.nativeCallHandler = nativeCallHandler;
- }
-
public interface Decorator {
void decorate(MutableClass mutableClass);
}
diff --git a/sandbox/src/main/java/org/robolectric/internal/bytecode/InvokeDynamicSupport.java b/sandbox/src/main/java/org/robolectric/internal/bytecode/InvokeDynamicSupport.java
index d7b8b7937..fa53caec0 100644
--- a/sandbox/src/main/java/org/robolectric/internal/bytecode/InvokeDynamicSupport.java
+++ b/sandbox/src/main/java/org/robolectric/internal/bytecode/InvokeDynamicSupport.java
@@ -31,6 +31,15 @@ public class InvokeDynamicSupport {
private static final MethodHandle EXCEPTION_HANDLER;
private static final MethodHandle GET_SHADOW;
+ /**
+ * Represents the boolean 'true' as an integer. Due to a JVM bug, invokedynamic bootstrap methods
+ * currently do not support extra primitive boolean parameters. Integers are required to convey
+ * booleans.
+ *
+ * <p>See https://bugs.java.com/bugdatabase/view_bug?bug_id=JDK-8322510
+ */
+ private static final int BOOLEAN_TRUE = 1;
+
static {
try {
MethodHandles.Lookup lookup = MethodHandles.lookup();
@@ -75,14 +84,24 @@ public class InvokeDynamicSupport {
@SuppressWarnings("UnusedDeclaration")
public static CallSite bootstrap(
- MethodHandles.Lookup caller, String name, MethodType type, MethodHandle original)
+ MethodHandles.Lookup caller,
+ String name,
+ MethodType type,
+ MethodHandle original,
+ int isNative /* 1 == originally native, 0 == not originally native */)
throws IllegalAccessException {
return PerfStatsCollector.getInstance()
.measure(
"invokedynamic bootstrap",
() -> {
MethodCallSite site =
- new MethodCallSite(caller.lookupClass(), type, name, original, REGULAR);
+ new MethodCallSite(
+ caller.lookupClass(),
+ type,
+ name,
+ original,
+ REGULAR,
+ isNative == BOOLEAN_TRUE);
bindCallSite(site);
@@ -92,14 +111,19 @@ public class InvokeDynamicSupport {
@SuppressWarnings("UnusedDeclaration")
public static CallSite bootstrapStatic(
- MethodHandles.Lookup caller, String name, MethodType type, MethodHandle original)
+ MethodHandles.Lookup caller,
+ String name,
+ MethodType type,
+ MethodHandle original,
+ int isNative /* 1 == originally native, 0 == not originally native */)
throws IllegalAccessException {
return PerfStatsCollector.getInstance()
.measure(
"invokedynamic bootstrap static",
() -> {
MethodCallSite site =
- new MethodCallSite(caller.lookupClass(), type, name, original, STATIC);
+ new MethodCallSite(
+ caller.lookupClass(), type, name, original, STATIC, isNative == BOOLEAN_TRUE);
bindCallSite(site);
@@ -159,7 +183,7 @@ public class InvokeDynamicSupport {
private static MethodHandle bindCallSite(MethodCallSite site) throws IllegalAccessException {
MethodHandle mh =
RobolectricInternals.findShadowMethodHandle(
- site.getTheClass(), site.getName(), site.type(), site.isStatic());
+ site.getTheClass(), site.getName(), site.type(), site.isStatic(), site.isNative());
if (mh == null) {
// call original code
@@ -169,8 +193,13 @@ public class InvokeDynamicSupport {
mh = dropArguments(mh, 0, site.type().parameterList());
} else if (!site.isStatic()) {
// drop arg 0 (this) for static methods
- Class<?> shadowType = mh.type().parameterType(0);
- mh = filterArguments(mh, 0, GET_SHADOW.asType(methodType(shadowType, site.thisType())));
+ Class<?> mhType = mh.type().parameterType(0);
+ // At this point, thisType is either a shadow type, or in the case of native method
+ // invocations, it can be equivalent to the original type.
+ if (!mhType.equals(site.getTheClass())) {
+ // Only invoke getShadow if the method is on class that is decoupled from the original.
+ mh = filterArguments(mh, 0, GET_SHADOW.asType(methodType(mhType, site.thisType())));
+ }
}
try {
diff --git a/sandbox/src/main/java/org/robolectric/internal/bytecode/MethodCallSite.java b/sandbox/src/main/java/org/robolectric/internal/bytecode/MethodCallSite.java
index bcb2ae089..df6f3f847 100644
--- a/sandbox/src/main/java/org/robolectric/internal/bytecode/MethodCallSite.java
+++ b/sandbox/src/main/java/org/robolectric/internal/bytecode/MethodCallSite.java
@@ -10,12 +10,20 @@ public class MethodCallSite extends RoboCallSite {
private final MethodHandle original;
private final Kind kind;
- public MethodCallSite(Class<?> theClass, MethodType type, String name, MethodHandle original,
- Kind kind) {
+ private final boolean isNative;
+
+ public MethodCallSite(
+ Class<?> theClass,
+ MethodType type,
+ String name,
+ MethodHandle original,
+ Kind kind,
+ boolean isNative) {
super(type, theClass);
this.name = name;
this.original = original;
this.kind = kind;
+ this.isNative = isNative;
}
public String getName() {
@@ -34,6 +42,10 @@ public class MethodCallSite extends RoboCallSite {
return kind == STATIC;
}
+ public boolean isNative() {
+ return isNative;
+ }
+
@Override public String toString() {
return "RoboCallSite{" +
"theClass=" + getTheClass() +
diff --git a/sandbox/src/main/java/org/robolectric/internal/bytecode/NativeCallHandler.java b/sandbox/src/main/java/org/robolectric/internal/bytecode/NativeCallHandler.java
deleted file mode 100644
index 89034d63f..000000000
--- a/sandbox/src/main/java/org/robolectric/internal/bytecode/NativeCallHandler.java
+++ /dev/null
@@ -1,137 +0,0 @@
-package org.robolectric.internal.bytecode;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileReader;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.Set;
-import java.util.TreeSet;
-import javax.annotation.Nonnull;
-
-/**
- * Handler for native calls instrumented by ClassInstrumentor.
- *
- * <p>Native Calls can either be instrumented as no-op calls (returning a default value or 0 or
- * null) or throw an exception. This helper class helps maintain a list of exemptions to indicates
- * which native calls should be no-op and never throw.
- */
-public class NativeCallHandler {
-
- private final File exemptionsFile;
- private final boolean writeExemptions;
- private final boolean throwOnNatives;
- private final Set<String> descriptors = new TreeSet<>();
-
- /**
- * Initializes the native calls handler.
- *
- * @param exemptionsFile The exemptions file to read from and/or to generate.
- * @param writeExemptions When true, native calls are added to the exemption list.
- * @param throwOnNatives Whether native calls should throw by default unless their signature is
- * listed in the exemption list. When false, all native calls become no-op.
- * @throws IOException if there's an issue reading an existing exemption list.
- */
- public NativeCallHandler(
- @Nonnull File exemptionsFile, boolean writeExemptions, boolean throwOnNatives)
- throws IOException {
- this.exemptionsFile = exemptionsFile;
- this.writeExemptions = writeExemptions;
- this.throwOnNatives = throwOnNatives;
-
- if (exemptionsFile.exists()) {
- readExemptionsList(exemptionsFile);
- }
- }
-
- private String getExemptionFileName() {
- return exemptionsFile.getName();
- }
-
- private void readExemptionsList(File exemptionsFile) throws IOException {
- try (BufferedReader reader =
- new BufferedReader(new FileReader(exemptionsFile.getPath(), UTF_8))) {
- String line;
- while ((line = reader.readLine()) != null) {
- // Sanitize input. Ignore empty lines and commented lines starting with #.
- line = sanitize(line.trim());
- if (line.isEmpty() || line.charAt(0) == '#') {
- continue;
- }
- descriptors.add(line);
- }
- }
- System.out.println(
- "Loaded " + descriptors.size() + " exemptions from " + exemptionsFile.getPath());
- }
-
- public void writeExemptionsList() throws IOException {
- try (BufferedWriter writer =
- new BufferedWriter(new FileWriter(exemptionsFile.getPath(), UTF_8))) {
- for (String descriptor : descriptors) {
- writer.write(descriptor);
- writer.write('\n');
- }
- }
- System.out.println(
- "Wrote " + descriptors.size() + " exemptions to " + exemptionsFile.getPath());
- }
-
- /**
- * Adds the method description to the native call exemption list if {@link #writeExemptions} is
- * set.
- */
- public void logNativeCall(@Nonnull String descriptor) {
- if (!writeExemptions) {
- return;
- }
- descriptors.add(sanitize(descriptor));
- }
-
- /** Returns whether the ClassInstrumentor should generate an exception or a no-op bytecode. */
- public boolean shouldThrow(@Nonnull String descriptor) {
- return throwOnNatives && !descriptors.contains(sanitize(descriptor));
- }
-
- private String sanitize(String descriptor) {
- // Post-processing of the exemptions files is made complicated by the presence of $ signs
- // in the FQCN. Instead of escaping them, just replace them by another unused character
- // that is not so sensitive to shell or make mangling.
- return descriptor.replace('$', '^');
- }
-
- /**
- * Returns the detailed message to be used by the ClassInstrumentor in the generated bytecode.
- *
- * @param descriptor The ASM descriptor as it should be written in the exemption file.
- * @param className The fully qualified class name, used for the user description.
- * @param methodName The method name, used for the user description.
- */
- public String getExceptionMessage(
- @Nonnull String descriptor, @Nonnull String className, @Nonnull String methodName) {
- // The shadow message is merely a hint based on the last component of the FQCN, which is
- // typically the pattern used for shadow classes.
- String shadowHint =
- "Shadow" + className.replaceAll("[^.]+\\.", "").replaceAll("\\$.*", "") + ".java";
- // The message below tries to educate the user that shadow overrides are not necessarily
- // needed nor desired for trivial cases that are better covered by a no-op return operation.
- return "Unexpected Robolectric native method call to '"
- + className
- + "#"
- + methodName
- + "()'.\n"
- + "Option 1: If customizing this method is useful, add an implementation in "
- + shadowHint
- + ".\n"
- + "Option 2: If this method just needs to trivially return 0 or null, please add an"
- + " exemption entry for\n"
- + " "
- + sanitize(descriptor)
- + "\n"
- + "to exemption file "
- + getExemptionFileName();
- }
-}
diff --git a/sandbox/src/main/java/org/robolectric/internal/bytecode/RobolectricInternals.java b/sandbox/src/main/java/org/robolectric/internal/bytecode/RobolectricInternals.java
index 673f5c1f8..bfecf8f92 100644
--- a/sandbox/src/main/java/org/robolectric/internal/bytecode/RobolectricInternals.java
+++ b/sandbox/src/main/java/org/robolectric/internal/bytecode/RobolectricInternals.java
@@ -31,9 +31,9 @@ public class RobolectricInternals {
}
public static MethodHandle findShadowMethodHandle(
- Class<?> theClass, String name, MethodType methodType, boolean isStatic)
+ Class<?> theClass, String name, MethodType methodType, boolean isStatic, boolean isNative)
throws IllegalAccessException {
- return classHandler.findShadowMethodHandle(theClass, name, methodType, isStatic);
+ return classHandler.findShadowMethodHandle(theClass, name, methodType, isStatic, isNative);
}
@SuppressWarnings("UnusedDeclaration")
diff --git a/sandbox/src/main/java/org/robolectric/internal/bytecode/Sandbox.java b/sandbox/src/main/java/org/robolectric/internal/bytecode/Sandbox.java
index 263e91bc3..92f897f8b 100644
--- a/sandbox/src/main/java/org/robolectric/internal/bytecode/Sandbox.java
+++ b/sandbox/src/main/java/org/robolectric/internal/bytecode/Sandbox.java
@@ -1,7 +1,6 @@
package org.robolectric.internal.bytecode;
import static java.util.concurrent.TimeUnit.SECONDS;
-import static org.robolectric.util.ReflectionHelpers.newInstance;
import static org.robolectric.util.ReflectionHelpers.setStaticField;
import java.io.IOException;
@@ -16,6 +15,7 @@ import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import javax.inject.Inject;
import org.robolectric.shadow.api.Shadow;
+import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.Util;
public class Sandbox {
@@ -92,7 +92,10 @@ public class Sandbox {
setStaticField(invokeDynamicSupportClass, "INTERCEPTORS", interceptors);
Class<?> shadowClass = bootstrappedClass(Shadow.class);
- setStaticField(shadowClass, "SHADOW_IMPL", newInstance(bootstrappedClass(ShadowImpl.class)));
+ setStaticField(
+ shadowClass,
+ "SHADOW_IMPL",
+ ReflectionHelpers.newInstance(bootstrappedClass(ShadowImpl.class)));
}
public void runOnMainThread(Runnable runnable) {
diff --git a/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowDecorator.java b/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowDecorator.java
index a83009d63..6a42c2063 100644
--- a/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowDecorator.java
+++ b/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowDecorator.java
@@ -22,7 +22,7 @@ public class ShadowDecorator implements ClassInstrumentor.Decorator {
mutableClass.addField(
0,
new FieldNode(
- Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC,
+ Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_TRANSIENT,
ShadowConstants.CLASS_HANDLER_DATA_FIELD_NAME,
OBJECT_DESC,
OBJECT_DESC,
diff --git a/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowImpl.java b/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowImpl.java
index a55bf5f42..5a5bb32b2 100644
--- a/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowImpl.java
+++ b/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowImpl.java
@@ -18,7 +18,8 @@ public class ShadowImpl implements IShadow {
return ReflectionHelpers.callConstructor(clazz);
}
- @Override public <T> T newInstance(Class<T> clazz, Class[] parameterTypes, Object[] params) {
+ @Override
+ public <T> T newInstance(Class<T> clazz, Class<?>[] parameterTypes, Object[] params) {
return ReflectionHelpers.callConstructor(clazz, ReflectionHelpers.ClassParameter.fromComponentLists(parameterTypes, params));
}
@@ -36,38 +37,50 @@ public class ShadowImpl implements IShadow {
return createProxy(shadowedObject, clazz);
}
- private <T> T createProxy(T shadowedObject, Class<T> clazz) {
- try {
- return proxyMaker.createProxy(clazz, shadowedObject);
- } catch (Exception e) {
- throw new RuntimeException("error creating direct call proxy for " + clazz, e);
- }
- }
-
- @Override @SuppressWarnings(value = {"unchecked", "TypeParameterUnusedInFormals"})
- public <R> R directlyOn(Object shadowedObject, String clazzName, String methodName, ReflectionHelpers.ClassParameter... paramValues) {
+ @Override
+ @SuppressWarnings(value = {"unchecked", "TypeParameterUnusedInFormals"})
+ public <R> R directlyOn(
+ Object shadowedObject,
+ String clazzName,
+ String methodName,
+ ReflectionHelpers.ClassParameter<?>... paramValues) {
try {
- Class<Object> aClass = (Class<Object>) shadowedObject.getClass().getClassLoader().loadClass(clazzName);
+ Class<Object> aClass =
+ (Class<Object>) shadowedObject.getClass().getClassLoader().loadClass(clazzName);
return directlyOn(shadowedObject, aClass, methodName, paramValues);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
- @Override @SuppressWarnings(value = {"unchecked", "TypeParameterUnusedInFormals"})
- public <R, T> R directlyOn(T shadowedObject, Class<T> clazz, String methodName, ReflectionHelpers.ClassParameter... paramValues) {
+ @Override
+ @SuppressWarnings(value = {"unchecked", "TypeParameterUnusedInFormals"})
+ public <R, T> R directlyOn(
+ T shadowedObject,
+ Class<T> clazz,
+ String methodName,
+ ReflectionHelpers.ClassParameter<?>... paramValues) {
String directMethodName = directMethodName(clazz.getName(), methodName);
- return (R) ReflectionHelpers.callInstanceMethod(clazz, shadowedObject, directMethodName, paramValues);
+ return (R)
+ ReflectionHelpers.callInstanceMethod(clazz, shadowedObject, directMethodName, paramValues);
}
- @Override @SuppressWarnings(value = {"unchecked", "TypeParameterUnusedInFormals"})
- public <R, T> R directlyOn(Class<T> clazz, String methodName, ReflectionHelpers.ClassParameter... paramValues) {
+ @Override
+ @SuppressWarnings(value = {"unchecked", "TypeParameterUnusedInFormals"})
+ public <R, T> R directlyOn(
+ Class<T> clazz, String methodName, ReflectionHelpers.ClassParameter<?>... paramValues) {
String directMethodName = directMethodName(clazz.getName(), methodName);
return (R) ReflectionHelpers.callStaticMethod(clazz, directMethodName, paramValues);
}
- @Override @SuppressWarnings(value = {"unchecked", "TypeParameterUnusedInFormals"})
- public <R> R invokeConstructor(Class<? extends R> clazz, R instance, ReflectionHelpers.ClassParameter... paramValues) {
+ private <T> T createProxy(T shadowedObject, Class<T> clazz) {
+ return proxyMaker.createProxy(clazz, shadowedObject);
+ }
+
+ @Override
+ @SuppressWarnings(value = {"unchecked", "TypeParameterUnusedInFormals"})
+ public <R> R invokeConstructor(
+ Class<? extends R> clazz, R instance, ReflectionHelpers.ClassParameter<?>... paramValues) {
String directMethodName =
directMethodName(clazz.getName(), ShadowConstants.CONSTRUCTOR_METHOD_NAME);
return (R) ReflectionHelpers.callInstanceMethod(clazz, instance, directMethodName, paramValues);
@@ -81,6 +94,11 @@ public class ShadowImpl implements IShadow {
}
@Override
+ public String directNativeMethodName(String className, String methodName) {
+ return ShadowConstants.ROBO_PREFIX + methodName + "$nativeBinding";
+ }
+
+ @Override
public void directInitialize(Class<?> clazz) {
try {
RobolectricInternals.performStaticInitialization(clazz);
diff --git a/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowInfo.java b/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowInfo.java
index 1276c3722..b82c25b33 100644
--- a/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowInfo.java
+++ b/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowInfo.java
@@ -11,6 +11,8 @@ public class ShadowInfo {
public final String shadowedClassName;
public final String shadowClassName;
public final boolean callThroughByDefault;
+ public final boolean callNativeMethodsByDefault;
+
public final boolean looseSignatures;
private final int minSdk;
private final int maxSdk;
@@ -20,6 +22,7 @@ public class ShadowInfo {
String shadowedClassName,
String shadowClassName,
boolean callThroughByDefault,
+ boolean callNativeMethodsByDefault,
boolean looseSignatures,
int minSdk,
int maxSdk,
@@ -27,6 +30,7 @@ public class ShadowInfo {
this.shadowedClassName = shadowedClassName;
this.shadowClassName = shadowClassName;
this.callThroughByDefault = callThroughByDefault;
+ this.callNativeMethodsByDefault = callNativeMethodsByDefault;
this.looseSignatures = looseSignatures;
this.minSdk = minSdk;
this.maxSdk = maxSdk;
@@ -37,9 +41,11 @@ public class ShadowInfo {
}
ShadowInfo(String shadowedClassName, String shadowClassName, Implements annotation) {
- this(shadowedClassName,
+ this(
+ shadowedClassName,
shadowClassName,
annotation.callThroughByDefault(),
+ annotation.callNativeMethodsByDefault(),
annotation.looseSignatures(),
annotation.minSdk(),
annotation.maxSdk(),
diff --git a/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowMap.java b/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowMap.java
index cb77a1f74..5fb531d4e 100644
--- a/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowMap.java
+++ b/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowMap.java
@@ -70,7 +70,7 @@ public class ShadowMap {
}
public boolean hasShadowPicker(MutableClass mutableClass) {
- return shadowPickers.containsKey(mutableClass.getName().replace('$', '.'));
+ return shadowPickers.containsKey(mutableClass.getName());
}
public ShadowInfo getShadowInfo(Class<?> clazz, ShadowMatcher shadowMatcher) {
@@ -265,10 +265,18 @@ public class ShadowMap {
String realClassName,
String shadowClassName,
boolean callThroughByDefault,
+ boolean callNativeMethodsByDefault,
boolean looseSignatures) {
addShadowInfo(
new ShadowInfo(
- realClassName, shadowClassName, callThroughByDefault, looseSignatures, -1, -1, null));
+ realClassName,
+ shadowClassName,
+ callThroughByDefault,
+ callNativeMethodsByDefault,
+ looseSignatures,
+ -1,
+ -1,
+ null));
return this;
}
diff --git a/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowWrangler.java b/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowWrangler.java
index 30ee4f563..1c6d8c19c 100644
--- a/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowWrangler.java
+++ b/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowWrangler.java
@@ -23,7 +23,6 @@ import javax.annotation.Nonnull;
import javax.annotation.Priority;
import org.robolectric.annotation.RealObject;
import org.robolectric.annotation.ReflectorObject;
-import org.robolectric.sandbox.NativeMethodNotFoundException;
import org.robolectric.sandbox.ShadowMatcher;
import org.robolectric.util.Function;
import org.robolectric.util.PerfStatsCollector;
@@ -183,21 +182,7 @@ public class ShadowWrangler implements ClassHandler {
} else {
RobolectricInternals.performStaticInitialization(clazz);
}
- } catch (InvocationTargetException e) {
- // Note: target exception originates from the sandbox classloader.
- // "instanceof" does not check class equality across classloaders (since they differ).
- // A simple workaround is to check the class FQCN instead.
- String nativeMethodNotFoundException = NativeMethodNotFoundException.class.getName();
-
- for (Throwable t = e.getTargetException(); t != null; ) {
- if (nativeMethodNotFoundException.equals(t.getClass().getName())) {
- throw (RuntimeException) t;
- }
-
- t = t.getCause();
- }
- throw new RuntimeException(e);
- } catch (IllegalAccessException e) {
+ } catch (InvocationTargetException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
@@ -210,7 +195,11 @@ public class ShadowWrangler implements ClassHandler {
@SuppressWarnings({"ReferenceEquality"})
@Override
public MethodHandle findShadowMethodHandle(
- Class<?> definingClass, String name, MethodType methodType, boolean isStatic)
+ Class<?> definingClass,
+ String name,
+ MethodType methodType,
+ boolean isStatic,
+ boolean isNative)
throws IllegalAccessException {
return PerfStatsCollector.getInstance()
.measure(
@@ -222,6 +211,18 @@ public class ShadowWrangler implements ClassHandler {
Method shadowMethod = pickShadowMethod(definingClass, name, paramTypes);
if (shadowMethod == CALL_REAL_CODE) {
+ ShadowInfo shadowInfo = getExactShadowInfo(definingClass);
+ if (isNative && shadowInfo != null && shadowInfo.callNativeMethodsByDefault) {
+ try {
+ Method method =
+ definingClass.getDeclaredMethod(
+ ShadowConstants.ROBO_PREFIX + name + "$nativeBinding", paramTypes);
+ method.setAccessible(true);
+ return LOOKUP.unreflect(method);
+ } catch (NoSuchMethodException e) {
+ throw new LinkageError("Missing native binding method", e);
+ }
+ }
return null;
} else if (shadowMethod == DO_NOTHING_METHOD) {
return DO_NOTHING;
diff --git a/sandbox/src/main/java/org/robolectric/sandbox/NativeMethodNotFoundException.java b/sandbox/src/main/java/org/robolectric/sandbox/NativeMethodNotFoundException.java
deleted file mode 100644
index ad04d959f..000000000
--- a/sandbox/src/main/java/org/robolectric/sandbox/NativeMethodNotFoundException.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package org.robolectric.sandbox;
-
-/**
- * Thrown when a particular Robolectric native method cannot be found.
- *
- * <p>Instrumented native methods throw this exception when the NativeCallHandler is set to
- * throw-on-native and that the dedicated method signature has not been exempted.
- */
-public class NativeMethodNotFoundException extends RuntimeException {
-
- public NativeMethodNotFoundException() {
- super();
- }
-
- public NativeMethodNotFoundException(String message) {
- super(message);
- }
-}
diff --git a/sandbox/src/test/java/org/robolectric/NativeMethodInvocationTest.java b/sandbox/src/test/java/org/robolectric/NativeMethodInvocationTest.java
new file mode 100644
index 000000000..2dcda2ae5
--- /dev/null
+++ b/sandbox/src/test/java/org/robolectric/NativeMethodInvocationTest.java
@@ -0,0 +1,33 @@
+package org.robolectric;
+
+import static org.junit.Assert.assertThrows;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.internal.Instrument;
+import org.robolectric.internal.SandboxTestRunner;
+import org.robolectric.internal.bytecode.SandboxConfig;
+
+/* Tests for native method instrumentation. */
+@RunWith(SandboxTestRunner.class)
+public class NativeMethodInvocationTest {
+ @SandboxConfig(shadows = ShadowClassWithNativeMethods.class)
+ @Test
+ public void callNativeMethodsByDefault_unsatisfiedLinkError() {
+ ClassWithNativeMethods classWithNativeMethods = new ClassWithNativeMethods();
+ assertThrows(UnsatisfiedLinkError.class, ClassWithNativeMethods::staticNativeMethod);
+ assertThrows(UnsatisfiedLinkError.class, classWithNativeMethods::instanceNativeMethod);
+ }
+
+ @Instrument
+ static class ClassWithNativeMethods {
+ static native void staticNativeMethod();
+
+ native void instanceNativeMethod();
+ }
+
+ /** Shadow for {@link NativeMethodInvocationTest.ClassWithNativeMethods} */
+ @Implements(value = ClassWithNativeMethods.class, callNativeMethodsByDefault = true)
+ public static class ShadowClassWithNativeMethods {}
+}
diff --git a/sandbox/src/test/java/org/robolectric/internal/bytecode/ClassInstrumentorTest.java b/sandbox/src/test/java/org/robolectric/internal/bytecode/ClassInstrumentorTest.java
index fa208d95d..f0f0c22a9 100644
--- a/sandbox/src/test/java/org/robolectric/internal/bytecode/ClassInstrumentorTest.java
+++ b/sandbox/src/test/java/org/robolectric/internal/bytecode/ClassInstrumentorTest.java
@@ -1,13 +1,8 @@
package org.robolectric.internal.bytecode;
import static com.google.common.truth.Truth.assertThat;
-import static java.nio.charset.StandardCharsets.UTF_8;
-import com.google.common.collect.ImmutableList;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
+import com.google.common.collect.Iterables;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -15,8 +10,11 @@ import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.MethodNode;
+import org.robolectric.shadow.api.Shadow;
/** Test for {@link ClassInstrumentor}. */
@RunWith(JUnit4.class)
@@ -39,29 +37,41 @@ public class ClassInstrumentorTest {
}
@Test
- public void instrumentNativeMethod_legacy() {
- ClassNode classNode = new ClassNode();
- classNode.name = "org/example/MyClass";
+ public void instrumentRegularMethod() {
+ ClassNode classNode = createClassWithRegularMethod();
+ MutableClass clazz =
+ new MutableClass(
+ classNode, InstrumentationConfiguration.newBuilder().build(), classNodeProvider);
+ instrumentor.instrument(clazz);
+
+ String someFunctionName = Shadow.directMethodName("org.example.MyClass", "someFunction");
+ MethodNode methodNode = findMethodNode(classNode, someFunctionName);
- MethodNode methodNode = new MethodNode();
- methodNode.access = Opcodes.ACC_PUBLIC + Opcodes.ACC_NATIVE;
- methodNode.name = "someFunction";
- methodNode.desc = "()I";
- methodNode.signature = "()";
- methodNode.exceptions = ImmutableList.of();
- methodNode.visibleAnnotations = ImmutableList.of();
+ assertThat(clazz.classNode.interfaces).contains(Type.getInternalName(ShadowedObject.class));
+ assertRoboDataField(clazz.getFields().get(0));
- classNode.methods.add(methodNode);
+ // Side effect: original method has been made private.
+ assertThat(methodNode.access & Opcodes.ACC_PRIVATE).isNotEqualTo(0);
+ // Side effect: instructions have been rewritten to return 0.
+ assertThat(methodNode.instructions).isEmpty();
+ }
+ @Test
+ public void instrumentNativeMethod_legacy() {
+ ClassNode classNode = createClassWithNativeMethod();
MutableClass clazz =
new MutableClass(
classNode, InstrumentationConfiguration.newBuilder().build(), classNodeProvider);
instrumentor.instrument(clazz);
+ String someFunctionName = Shadow.directMethodName("org.example.MyClass", "someFunction");
+ MethodNode methodNode = findMethodNode(classNode, someFunctionName);
+
+ assertThat(clazz.classNode.interfaces).contains(Type.getInternalName(ShadowedObject.class));
+ assertRoboDataField(clazz.getFields().get(0));
+
// Side effect: original method has been made private.
assertThat(methodNode.access & Opcodes.ACC_PRIVATE).isNotEqualTo(0);
- // Side effect: original method has been renamed to a robolectric delegate
- assertThat(methodNode.name).isEqualTo("$$robo$$org_example_MyClass$someFunction");
// Side effect: instructions have been rewritten to return 0.
assertThat(methodNode.instructions.size()).isEqualTo(2);
assertThat(methodNode.instructions.get(0).getOpcode()).isEqualTo(Opcodes.ICONST_0);
@@ -69,90 +79,49 @@ public class ClassInstrumentorTest {
}
@Test
- public void instrumentNativeMethod_withoutExemption_generatesThrowException() throws IOException {
- File exemptionsFile = tempFolder.newFile("natives.txt");
- try (BufferedWriter writer =
- new BufferedWriter(new FileWriter(exemptionsFile.getPath(), UTF_8))) {
- writer.write("org.example.MyClass#someOtherMethod()V\n");
- }
-
- NativeCallHandler nativeCallHandler =
- new NativeCallHandler(
- exemptionsFile, /* writeExemptions= */ false, /* throwOnNatives= */ true);
- instrumentor.setNativeCallHandler(nativeCallHandler);
-
- ClassNode classNode = new ClassNode();
- classNode.name = "org/example/MyClass";
-
- MethodNode methodNode = new MethodNode();
- methodNode.access = Opcodes.ACC_PUBLIC + Opcodes.ACC_NATIVE;
- methodNode.name = "someFunction";
- methodNode.desc = "()I";
- methodNode.signature = "()";
- methodNode.exceptions = ImmutableList.of();
- methodNode.visibleAnnotations = ImmutableList.of();
-
- classNode.methods.add(methodNode);
-
+ public void instrumentNativeMethod_generatesNativeBindingMethod() {
+ ClassNode classNode = createClassWithNativeMethod();
MutableClass clazz =
new MutableClass(
classNode, InstrumentationConfiguration.newBuilder().build(), classNodeProvider);
instrumentor.instrument(clazz);
- // Side effect: original method has been made private.
+ String nativeMethodName = Shadow.directNativeMethodName("org.example.MyClass", "someFunction");
+ MethodNode methodNode = findMethodNode(classNode, nativeMethodName);
+
+ assertThat(clazz.classNode.interfaces).contains(Type.getInternalName(ShadowedObject.class));
+ assertRoboDataField(clazz.getFields().get(0));
+
+ assertThat(methodNode.access & Opcodes.ACC_NATIVE).isNotEqualTo(0);
assertThat(methodNode.access & Opcodes.ACC_PRIVATE).isNotEqualTo(0);
- // Side effect: original method has been renamed to a robolectric delegate
- assertThat(methodNode.name).isEqualTo("$$robo$$org_example_MyClass$someFunction");
- // Side effect: instructions have been rewritten to throw and return.
- assertThat(methodNode.instructions.size()).isEqualTo(7);
- assertThat(methodNode.instructions.get(0).getOpcode()).isEqualTo(Opcodes.NEW);
- assertThat(methodNode.instructions.get(1).getOpcode()).isEqualTo(Opcodes.DUP);
- assertThat(methodNode.instructions.get(2).getOpcode()).isEqualTo(Opcodes.LDC);
- assertThat(methodNode.instructions.get(3).getOpcode()).isEqualTo(Opcodes.INVOKESPECIAL);
- assertThat(methodNode.instructions.get(4).getOpcode()).isEqualTo(Opcodes.ATHROW);
- assertThat(methodNode.instructions.get(5).getOpcode()).isEqualTo(Opcodes.ICONST_0);
- assertThat(methodNode.instructions.get(6).getOpcode()).isEqualTo(Opcodes.IRETURN);
+ assertThat(methodNode.access & Opcodes.ACC_SYNTHETIC).isNotEqualTo(0);
}
- @Test
- public void instrumentNativeMethod_withExemption_generatesNoOpReturn() throws IOException {
- File exemptionsFile = tempFolder.newFile("natives.txt");
- try (BufferedWriter writer =
- new BufferedWriter(new FileWriter(exemptionsFile.getPath(), UTF_8))) {
- writer.write("org.example.MyClass#someOtherMethod()V\n");
- writer.write("org.example.MyClass#someFunction()I\n");
- }
-
- NativeCallHandler nativeCallHandler =
- new NativeCallHandler(
- exemptionsFile, /* writeExemptions= */ false, /* throwOnNatives= */ true);
- instrumentor.setNativeCallHandler(nativeCallHandler);
-
+ private static ClassNode createClassWithRegularMethod() {
ClassNode classNode = new ClassNode();
classNode.name = "org/example/MyClass";
+ classNode.methods.add(new MethodNode(Opcodes.ACC_PUBLIC, "someFunction", "()I", null, null));
+ return classNode;
+ }
- MethodNode methodNode = new MethodNode();
- methodNode.access = Opcodes.ACC_PUBLIC + Opcodes.ACC_NATIVE;
- methodNode.name = "someFunction";
- methodNode.desc = "()I";
- methodNode.signature = "()";
- methodNode.exceptions = ImmutableList.of();
- methodNode.visibleAnnotations = ImmutableList.of();
-
- classNode.methods.add(methodNode);
+ private static ClassNode createClassWithNativeMethod() {
+ ClassNode classNode = new ClassNode();
+ classNode.name = "org/example/MyClass";
+ classNode.methods.add(
+ new MethodNode(Opcodes.ACC_PUBLIC + Opcodes.ACC_NATIVE, "someFunction", "()I", null, null));
+ return classNode;
+ }
- MutableClass clazz =
- new MutableClass(
- classNode, InstrumentationConfiguration.newBuilder().build(), classNodeProvider);
- instrumentor.instrument(clazz);
+ private static MethodNode findMethodNode(ClassNode classNode, String name) {
+ return Iterables.find(classNode.methods, input -> input.name.equals(name));
+ }
- // Side effect: original method has been made private.
- assertThat(methodNode.access & Opcodes.ACC_PRIVATE).isNotEqualTo(0);
- // Side effect: original method has been renamed to a robolectric delegate
- assertThat(methodNode.name).isEqualTo("$$robo$$org_example_MyClass$someFunction");
- // Side effect: instructions have been rewritten to return 0.
- assertThat(methodNode.instructions.size()).isEqualTo(2);
- assertThat(methodNode.instructions.get(0).getOpcode()).isEqualTo(Opcodes.ICONST_0);
- assertThat(methodNode.instructions.get(1).getOpcode()).isEqualTo(Opcodes.IRETURN);
+ private static void assertRoboDataField(FieldNode fieldNode) {
+ assertThat(fieldNode.access)
+ .isEqualTo(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_TRANSIENT);
+ assertThat(fieldNode.name).isEqualTo(ShadowConstants.CLASS_HANDLER_DATA_FIELD_NAME);
+ assertThat(fieldNode.desc).isEqualTo(Type.getDescriptor(Object.class));
+ assertThat(fieldNode.signature).isEqualTo(Type.getDescriptor(Object.class));
+ assertThat(fieldNode.value).isNull();
}
}
diff --git a/sandbox/src/test/java/org/robolectric/internal/bytecode/NativeCallHandlerTest.java b/sandbox/src/test/java/org/robolectric/internal/bytecode/NativeCallHandlerTest.java
deleted file mode 100644
index 04035346e..000000000
--- a/sandbox/src/test/java/org/robolectric/internal/bytecode/NativeCallHandlerTest.java
+++ /dev/null
@@ -1,218 +0,0 @@
-package org.robolectric.internal.bytecode;
-
-import static com.google.common.truth.Truth.assertThat;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import com.google.common.io.Files;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/** Test for {@link NativeCallHandler}. */
-@RunWith(JUnit4.class)
-public class NativeCallHandlerTest {
- @Rule public TemporaryFolder tempFolder = new TemporaryFolder();
-
- @Test
- public void jarInstrumentorLegacyUsage() throws IOException {
- // CUJ: Legacy jarInstrumentor usage; there is no exemption file, native methods do not throw.
-
- File exemptionsFile = tempFolder.newFile("natives.txt");
- assertThat(exemptionsFile.delete()).isTrue();
-
- // Create handler, which loads exemptions from file. It's fine for the file to be missing.
- NativeCallHandler handler =
- new NativeCallHandler(
- exemptionsFile, /* writeExemptions= */ false, /* throwOnNatives= */ false);
-
- // No method descriptor should throw.
- assertThat(handler.shouldThrow("org.example.MyClass#someOtherMethod()V")).isFalse();
- assertThat(handler.shouldThrow("org.example.MyClass#someOtherMethod(II)V")).isFalse();
- }
-
- @Test
- public void jarInstrumentorUsage_throwOnNativesEnabled() throws IOException {
- // CUJ: jarInstrumentor usage with an exemption list and non-exempted native methods should
- // throw.
-
- File exemptionsFile = tempFolder.newFile("natives.txt");
- try (BufferedWriter writer =
- new BufferedWriter(new FileWriter(exemptionsFile.getPath(), UTF_8))) {
- writer.write("android.app.ActivityThread#dumpGraphicsInfo(Ljava/io/FileDescriptor;)V\n");
- writer.write("libcore.io.Linux#chmod(Ljava/lang/String;I)V\n");
- writer.write("libcore.io.Linux#fchmod(Ljava/io/FileDescriptor;I)V\n");
- writer.write("android.graphics.fonts.Font^Builder#nAddAxis(JIF)V\n");
- writer.write("org.example.MyClass#someOtherMethod()V\n");
- // empty or white-space lines are ignored
- writer.write("\n");
- writer.write(" \t \n");
- // A # prefix denotes a comment and is ignored too
- writer.write("# org.example.Ignored#comment()V\n");
- writer.write(" # org.example.Ignored#thisIsACommentToo()V \n");
- }
-
- // Create handler, which loads exemptions from file. ThrowOnNatives is enabled.
- NativeCallHandler handler =
- new NativeCallHandler(
- exemptionsFile, /* writeExemptions= */ false, /* throwOnNatives= */ true);
-
- // Test exempted methods
- assertThat(handler.shouldThrow("org.example.MyClass#someOtherMethod()V")).isFalse();
-
- // Test non-exempted methods
- assertThat(handler.shouldThrow("org.example.MyClass#someOtherMethod(II)V")).isTrue();
-
- // Empty lines and comments are ignored and not present in the exemption list.
- assertThat(handler.shouldThrow("")).isTrue();
- assertThat(handler.shouldThrow(" \t ")).isTrue();
- assertThat(handler.shouldThrow("# org.example.Ignored#comment()V")).isTrue();
- assertThat(handler.shouldThrow(" # org.example.Ignored#thisIsACommentToo()V ")).isTrue();
- }
-
- @Test
- public void jarInstrumentorUsage_throwOnNativesDisabled() throws IOException {
- // CUJ: jarInstrumentor usage with an exemption list and non-exempted native methods should
- // throw.
-
- File exemptionsFile = tempFolder.newFile("natives.txt");
- try (BufferedWriter writer =
- new BufferedWriter(new FileWriter(exemptionsFile.getPath(), UTF_8))) {
- writer.write("android.app.ActivityThread#dumpGraphicsInfo(Ljava/io/FileDescriptor;)V\n");
- writer.write("libcore.io.Linux#chmod(Ljava/lang/String;I)V\n");
- writer.write("libcore.io.Linux#fchmod(Ljava/io/FileDescriptor;I)V\n");
- writer.write("android.graphics.fonts.Font^Builder#nAddAxis(JIF)V\n");
- writer.write("org.example.MyClass#someOtherMethod()V\n");
- }
-
- // Create handler, which loads exemptions from file. ThrowOnNatives is disabled.
- NativeCallHandler handler =
- new NativeCallHandler(
- exemptionsFile, /* writeExemptions= */ false, /* throwOnNatives= */ false);
-
- // Test exempted methods
- assertThat(handler.shouldThrow("org.example.MyClass#someOtherMethod()V")).isFalse();
-
- // Test non-exempted methods
- assertThat(handler.shouldThrow("org.example.MyClass#someOtherMethod(II)V")).isFalse();
- }
-
- @Test
- public void jarInstrumentorUsage_logNativeCall_ignored() throws IOException {
- // When not writing the exemption list, logNativeCall calls are no-op.
-
- File exemptionsFile = tempFolder.newFile("natives.txt");
-
- // Create handler, which loads exemptions from file. ThrowOnNatives is enabled.
- NativeCallHandler handler =
- new NativeCallHandler(
- exemptionsFile, /* writeExemptions= */ false, /* throwOnNatives= */ true);
-
- // No methods are exempted -- initial list is empty.
- assertThat(handler.shouldThrow("org.example.MyClass#someOtherMethod()V")).isTrue();
- assertThat(handler.shouldThrow("android.graphics.fonts.Font$Builder#nAddAxis(JIF)V")).isTrue();
-
- handler.logNativeCall("org.example.MyClass#someOtherMethod()V");
- handler.logNativeCall("android.graphics.fonts.Font$Builder#nAddAxis(JIF)V");
-
- // LogNativeCall did not capture. These methods are still not exempted.
- assertThat(handler.shouldThrow("org.example.MyClass#someOtherMethod()V")).isTrue();
- assertThat(handler.shouldThrow("android.graphics.fonts.Font$Builder#nAddAxis(JIF)V")).isTrue();
- }
-
- @Test
- public void exemptionListGeneratorUsage_logNativeCall_capturesCalls() throws IOException {
- // CUJ: jarInstrumentor called to generate the exemption list.
-
- File exemptionsFile = tempFolder.newFile("natives.txt");
-
- // Create handler, which loads exemptions from file. ThrowOnNatives is enabled.
- NativeCallHandler handler =
- new NativeCallHandler(
- exemptionsFile, /* writeExemptions= */ true, /* throwOnNatives= */ true);
-
- // No methods are exempted -- initial list is empty.
- assertThat(handler.shouldThrow("org.example.MyClass#someOtherMethod()V")).isTrue();
- assertThat(handler.shouldThrow("android.graphics.fonts.Font$Builder#nAddAxis(JIF)V")).isTrue();
-
- handler.logNativeCall("org.example.MyClass#someOtherMethod()V");
- handler.logNativeCall("android.graphics.fonts.Font$Builder#nAddAxis(JIF)V");
-
- // These methods are now exempted.
- assertThat(handler.shouldThrow("org.example.MyClass#someOtherMethod()V")).isFalse();
- assertThat(handler.shouldThrow("android.graphics.fonts.Font$Builder#nAddAxis(JIF)V")).isFalse();
- }
-
- @Test
- public void exemptionListGeneratorUsage_writeExemptionFile() throws IOException {
- // CUJ: jarInstrumentor called to generate the exemption list.
-
- File exemptionsFile = tempFolder.newFile("natives.txt");
- try (BufferedWriter writer =
- new BufferedWriter(new FileWriter(exemptionsFile.getPath(), UTF_8))) {
- writer.write("android.app.ActivityThread#dumpGraphicsInfo(Ljava/io/FileDescriptor;)V\n");
- writer.write("libcore.io.Linux#fchmod(Ljava/io/FileDescriptor;I)V\n");
- }
-
- // Create handler, which loads exemptions from file. ThrowOnNatives is disabled.
- NativeCallHandler handler =
- new NativeCallHandler(
- exemptionsFile, /* writeExemptions= */ true, /* throwOnNatives= */ false);
-
- handler.logNativeCall("org.example.MyClass#someOtherMethod()V");
- // Multiple calls with same value are idempotent.
- handler.logNativeCall("org.example.MyClass#someOtherMethod(I)V");
- handler.logNativeCall("org.example.MyClass#someOtherMethod(I)V");
- handler.logNativeCall("org.example.MyClass#someOtherMethod(I)V");
- handler.logNativeCall("org.example.MyClass#someOtherMethod(II)V");
- handler.logNativeCall("libcore.io.Linux#chmod(Ljava/lang/String;I)V");
- // Case of a nested class with $ in the FQCN.
- handler.logNativeCall("android.graphics.fonts.Font$Builder#nAddAxis(JIF)V");
-
- handler.writeExemptionsList();
-
- // Note: due to how the generated files are manipulated in the shell/makefile build system,
- // '$' characters are a problem and would need to be escaped (and potentially differently for
- // shell vs makefiles). The workaround is to have '$' rewritten as '^'.
-
- assertThat(Files.asCharSource(exemptionsFile, UTF_8).read())
- .isEqualTo(
- "android.app.ActivityThread#dumpGraphicsInfo(Ljava/io/FileDescriptor;)V\n"
- // Font$Builder gets written as Font^Builder.
- + "android.graphics.fonts.Font^Builder#nAddAxis(JIF)V\n"
- + "libcore.io.Linux#chmod(Ljava/lang/String;I)V\n"
- + "libcore.io.Linux#fchmod(Ljava/io/FileDescriptor;I)V\n"
- + "org.example.MyClass#someOtherMethod()V\n"
- + "org.example.MyClass#someOtherMethod(I)V\n"
- + "org.example.MyClass#someOtherMethod(II)V\n");
- }
-
- @Test
- public void getExceptionMessage() throws IOException {
- File exemptionsFile = tempFolder.newFile("natives.txt");
- NativeCallHandler handler =
- new NativeCallHandler(
- exemptionsFile, /* writeExemptions= */ false, /* throwOnNatives= */ true);
-
- // Test generated exception message for non-exempted methods.
- assertThat(
- handler.getExceptionMessage(
- "org.example.MyClass$1#someOtherMethod(II)V",
- "org.example.MyClass$1",
- "someOtherMethod"))
- .isEqualTo(
- "Unexpected Robolectric native method call to"
- + " 'org.example.MyClass$1#someOtherMethod()'.\n"
- + "Option 1: If customizing this method is useful, add an implementation in"
- + " ShadowMyClass.java.\n"
- + "Option 2: If this method just needs to trivially return 0 or null, please add an"
- + " exemption entry for\n"
- + " org.example.MyClass^1#someOtherMethod(II)V\n"
- + "to exemption file natives.txt");
- }
-}
diff --git a/sandbox/src/test/java/org/robolectric/internal/bytecode/SandboxClassLoaderTest.java b/sandbox/src/test/java/org/robolectric/internal/bytecode/SandboxClassLoaderTest.java
index 5c9411ce0..84b97ce8a 100644
--- a/sandbox/src/test/java/org/robolectric/internal/bytecode/SandboxClassLoaderTest.java
+++ b/sandbox/src/test/java/org/robolectric/internal/bytecode/SandboxClassLoaderTest.java
@@ -147,6 +147,7 @@ public class SandboxClassLoaderTest {
Field roboDataField = exampleClass.getField(ShadowConstants.CLASS_HANDLER_DATA_FIELD_NAME);
assertNotNull(roboDataField);
assertThat(Modifier.isPublic(roboDataField.getModifiers())).isTrue();
+ assertThat(Modifier.isTransient(roboDataField.getModifiers())).isTrue();
// Java 9 doesn't allow updates to final fields from outside <init> or <clinit>:
// https://bugs.openjdk.java.net/browse/JDK-8157181
@@ -625,7 +626,7 @@ public class SandboxClassLoaderTest {
@Override
public MethodHandle findShadowMethodHandle(
- Class<?> theClass, String name, MethodType type, boolean isStatic)
+ Class<?> theClass, String name, MethodType type, boolean isStatic, boolean isNative)
throws IllegalAccessException {
String signature = getSignature(theClass, name, type, isStatic);
InvocationProfile invocationProfile =
diff --git a/scripts/install-android-prebuilt.sh b/scripts/install-android-prebuilt.sh
index 443688137..555ef054d 100755
--- a/scripts/install-android-prebuilt.sh
+++ b/scripts/install-android-prebuilt.sh
@@ -1,4 +1,3 @@
-@@ -0,0 1,85 @@
#!/bin/bash
#
# This script signs already built AOSP Android jars, and installs them in your local
@@ -20,11 +19,6 @@ if [[ $# -ne 3 ]]; then
exit 1
fi
-read -p "Please set the GPG passphrase: " -s signingPassphrase
-if [[ -z "${signingPassphrase}" ]]; then
- exit 1
-fi
-
JAR_DIR=$(readlink -e "$1")
ANDROID_VERSION="$2"
ROBOLECTRIC_SUB_VERSION="$3"
@@ -53,7 +47,7 @@ build_signed_packages() {
echo "Robolectric: Signing files with gpg..."
for ext in ".jar" "-javadoc.jar" "-sources.jar" ".pom"; do
- ( cd ${JAR_DIR} && gpg -ab --passphrase ${signingPassphrase} android-all-${ROBOLECTRIC_VERSION}$ext )
+ ( cd ${JAR_DIR} && gpg -ab android-all-${ROBOLECTRIC_VERSION}$ext )
done
echo "Robolectric: Creating bundle for Sonatype upload..."
@@ -90,4 +84,4 @@ generate_empty_javadoc
build_signed_packages
mavenize
-echo "DONE!!" \ No newline at end of file
+echo "DONE!!"
diff --git a/settings.gradle b/settings.gradle
index 37692707e..1d2e82c32 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -6,7 +6,6 @@ include ":junit"
include ":utils"
include ":utils:reflector"
include ":pluginapi"
-include ":plugins:accessibility-deprecated"
include ":plugins:maven-dependency-resolver"
include ":preinstrumented"
include ":processor"
@@ -30,6 +29,7 @@ include ":integration_tests:mockito"
include ":integration_tests:mockito-kotlin"
include ":integration_tests:mockito-experimental"
include ":integration_tests:powermock"
+include ":integration_tests:roborazzi"
include ':integration_tests:androidx'
include ':integration_tests:androidx_test'
include ':integration_tests:ctesque'
diff --git a/shadowapi/src/main/java/org/robolectric/internal/IShadow.java b/shadowapi/src/main/java/org/robolectric/internal/IShadow.java
index d46cbe742..3783164c0 100644
--- a/shadowapi/src/main/java/org/robolectric/internal/IShadow.java
+++ b/shadowapi/src/main/java/org/robolectric/internal/IShadow.java
@@ -8,7 +8,7 @@ public interface IShadow {
<T> T newInstanceOf(Class<T> clazz);
- <T> T newInstance(Class<T> clazz, Class[] parameterTypes, Object[] params);
+ <T> T newInstance(Class<T> clazz, Class<?>[] parameterTypes, Object[] params);
/**
* Returns a proxy object that invokes the original $$robo$$-prefixed methods for {@code
@@ -21,16 +21,27 @@ public interface IShadow {
@Deprecated
<T> T directlyOn(T shadowedObject, Class<T> clazz);
- @SuppressWarnings("unchecked")
- <R> R directlyOn(Object shadowedObject, String clazzName, String methodName, ReflectionHelpers.ClassParameter... paramValues);
+ <R> R directlyOn(
+ Object shadowedObject,
+ String clazzName,
+ String methodName,
+ ReflectionHelpers.ClassParameter<?>... paramValues);
- <R, T> R directlyOn(T shadowedObject, Class<T> clazz, String methodName, ReflectionHelpers.ClassParameter... paramValues);
+ <R, T> R directlyOn(
+ T shadowedObject,
+ Class<T> clazz,
+ String methodName,
+ ReflectionHelpers.ClassParameter<?>... paramValues);
- <R, T> R directlyOn(Class<T> clazz, String methodName, ReflectionHelpers.ClassParameter... paramValues);
+ <R, T> R directlyOn(
+ Class<T> clazz, String methodName, ReflectionHelpers.ClassParameter<?>... paramValues);
- <R> R invokeConstructor(Class<? extends R> clazz, R instance, ReflectionHelpers.ClassParameter... paramValues);
+ <R> R invokeConstructor(
+ Class<? extends R> clazz, R instance, ReflectionHelpers.ClassParameter<?>... paramValues);
String directMethodName(String className, String methodName);
+ String directNativeMethodName(String className, String methodName);
+
void directInitialize(Class<?> clazz);
}
diff --git a/shadowapi/src/main/java/org/robolectric/shadow/api/Shadow.java b/shadowapi/src/main/java/org/robolectric/shadow/api/Shadow.java
index 7c01b898b..02ba581a1 100644
--- a/shadowapi/src/main/java/org/robolectric/shadow/api/Shadow.java
+++ b/shadowapi/src/main/java/org/robolectric/shadow/api/Shadow.java
@@ -79,6 +79,10 @@ public class Shadow {
return SHADOW_IMPL.directMethodName(className, methodName);
}
+ public static String directNativeMethodName(String className, String methodName) {
+ return SHADOW_IMPL.directNativeMethodName(className, methodName);
+ }
+
public static void directInitialize(Class<?> clazz) {
SHADOW_IMPL.directInitialize(clazz);
}
diff --git a/shadowapi/src/main/java/org/robolectric/util/ReflectionHelpers.java b/shadowapi/src/main/java/org/robolectric/util/ReflectionHelpers.java
index 8ae639971..d32920217 100644
--- a/shadowapi/src/main/java/org/robolectric/util/ReflectionHelpers.java
+++ b/shadowapi/src/main/java/org/robolectric/util/ReflectionHelpers.java
@@ -522,23 +522,4 @@ public class ReflectionHelpers {
return values;
}
}
-
- /**
- * String parameter used with reflective method calls.
- *
- * @param <V> The value of the method parameter.
- */
- public static class StringParameter<V> {
- public final String className;
- public final V value;
-
- public StringParameter(String className, V value) {
- this.className = className;
- this.value = value;
- }
-
- public static <V> StringParameter<V> from(String className, V value) {
- return new StringParameter<>(className, value);
- }
- }
}
diff --git a/shadows/framework/build.gradle b/shadows/framework/build.gradle
index 57d2679d6..74409a64f 100644
--- a/shadows/framework/build.gradle
+++ b/shadows/framework/build.gradle
@@ -62,7 +62,6 @@ dependencies {
api libs.sqlite4java
compileOnly(AndroidSdk.MAX_SDK.coordinates)
api libs.icu4j
- api libs.androidx.annotation
api libs.auto.value.annotations
annotationProcessor libs.auto.value
diff --git a/shadows/framework/src/main/java/android/webkit/RoboCookieManager.java b/shadows/framework/src/main/java/android/webkit/RoboCookieManager.java
index b9626e882..e867a2fac 100644
--- a/shadows/framework/src/main/java/android/webkit/RoboCookieManager.java
+++ b/shadows/framework/src/main/java/android/webkit/RoboCookieManager.java
@@ -1,8 +1,8 @@
package android.webkit;
import java.io.UnsupportedEncodingException;
-import java.net.URI;
-import java.net.URISyntaxException;
+import java.net.MalformedURLException;
+import java.net.URL;
import java.net.URLDecoder;
import java.text.DateFormat;
import java.text.ParseException;
@@ -242,8 +242,8 @@ public class RoboCookieManager extends CookieManager {
}
try {
- return new URI(url).getHost();
- } catch (URISyntaxException e) {
+ return new URL(url).getHost();
+ } catch (MalformedURLException e) {
throw new IllegalArgumentException("wrong URL : " + url, e);
}
}
diff --git a/shadows/framework/src/main/java/org/robolectric/RuntimeEnvironment.java b/shadows/framework/src/main/java/org/robolectric/RuntimeEnvironment.java
index cc7c1d457..a728c859b 100644
--- a/shadows/framework/src/main/java/org/robolectric/RuntimeEnvironment.java
+++ b/shadows/framework/src/main/java/org/robolectric/RuntimeEnvironment.java
@@ -1,7 +1,5 @@
package org.robolectric;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static org.robolectric.annotation.LooperMode.Mode.LEGACY;
import static org.robolectric.shadows.ShadowLooper.assertLooperMode;
@@ -12,7 +10,6 @@ import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
-import android.os.Build;
import android.util.DisplayMetrics;
import android.view.Display;
import com.google.common.base.Supplier;
@@ -197,9 +194,7 @@ public class RuntimeEnvironment {
* @param newQualifiers the qualifiers to apply
*/
public static void setQualifiers(String newQualifiers) {
- if (getApiLevel() >= JELLY_BEAN_MR1) {
- ShadowDisplayManager.changeDisplay(Display.DEFAULT_DISPLAY, newQualifiers);
- }
+ ShadowDisplayManager.changeDisplay(Display.DEFAULT_DISPLAY, newQualifiers);
Configuration configuration;
DisplayMetrics displayMetrics = new DisplayMetrics();
@@ -238,8 +233,7 @@ public class RuntimeEnvironment {
Configuration configuration, DisplayMetrics displayMetrics) {
// Update the resources last so that listeners will have a consistent environment.
// TODO(paulsowden): Can we call ResourcesManager.getInstance().applyConfigurationToResources()?
- if (Build.VERSION.SDK_INT >= KITKAT
- && ResourcesManager.getInstance().getConfiguration() != null) {
+ if (ResourcesManager.getInstance().getConfiguration() != null) {
ResourcesManager.getInstance().getConfiguration().updateFrom(configuration);
}
Resources.getSystem().updateConfiguration(configuration, displayMetrics);
diff --git a/shadows/framework/src/main/java/org/robolectric/android/Bootstrap.java b/shadows/framework/src/main/java/org/robolectric/android/Bootstrap.java
index 1ed8d2172..97d9ce294 100644
--- a/shadows/framework/src/main/java/org/robolectric/android/Bootstrap.java
+++ b/shadows/framework/src/main/java/org/robolectric/android/Bootstrap.java
@@ -3,14 +3,10 @@ package org.robolectric.android;
import android.content.res.AssetManager;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.os.Build;
-import android.os.Build.VERSION_CODES;
import android.util.DisplayMetrics;
-import org.robolectric.RuntimeEnvironment;
import org.robolectric.res.Qualifiers;
import org.robolectric.shadows.ShadowDateUtils;
import org.robolectric.shadows.ShadowDisplayManager;
-import org.robolectric.shadows.ShadowWindowManagerImpl;
public class Bootstrap {
@@ -20,6 +16,20 @@ public class Bootstrap {
/** internal only */
public static boolean displaySet = false;
+ public static Configuration getConfiguration() {
+ if (displayResources != null) {
+ return displayResources.getConfiguration();
+ }
+ return Bootstrap.configuration;
+ }
+
+ public static DisplayMetrics getDisplayMetrics() {
+ if (displayResources != null) {
+ return displayResources.getDisplayMetrics();
+ }
+ return Bootstrap.displayMetrics;
+ }
+
/** internal only */
public static void setDisplayConfiguration(
Configuration configuration, DisplayMetrics displayMetrics) {
@@ -47,24 +57,10 @@ public class Bootstrap {
}
/** internal only */
- public static void updateConfiguration(Resources resources) {
- if (displayResources == null) {
- resources.updateConfiguration(Bootstrap.configuration, Bootstrap.displayMetrics);
- } else {
- resources.updateConfiguration(
- displayResources.getConfiguration(), displayResources.getDisplayMetrics());
- }
- }
-
- /** internal only */
public static void setUpDisplay() {
if (!displaySet) {
displaySet = true;
- if (Build.VERSION.SDK_INT == VERSION_CODES.JELLY_BEAN) {
- ShadowWindowManagerImpl.configureDefaultDisplayForJBOnly(configuration, displayMetrics);
- } else {
- ShadowDisplayManager.configureDefaultDisplay(configuration, displayMetrics);
- }
+ ShadowDisplayManager.configureDefaultDisplay(configuration, displayMetrics);
}
}
@@ -101,20 +97,7 @@ public class Bootstrap {
DeviceConfig.applyRules(configuration, displayMetrics, apiLevel);
- fixJellyBean(configuration, displayMetrics);
-
// DateUtils has a static cache of the last Configuration, so it may need to be reset.
ShadowDateUtils.resetLastConfig();
}
-
- private static void fixJellyBean(Configuration configuration, DisplayMetrics displayMetrics) {
- if (RuntimeEnvironment.getApiLevel() < Build.VERSION_CODES.KITKAT) {
- int widthPx = (int) (configuration.screenWidthDp * displayMetrics.density);
- int heightPx = (int) (configuration.screenHeightDp * displayMetrics.density);
- displayMetrics.widthPixels = displayMetrics.noncompatWidthPixels = widthPx;
- displayMetrics.heightPixels = displayMetrics.noncompatHeightPixels = heightPx;
- displayMetrics.xdpi = displayMetrics.noncompatXdpi = displayMetrics.densityDpi;
- displayMetrics.ydpi = displayMetrics.noncompatYdpi = displayMetrics.densityDpi;
- }
- }
}
diff --git a/shadows/framework/src/main/java/org/robolectric/android/ConfigurationV25.java b/shadows/framework/src/main/java/org/robolectric/android/ConfigurationV25.java
index d92298a8e..da0c0f8ea 100644
--- a/shadows/framework/src/main/java/org/robolectric/android/ConfigurationV25.java
+++ b/shadows/framework/src/main/java/org/robolectric/android/ConfigurationV25.java
@@ -221,12 +221,7 @@ public class ConfigurationV25 {
break;
}
- int densityDpi;
- if (RuntimeEnvironment.getApiLevel() > VERSION_CODES.JELLY_BEAN) {
- densityDpi = config.densityDpi;
- } else {
- densityDpi = displayMetrics.densityDpi;
- }
+ int densityDpi = config.densityDpi;
switch (densityDpi) {
case DENSITY_DPI_UNDEFINED:
diff --git a/shadows/framework/src/main/java/org/robolectric/android/DeviceConfig.java b/shadows/framework/src/main/java/org/robolectric/android/DeviceConfig.java
index 6ca8a43f7..2b1bd365b 100644
--- a/shadows/framework/src/main/java/org/robolectric/android/DeviceConfig.java
+++ b/shadows/framework/src/main/java/org/robolectric/android/DeviceConfig.java
@@ -10,7 +10,6 @@ import android.os.Build.VERSION_CODES;
import android.util.DisplayMetrics;
import java.util.Locale;
import org.robolectric.res.Qualifiers;
-import org.robolectric.res.android.ConfigDescription;
import org.robolectric.res.android.ResTable_config;
import org.robolectric.util.ReflectionHelpers;
@@ -165,7 +164,7 @@ public class DeviceConfig {
.build();
}
if (locale != null) {
- setLocale(apiLevel, configuration, locale);
+ configuration.setLocale(locale);
}
if (resTab.smallestScreenWidthDp != 0) {
@@ -236,9 +235,7 @@ public class DeviceConfig {
private static void setDensity(int densityDpi, int apiLevel, Configuration configuration,
DisplayMetrics displayMetrics) {
- if (apiLevel >= VERSION_CODES.JELLY_BEAN_MR1) {
- configuration.densityDpi = densityDpi;
- }
+ configuration.densityDpi = densityDpi;
displayMetrics.densityDpi = densityDpi;
displayMetrics.density = displayMetrics.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
@@ -278,12 +275,7 @@ public class DeviceConfig {
}
locale = new Locale(language, country);
- setLocale(apiLevel, configuration, locale);
- }
-
- if (apiLevel <= ConfigDescription.SDK_JELLY_BEAN &&
- getScreenLayoutLayoutDir(configuration) == Configuration.SCREENLAYOUT_LAYOUTDIR_UNDEFINED) {
- setScreenLayoutLayoutDir(configuration, Configuration.SCREENLAYOUT_LAYOUTDIR_LTR);
+ configuration.setLocale(locale);
}
ScreenSize requestedScreenSize = getScreenSize(configuration);
@@ -409,14 +401,6 @@ public class DeviceConfig {
configuration.screenHeightDp = oldWidth;
}
- private static void setLocale(int apiLevel, Configuration configuration, Locale locale) {
- if (apiLevel >= VERSION_CODES.JELLY_BEAN_MR1) {
- configuration.setLocale(locale);
- } else {
- configuration.locale = locale;
- }
- }
-
private static Locale getLocale(Configuration configuration, int apiLevel) {
Locale locale;
if (apiLevel > Build.VERSION_CODES.M) {
diff --git a/shadows/framework/src/main/java/org/robolectric/android/internal/DisplayConfig.java b/shadows/framework/src/main/java/org/robolectric/android/internal/DisplayConfig.java
index 7d07118d6..22368c241 100644
--- a/shadows/framework/src/main/java/org/robolectric/android/internal/DisplayConfig.java
+++ b/shadows/framework/src/main/java/org/robolectric/android/internal/DisplayConfig.java
@@ -1,6 +1,5 @@
package org.robolectric.android.internal;
-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;
@@ -238,10 +237,8 @@ public final class DisplayConfig {
presentationDeadlineNanos = other.presentationDeadlineNanos;
state = other.state;
}
- if (RuntimeEnvironment.getApiLevel() >= KITKAT) {
- ownerUid = other.ownerUid;
- ownerPackageName = other.ownerPackageName;
- }
+ ownerUid = other.ownerUid;
+ ownerPackageName = other.ownerPackageName;
if (RuntimeEnvironment.getApiLevel() >= O) {
removeMode = other.removeMode;
}
@@ -372,10 +369,8 @@ public final class DisplayConfig {
other.presentationDeadlineNanos = presentationDeadlineNanos;
other.state = state;
}
- if (RuntimeEnvironment.getApiLevel() >= KITKAT) {
- other.ownerUid = ownerUid;
- other.ownerPackageName = ownerPackageName;
- }
+ other.ownerUid = ownerUid;
+ other.ownerPackageName = ownerPackageName;
if (RuntimeEnvironment.getApiLevel() >= O) {
other.removeMode = removeMode;
}
diff --git a/shadows/framework/src/main/java/org/robolectric/android/util/concurrent/InlineExecutorService.java b/shadows/framework/src/main/java/org/robolectric/android/util/concurrent/InlineExecutorService.java
index 255130368..5294c4523 100644
--- a/shadows/framework/src/main/java/org/robolectric/android/util/concurrent/InlineExecutorService.java
+++ b/shadows/framework/src/main/java/org/robolectric/android/util/concurrent/InlineExecutorService.java
@@ -1,6 +1,6 @@
package org.robolectric.android.util.concurrent;
-import androidx.annotation.NonNull;
+import android.annotation.NonNull;
import com.google.common.annotations.Beta;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.Collection;
diff --git a/shadows/framework/src/main/java/org/robolectric/android/util/concurrent/PausedExecutorService.java b/shadows/framework/src/main/java/org/robolectric/android/util/concurrent/PausedExecutorService.java
index cd16c11d9..bb540577d 100644
--- a/shadows/framework/src/main/java/org/robolectric/android/util/concurrent/PausedExecutorService.java
+++ b/shadows/framework/src/main/java/org/robolectric/android/util/concurrent/PausedExecutorService.java
@@ -1,6 +1,6 @@
package org.robolectric.android.util.concurrent;
-import androidx.annotation.NonNull;
+import android.annotation.NonNull;
import com.google.common.annotations.Beta;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.AbstractFuture;
diff --git a/shadows/framework/src/main/java/org/robolectric/fakes/RoboSplashScreen.java b/shadows/framework/src/main/java/org/robolectric/fakes/RoboSplashScreen.java
index 915834d59..93b30dbbd 100644
--- a/shadows/framework/src/main/java/org/robolectric/fakes/RoboSplashScreen.java
+++ b/shadows/framework/src/main/java/org/robolectric/fakes/RoboSplashScreen.java
@@ -1,9 +1,9 @@
package org.robolectric.fakes;
+import android.annotation.RequiresApi;
import android.annotation.StyleRes;
import android.os.Build;
import android.window.SplashScreen;
-import androidx.annotation.RequiresApi;
/** Robolectric implementation of {@link android.window.SplashScreen}. */
@RequiresApi(api = Build.VERSION_CODES.S)
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/AssociationInfoBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/AssociationInfoBuilder.java
index 2c56e50ab..5750126f3 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/AssociationInfoBuilder.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/AssociationInfoBuilder.java
@@ -25,6 +25,7 @@ public class AssociationInfoBuilder {
// We have two different constructors for AssociationInfo across
// T branches. aosp has the constructor that takes a new "revoked" parameter.
private boolean revoked;
+ private boolean pending;
private long lastTimeConnectedMs;
private int systemDataSyncFlags;
@@ -93,7 +94,7 @@ public class AssociationInfoBuilder {
this.revoked = revoked;
return this;
}
-
+
public AssociationInfoBuilder setLastTimeConnectedMs(long lastTimeConnectedMs) {
this.lastTimeConnectedMs = lastTimeConnectedMs;
return this;
@@ -192,6 +193,7 @@ public class AssociationInfoBuilder {
ClassParameter.from(boolean.class, selfManaged),
ClassParameter.from(boolean.class, notifyOnDeviceNearby),
ClassParameter.from(boolean.class, revoked),
+ ClassParameter.from(boolean.class, pending),
ClassParameter.from(long.class, approvedMs),
ClassParameter.from(long.class, lastTimeConnectedMs),
ClassParameter.from(int.class, systemDataSyncFlags));
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/AudioDeviceInfoBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/AudioDeviceInfoBuilder.java
index c13b68678..b092d1d64 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/AudioDeviceInfoBuilder.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/AudioDeviceInfoBuilder.java
@@ -2,10 +2,15 @@ package org.robolectric.shadows;
import static org.robolectric.util.reflector.Reflector.reflector;
+import android.annotation.RequiresApi;
import android.media.AudioDeviceInfo;
+import android.media.AudioProfile;
+import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.util.SparseIntArray;
-import androidx.annotation.RequiresApi;
+import com.google.common.collect.ImmutableList;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import java.util.List;
import java.util.Optional;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.ReflectionHelpers;
@@ -18,7 +23,8 @@ import org.robolectric.util.reflector.Static;
@RequiresApi(VERSION_CODES.M)
public class AudioDeviceInfoBuilder {
- private int type;
+ private int type = AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
+ private ImmutableList<AudioProfile> profiles = ImmutableList.of();
private AudioDeviceInfoBuilder() {}
@@ -29,18 +35,39 @@ public class AudioDeviceInfoBuilder {
/**
* Sets the device type.
*
+ * <p>The default is {@link AudioDeviceInfo#TYPE_BUILTIN_SPEAKER}.
+ *
* @param type The device type. The possible values are the constants defined as <a
* href="https://cs.android.com/android/platform/superproject/+/master:frameworks/base/media/java/android/media/AudioDeviceInfo.java?q=AudioDeviceType">AudioDeviceInfo.AudioDeviceType</a>
*/
+ @CanIgnoreReturnValue
public AudioDeviceInfoBuilder setType(int type) {
this.type = type;
return this;
}
+ /**
+ * Sets the {@link AudioProfile profiles}.
+ *
+ * @param profiles The list of {@link AudioProfile profiles}.
+ */
+ @RequiresApi(VERSION_CODES.S)
+ @CanIgnoreReturnValue
+ public AudioDeviceInfoBuilder setProfiles(List<AudioProfile> profiles) {
+ this.profiles = ImmutableList.copyOf(profiles);
+ return this;
+ }
+
public AudioDeviceInfo build() {
Object port = Shadow.newInstanceOf("android.media.AudioDevicePort");
ReflectionHelpers.setField(port, "mType", externalToInternalType(type));
-
+ ReflectionHelpers.setField(port, "mAddress", "");
+ Object handle = Shadow.newInstanceOf("android.media.AudioHandle");
+ ReflectionHelpers.setField(handle, "mId", 0);
+ ReflectionHelpers.setField(port, "mHandle", handle);
+ if (VERSION.SDK_INT >= 31) {
+ ReflectionHelpers.setField(port, "mProfiles", profiles);
+ }
return ReflectionHelpers.callConstructor(
AudioDeviceInfo.class, ClassParameter.from(port.getClass(), port));
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/AudioProfileBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/AudioProfileBuilder.java
new file mode 100644
index 000000000..afbbf4b8d
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/AudioProfileBuilder.java
@@ -0,0 +1,104 @@
+package org.robolectric.shadows;
+
+import android.annotation.RequiresApi;
+import android.media.AudioFormat;
+import android.media.AudioProfile;
+import android.os.Build.VERSION_CODES;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import org.robolectric.util.ReflectionHelpers;
+import org.robolectric.util.ReflectionHelpers.ClassParameter;
+
+/** Builder for {@link AudioProfile}. */
+@RequiresApi(VERSION_CODES.S)
+public class AudioProfileBuilder {
+
+ private int format = AudioFormat.ENCODING_PCM_16BIT;
+ private int[] samplingRates = new int[] {48000};
+ private int[] channelMasks = new int[] {AudioFormat.CHANNEL_OUT_STEREO};
+ private int[] channelIndexMasks = new int[] {};
+ private int encapsulationType = AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE;
+
+ private AudioProfileBuilder() {}
+
+ public static AudioProfileBuilder newBuilder() {
+ return new AudioProfileBuilder();
+ }
+
+ /**
+ * Sets the audio format.
+ *
+ * <p>The default is {@link AudioFormat#ENCODING_PCM_16BIT}.
+ *
+ * @param format The audio format. The possible values are the {@code ENCODING_} constants defined
+ * in {@link AudioFormat}.
+ */
+ @CanIgnoreReturnValue
+ public AudioProfileBuilder setFormat(int format) {
+ this.format = format;
+ return this;
+ }
+
+ /**
+ * Sets the sampling rates.
+ *
+ * <p>The default is a single-item array with 48000.
+ *
+ * @param samplingRates The array of supported sampling rates.
+ */
+ @CanIgnoreReturnValue
+ public AudioProfileBuilder setSamplingRates(int[] samplingRates) {
+ this.samplingRates = samplingRates;
+ return this;
+ }
+
+ /**
+ * Sets the channel masks.
+ *
+ * <p>The default is a single-item array with {@link AudioFormat#CHANNEL_OUT_STEREO}.
+ *
+ * @param channelMasks The array of supported channel masks. The possible values are the {@code
+ * CHANNEL_OUT_} constants defined in {@link AudioFormat}.
+ */
+ @CanIgnoreReturnValue
+ public AudioProfileBuilder setChannelMasks(int[] channelMasks) {
+ this.channelMasks = channelMasks;
+ return this;
+ }
+
+ /**
+ * Sets the channel index masks.
+ *
+ * <p>The default is an empty array.
+ *
+ * @param channelIndexMasks The array of supported channel index masks.
+ */
+ @CanIgnoreReturnValue
+ public AudioProfileBuilder setChannelIndexMasks(int[] channelIndexMasks) {
+ this.channelIndexMasks = channelIndexMasks;
+ return this;
+ }
+
+ /**
+ * Sets the encapsulation type.
+ *
+ * <p>The default is {@link AudioProfile#AUDIO_ENCAPSULATION_TYPE_NONE}.
+ *
+ * @param encapsulationType The encapsulation type. The possible values are the {@code
+ * AUDIO_ENCAPSULATION_TYPE_} constants defined in {@link AudioProfile}.
+ */
+ @CanIgnoreReturnValue
+ public AudioProfileBuilder setEncapsulationType(int encapsulationType) {
+ this.encapsulationType = encapsulationType;
+ return this;
+ }
+
+ public AudioProfile build() {
+ return ReflectionHelpers.callConstructor(
+ AudioProfile.class,
+ ClassParameter.from(Integer.TYPE, format),
+ ClassParameter.from(int[].class, samplingRates),
+ ClassParameter.from(int[].class, channelMasks),
+ ClassParameter.from(int[].class, channelIndexMasks),
+ ClassParameter.from(Integer.TYPE, encapsulationType));
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/BackupDataOutputFactory.java b/shadows/framework/src/main/java/org/robolectric/shadows/BackupDataOutputFactory.java
index eb8c888a0..863a60298 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/BackupDataOutputFactory.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/BackupDataOutputFactory.java
@@ -2,9 +2,9 @@ package org.robolectric.shadows;
import static org.robolectric.util.reflector.Reflector.reflector;
+import android.annotation.RequiresApi;
import android.app.backup.BackupDataOutput;
import android.os.Build.VERSION_CODES;
-import androidx.annotation.RequiresApi;
import java.io.FileDescriptor;
import org.robolectric.util.reflector.Constructor;
import org.robolectric.util.reflector.ForType;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/BarringInfoBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/BarringInfoBuilder.java
index d30219824..1edfebe59 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/BarringInfoBuilder.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/BarringInfoBuilder.java
@@ -47,12 +47,12 @@ import static android.telephony.BarringInfo.BarringServiceInfo.BARRING_TYPE_NONE
import static android.telephony.BarringInfo.BarringServiceInfo.BARRING_TYPE_UNCONDITIONAL;
import static android.telephony.BarringInfo.BarringServiceInfo.BARRING_TYPE_UNKNOWN;
+import android.annotation.RequiresApi;
import android.os.Build.VERSION_CODES;
import android.telephony.BarringInfo;
import android.telephony.BarringInfo.BarringServiceInfo;
import android.telephony.CellIdentity;
import android.util.SparseArray;
-import androidx.annotation.RequiresApi;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.ReflectionHelpers.ClassParameter;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/CellIdentityLteBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/CellIdentityLteBuilder.java
index 597aef246..1aa19cdaf 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/CellIdentityLteBuilder.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/CellIdentityLteBuilder.java
@@ -6,7 +6,6 @@ import android.os.Build;
import android.telephony.CellIdentityLte;
import android.telephony.CellInfo;
import android.telephony.ClosedSubscriberGroupInfo;
-import androidx.annotation.RequiresApi;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -16,7 +15,6 @@ import org.robolectric.util.reflector.Constructor;
import org.robolectric.util.reflector.ForType;
/** Builder for {@link android.telephony.CellIdentityLte}. */
-@RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public class CellIdentityLteBuilder {
@Nullable private String mcc = null;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/CellIdentityNrBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/CellIdentityNrBuilder.java
index 22a0e75c0..f103b2bb4 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/CellIdentityNrBuilder.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/CellIdentityNrBuilder.java
@@ -2,10 +2,10 @@ package org.robolectric.shadows;
import static org.robolectric.util.reflector.Reflector.reflector;
+import android.annotation.RequiresApi;
import android.os.Build;
import android.telephony.CellIdentityNr;
import android.telephony.CellInfo;
-import androidx.annotation.RequiresApi;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/CellInfoLteBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/CellInfoLteBuilder.java
index 6f3f93420..06396e050 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/CellInfoLteBuilder.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/CellInfoLteBuilder.java
@@ -7,7 +7,6 @@ import android.telephony.CellIdentityLte;
import android.telephony.CellInfo;
import android.telephony.CellInfoLte;
import android.telephony.CellSignalStrengthLte;
-import androidx.annotation.RequiresApi;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.reflector.Accessor;
@@ -16,7 +15,6 @@ import org.robolectric.util.reflector.ForType;
import org.robolectric.util.reflector.WithType;
/** Builder for {@link android.telephony.CellInfoLte}. */
-@RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public class CellInfoLteBuilder {
private boolean isRegistered = false;
@@ -76,7 +74,7 @@ public class CellInfoLteBuilder {
cellInfoLteReflector.setCellSignalStrength(cellSignalStrength);
CellInfoReflector cellInfoReflector = reflector(CellInfoReflector.class, cellInfo);
cellInfoReflector.setTimeStamp(timeStamp);
- if (apiLevel <= Build.VERSION_CODES.KITKAT) {
+ if (apiLevel == Build.VERSION_CODES.KITKAT) {
cellInfoReflector.setRegisterd(isRegistered);
} else {
cellInfoReflector.setRegistered(isRegistered);
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/CellInfoNrBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/CellInfoNrBuilder.java
index 78195246e..f99a01377 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/CellInfoNrBuilder.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/CellInfoNrBuilder.java
@@ -2,12 +2,12 @@ package org.robolectric.shadows;
import static org.robolectric.util.reflector.Reflector.reflector;
+import android.annotation.RequiresApi;
import android.os.Build;
import android.os.Parcel;
import android.telephony.CellIdentityNr;
import android.telephony.CellInfoNr;
import android.telephony.CellSignalStrengthNr;
-import androidx.annotation.RequiresApi;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.util.reflector.Constructor;
import org.robolectric.util.reflector.ForType;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/CellSignalStrengthLteBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/CellSignalStrengthLteBuilder.java
index 9b5d1a1ac..fc44c3a65 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/CellSignalStrengthLteBuilder.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/CellSignalStrengthLteBuilder.java
@@ -5,13 +5,11 @@ import static org.robolectric.util.reflector.Reflector.reflector;
import android.os.Build;
import android.telephony.CellInfo;
import android.telephony.CellSignalStrengthLte;
-import androidx.annotation.RequiresApi;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.util.reflector.Constructor;
import org.robolectric.util.reflector.ForType;
/** Builder for {@link android.telephony.CellSignalStrengthLte} */
-@RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public class CellSignalStrengthLteBuilder {
private int rssi = CellInfo.UNAVAILABLE;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/CellSignalStrengthNrBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/CellSignalStrengthNrBuilder.java
index 4f3f859bc..5f23b3adb 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/CellSignalStrengthNrBuilder.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/CellSignalStrengthNrBuilder.java
@@ -2,10 +2,10 @@ package org.robolectric.shadows;
import static org.robolectric.util.reflector.Reflector.reflector;
+import android.annotation.RequiresApi;
import android.os.Build;
import android.telephony.CellInfo;
import android.telephony.CellSignalStrengthNr;
-import androidx.annotation.RequiresApi;
import java.util.ArrayList;
import java.util.List;
import org.robolectric.RuntimeEnvironment;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/DeviceStateSensorOrientationBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/DeviceStateSensorOrientationBuilder.java
index ae97119e4..2b4a24451 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/DeviceStateSensorOrientationBuilder.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/DeviceStateSensorOrientationBuilder.java
@@ -1,8 +1,8 @@
package org.robolectric.shadows;
+import android.annotation.RequiresApi;
import android.hardware.camera2.params.DeviceStateSensorOrientationMap;
import android.os.Build.VERSION_CODES;
-import androidx.annotation.RequiresApi;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
/** Builder for {@link DeviceStateSensorOrientationMap} which was introduced in Android T. */
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/DragEventBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/DragEventBuilder.java
index 6b10fac15..ba00e3882 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/DragEventBuilder.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/DragEventBuilder.java
@@ -3,11 +3,11 @@ package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.R;
+import android.annotation.Nullable;
import android.content.ClipData;
import android.content.ClipDescription;
import android.view.DragEvent;
import android.view.SurfaceControl;
-import androidx.annotation.Nullable;
import com.android.internal.view.IDragAndDropPermissions;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.util.ReflectionHelpers;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/GnssStatusBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/GnssStatusBuilder.java
index df5a5c344..29b05b48e 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/GnssStatusBuilder.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/GnssStatusBuilder.java
@@ -1,8 +1,8 @@
package org.robolectric.shadows;
+import android.annotation.Nullable;
import android.location.GnssStatus;
import android.os.Build;
-import androidx.annotation.Nullable;
import com.google.auto.value.AutoValue;
import java.util.ArrayList;
import java.util.Arrays;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/HealthStatsBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/HealthStatsBuilder.java
new file mode 100644
index 000000000..300a21b79
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/HealthStatsBuilder.java
@@ -0,0 +1,214 @@
+package org.robolectric.shadows;
+
+import static org.robolectric.util.reflector.Reflector.reflector;
+
+import android.os.Parcel;
+import android.os.health.HealthStats;
+import android.os.health.TimerStat;
+import android.util.ArrayMap;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Set;
+import org.robolectric.util.reflector.Accessor;
+import org.robolectric.util.reflector.ForType;
+
+/** Test helper class to build {@link HealthStats} */
+final class HealthStatsBuilder {
+
+ // Header fields
+ private String dataType = null;
+
+ // TimerStat fields
+ private final HashMap<Integer, TimerStat> timerMap = new HashMap<>();
+
+ // Measurement fields
+ private final HashMap<Integer, Long> measurementMap = new HashMap<>();
+
+ // Stats fields
+ private final HashMap<Integer, ArrayMap<String, HealthStats>> statsMap = new HashMap<>();
+
+ // Timers fields
+ private final HashMap<Integer, ArrayMap<String, TimerStat>> timersMap = new HashMap<>();
+
+ // Measurements fields
+ private final HashMap<Integer, ArrayMap<String, Long>> measurementsMap = new HashMap<>();
+
+ public static HealthStatsBuilder newBuilder() {
+ return new HealthStatsBuilder();
+ }
+
+ /** Sets the DataType. Defaults to null. */
+ @CanIgnoreReturnValue
+ public HealthStatsBuilder setDataType(String dataType) {
+ this.dataType = dataType;
+ return this;
+ }
+
+ /**
+ * Adds a TimerStat for the given key. If the same key is used multiple times, the last provided
+ * TimerStat is used.
+ */
+ @CanIgnoreReturnValue
+ public HealthStatsBuilder addTimerStat(int key, TimerStat value) {
+ timerMap.put(key, value);
+ return this;
+ }
+
+ /**
+ * Adds a measurement for the given key. If the same key is used multiple times, the last provided
+ * measurement is used.
+ */
+ @CanIgnoreReturnValue
+ public HealthStatsBuilder addMeasurement(int key, long value) {
+ measurementMap.put(key, value);
+ return this;
+ }
+
+ /**
+ * Adds a map of HealthStats for the given key. If the same key is used multiple times, the last
+ * provided map is used.
+ */
+ @CanIgnoreReturnValue
+ public HealthStatsBuilder addStats(int key, ArrayMap<String, HealthStats> value) {
+ statsMap.put(key, new ArrayMap<String, HealthStats>(value));
+ return this;
+ }
+
+ /**
+ * Adds a map of TimerStats for the given key. If the same key is used multiple times, the last
+ * provided map is used.
+ */
+ @CanIgnoreReturnValue
+ public HealthStatsBuilder addTimers(int key, ArrayMap<String, TimerStat> value) {
+ timersMap.put(key, new ArrayMap<String, TimerStat>(value));
+ return this;
+ }
+
+ /**
+ * Adds a map of measurements for the given key. If the same key is used multiple times, the last
+ * provided map is used.
+ */
+ @CanIgnoreReturnValue
+ public HealthStatsBuilder addMeasurements(int key, ArrayMap<String, Long> value) {
+ measurementsMap.put(key, new ArrayMap<String, Long>(value));
+ return this;
+ }
+
+ // HealthStats has internal fields that are generic arrays, so this is unavoidable.
+ @SuppressWarnings("unchecked")
+ public HealthStats build() {
+ // HealthStats' default constructor throws an exception
+ HealthStats result = new HealthStats(Parcel.obtain());
+
+ reflector(HealthStatsReflector.class, result).setDataType(dataType);
+
+ int[] mTimerKeys = toSortedIntArray(timerMap.keySet());
+ reflector(HealthStatsReflector.class, result).setTimerKeys(mTimerKeys);
+ int[] mTimerCounts = new int[mTimerKeys.length];
+ long[] mTimerTimes = new long[mTimerKeys.length];
+ for (int i = 0; i < mTimerKeys.length; i++) {
+ TimerStat timerStat = timerMap.get(mTimerKeys[i]);
+ mTimerCounts[i] = timerStat.getCount();
+ mTimerTimes[i] = timerStat.getTime();
+ }
+ reflector(HealthStatsReflector.class, result).setTimerCounts(mTimerCounts);
+ reflector(HealthStatsReflector.class, result).setTimerTimes(mTimerTimes);
+
+ int[] mMeasurementKeys = toSortedIntArray(measurementMap.keySet());
+ reflector(HealthStatsReflector.class, result).setMeasurementKeys(mMeasurementKeys);
+ long[] mMeasurementValues = new long[mMeasurementKeys.length];
+ for (int i = 0; i < mMeasurementKeys.length; i++) {
+ long measurementValue = measurementMap.get(mMeasurementKeys[i]);
+ mMeasurementValues[i] = measurementValue;
+ }
+ reflector(HealthStatsReflector.class, result).setMeasurementValues(mMeasurementValues);
+
+ int[] mStatsKeys = toSortedIntArray(statsMap.keySet());
+ reflector(HealthStatsReflector.class, result).setStatsKeys(mStatsKeys);
+ ArrayMap<String, HealthStats>[] mStatsValues =
+ (ArrayMap<String, HealthStats>[]) new ArrayMap<?, ?>[mStatsKeys.length];
+ for (int i = 0; i < mStatsKeys.length; i++) {
+ ArrayMap<String, HealthStats> stats = statsMap.get(mStatsKeys[i]);
+ mStatsValues[i] = stats;
+ }
+ reflector(HealthStatsReflector.class, result).setStatsValues(mStatsValues);
+
+ int[] mTimersKeys = toSortedIntArray(timersMap.keySet());
+ reflector(HealthStatsReflector.class, result).setTimersKeys(mTimersKeys);
+ ArrayMap<String, TimerStat>[] mTimersValues =
+ (ArrayMap<String, TimerStat>[]) new ArrayMap<?, ?>[mTimersKeys.length];
+ for (int i = 0; i < mTimersKeys.length; i++) {
+ ArrayMap<String, TimerStat> timers = timersMap.get(mTimersKeys[i]);
+ mTimersValues[i] = timers;
+ }
+ reflector(HealthStatsReflector.class, result).setTimersValues(mTimersValues);
+
+ int[] mMeasurementsKeys = toSortedIntArray(measurementsMap.keySet());
+ reflector(HealthStatsReflector.class, result).setMeasurementsKeys(mMeasurementsKeys);
+ ArrayMap<String, Long>[] mMeasurementsValues =
+ (ArrayMap<String, Long>[]) new ArrayMap<?, ?>[mMeasurementsKeys.length];
+ for (int i = 0; i < mMeasurementsKeys.length; i++) {
+ ArrayMap<String, Long> measurements = measurementsMap.get(mMeasurementsKeys[i]);
+ mMeasurementsValues[i] = measurements;
+ }
+ reflector(HealthStatsReflector.class, result).setMeasurementsValues(mMeasurementsValues);
+
+ return result;
+ }
+
+ private HealthStatsBuilder() {}
+
+ @ForType(HealthStats.class)
+ private interface HealthStatsReflector {
+
+ @Accessor("mDataType")
+ void setDataType(String dataType);
+
+ @Accessor("mTimerKeys")
+ void setTimerKeys(int[] timerKeys);
+
+ @Accessor("mTimerCounts")
+ void setTimerCounts(int[] timerCounts);
+
+ @Accessor("mTimerTimes")
+ void setTimerTimes(long[] timerTimes);
+
+ @Accessor("mMeasurementKeys")
+ void setMeasurementKeys(int[] measurementKeys);
+
+ @Accessor("mMeasurementValues")
+ void setMeasurementValues(long[] measurementValues);
+
+ @Accessor("mStatsKeys")
+ void setStatsKeys(int[] statsKeys);
+
+ @Accessor("mStatsValues")
+ void setStatsValues(ArrayMap<String, HealthStats>[] statsValues);
+
+ @Accessor("mTimersKeys")
+ void setTimersKeys(int[] timersKeys);
+
+ @Accessor("mTimersValues")
+ void setTimersValues(ArrayMap<String, TimerStat>[] timersValues);
+
+ @Accessor("mMeasurementsKeys")
+ void setMeasurementsKeys(int[] measurementsKeys);
+
+ @Accessor("mMeasurementsValues")
+ void setMeasurementsValues(ArrayMap<String, Long>[] measurementsValues);
+ }
+
+ @VisibleForTesting
+ static final int[] toSortedIntArray(Set<Integer> set) {
+ int[] result = new int[set.size()];
+ Object[] inputObjArray = set.toArray();
+ for (int i = 0; i < inputObjArray.length; i++) {
+ result[i] = (int) inputObjArray[i];
+ }
+ // mFooKeys fields of HealthStats are used in Arrays.binarySearch, so they have to be sorted.
+ Arrays.sort(result);
+ return result;
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ModuleInfoBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/ModuleInfoBuilder.java
index 39063caf0..ba5511aca 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ModuleInfoBuilder.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ModuleInfoBuilder.java
@@ -2,8 +2,8 @@ package org.robolectric.shadows;
import static com.google.common.base.Preconditions.checkNotNull;
+import android.annotation.Nullable;
import android.content.pm.ModuleInfo;
-import androidx.annotation.Nullable;
/**
* Builder for {@link ModuleInfo} as ModuleInfo has hidden constructors, this builder class has been
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/NetworkRegistrationInfoTestBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/NetworkRegistrationInfoTestBuilder.java
index 44706fba2..c3e1eac10 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/NetworkRegistrationInfoTestBuilder.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/NetworkRegistrationInfoTestBuilder.java
@@ -4,12 +4,12 @@ import static android.os.Build.VERSION_CODES.Q;
import static android.os.Build.VERSION_CODES.TIRAMISU;
import static org.robolectric.util.reflector.Reflector.reflector;
+import android.annotation.RequiresApi;
import android.os.Build.VERSION;
import android.telephony.CellIdentity;
import android.telephony.DataSpecificRegistrationInfo;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.VoiceSpecificRegistrationInfo;
-import androidx.annotation.RequiresApi;
import java.util.List;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.util.reflector.Accessor;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/NetworkSpecifierFactory.java b/shadows/framework/src/main/java/org/robolectric/shadows/NetworkSpecifierFactory.java
new file mode 100644
index 000000000..93b11acd1
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/NetworkSpecifierFactory.java
@@ -0,0 +1,29 @@
+package org.robolectric.shadows;
+
+import android.annotation.RequiresApi;
+import android.net.NetworkSpecifier;
+import android.net.StringNetworkSpecifier;
+import android.os.Build;
+
+/** Factory to create {@link NetworkSpecifier} types that are hidden on certain SDK levels. */
+@RequiresApi(Build.VERSION_CODES.O)
+public final class NetworkSpecifierFactory {
+
+ /**
+ * Constructs a new {@link StringNetworkSpecifier} instance, which has remained hidden on all SDKs
+ * (as of U), but has existed since the {@link NetworkSpecifier} hierarchy was created in O to
+ * represent a few more niche specifier types without defining a full-blown subclass of {@link
+ * NetworkSpecifier} for each of their particular use cases. These meanings typically stabilize
+ * over time and then gain a concrete {@link NetworkSpecifier} subtype in the public SDK, which
+ * tests should prefer when available.
+ *
+ * <p>Depending on the {@code specifier} string's content, the returned instance will have one of
+ * several different meanings. See {@link
+ * android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} documentation for more detail.
+ */
+ public static NetworkSpecifier newStringNetworkSpecifier(String specifier) {
+ return new StringNetworkSpecifier(specifier);
+ }
+
+ private NetworkSpecifierFactory() {}
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/PackageRollbackInfoBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/PackageRollbackInfoBuilder.java
index ac71cb5cd..393a32d72 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/PackageRollbackInfoBuilder.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/PackageRollbackInfoBuilder.java
@@ -3,13 +3,13 @@ package org.robolectric.shadows;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
+import android.annotation.Nullable;
import android.content.pm.VersionedPackage;
import android.content.rollback.PackageRollbackInfo;
import android.content.rollback.PackageRollbackInfo.RestoreInfo;
import android.os.Build.VERSION_CODES;
import android.util.IntArray;
import android.util.SparseLongArray;
-import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import org.robolectric.RuntimeEnvironment;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ResourceHelper.java b/shadows/framework/src/main/java/org/robolectric/shadows/ResourceHelper.java
index 7fe54d94b..f031681b0 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ResourceHelper.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ResourceHelper.java
@@ -19,6 +19,7 @@ package org.robolectric.shadows;
import android.util.TypedValue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import org.robolectric.util.Logger;
/**
* Helper class to provide various conversion method used in handling android resources.
@@ -141,17 +142,19 @@ public final class ResourceHelper {
}
}
- private final static UnitEntry[] sUnitNames = new UnitEntry[] {
- new UnitEntry("px", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_PX, 1.0f),
- new UnitEntry("dip", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_DIP, 1.0f),
- new UnitEntry("dp", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_DIP, 1.0f),
- new UnitEntry("sp", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_SP, 1.0f),
- new UnitEntry("pt", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_PT, 1.0f),
- new UnitEntry("in", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_IN, 1.0f),
- new UnitEntry("mm", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_MM, 1.0f),
- new UnitEntry("%", TypedValue.TYPE_FRACTION, TypedValue.COMPLEX_UNIT_FRACTION, 1.0f/100),
- new UnitEntry("%p", TypedValue.TYPE_FRACTION, TypedValue.COMPLEX_UNIT_FRACTION_PARENT, 1.0f/100),
- };
+ private static final UnitEntry[] sUnitNames =
+ new UnitEntry[] {
+ new UnitEntry("px", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_PX, 1.0f),
+ new UnitEntry("dip", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_DIP, 1.0f),
+ new UnitEntry("dp", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_DIP, 1.0f),
+ new UnitEntry("sp", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_SP, 1.0f),
+ new UnitEntry("pt", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_PT, 1.0f),
+ new UnitEntry("in", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_IN, 1.0f),
+ new UnitEntry("mm", TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_MM, 1.0f),
+ new UnitEntry("%", TypedValue.TYPE_FRACTION, TypedValue.COMPLEX_UNIT_FRACTION, 1.0f / 100),
+ new UnitEntry(
+ "%p", TypedValue.TYPE_FRACTION, TypedValue.COMPLEX_UNIT_FRACTION_PARENT, 1.0f / 100),
+ };
/**
* Returns the raw value from the given attribute float-type value string.
@@ -190,6 +193,17 @@ public final class ResourceHelper {
return false;
}
+ // Limit the maximum float attribute string length to mitigate potential
+ // "Polynomial regular expression used on uncontrolled data" check severity
+ // from GitHub CodeQL.
+ if (len > 1000) {
+ Logger.info(
+ "Skip to parse attribute for float attribute, because it is too long. The attribute is "
+ + attribute
+ + ".");
+ return false;
+ }
+
// check that there's no non ascii characters.
char[] buf = value.toCharArray();
for (int i = 0 ; i < len ; i++) {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ServiceStateBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/ServiceStateBuilder.java
index 2417bf8a7..736b698a5 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ServiceStateBuilder.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ServiceStateBuilder.java
@@ -5,10 +5,10 @@ import static android.os.Build.VERSION_CODES.Q;
import static android.os.Build.VERSION_CODES.R;
import static org.robolectric.util.reflector.Reflector.reflector;
+import android.annotation.RequiresApi;
import android.os.Build.VERSION;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.ServiceState;
-import androidx.annotation.RequiresApi;
import java.util.List;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.util.reflector.Accessor;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityManager.java
index 31815eebb..f2f53a073 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityManager.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.O_MR1;
import static org.robolectric.RuntimeEnvironment.getApiLevel;
@@ -75,32 +74,18 @@ public class ShadowAccessibilityManager {
}
private static AccessibilityManager createInstance(Context context) {
- if (getApiLevel() >= KITKAT) {
- AccessibilityManager accessibilityManager =
- Shadow.newInstance(
- AccessibilityManager.class,
- new Class[] {Context.class, IAccessibilityManager.class, int.class},
- new Object[] {
- context, ReflectionHelpers.createNullProxy(IAccessibilityManager.class), 0
- });
- ReflectionHelpers.setField(
- accessibilityManager,
- "mHandler",
- new MyHandler(context.getMainLooper(), accessibilityManager));
- return accessibilityManager;
- } else {
- AccessibilityManager accessibilityManager =
- Shadow.newInstance(AccessibilityManager.class, new Class[0], new Object[0]);
- ReflectionHelpers.setField(
- accessibilityManager,
- "mHandler",
- new MyHandler(context.getMainLooper(), accessibilityManager));
- ReflectionHelpers.setField(
- accessibilityManager,
- "mService",
- ReflectionHelpers.createNullProxy(IAccessibilityManager.class));
- return accessibilityManager;
- }
+ AccessibilityManager accessibilityManager =
+ Shadow.newInstance(
+ AccessibilityManager.class,
+ new Class[] {Context.class, IAccessibilityManager.class, int.class},
+ new Object[] {
+ context, ReflectionHelpers.createNullProxy(IAccessibilityManager.class), 0
+ });
+ ReflectionHelpers.setField(
+ accessibilityManager,
+ "mHandler",
+ new MyHandler(context.getMainLooper(), accessibilityManager));
+ return accessibilityManager;
}
@Implementation
@@ -215,7 +200,7 @@ public class ShadowAccessibilityManager {
reflector(AccessibilityManagerReflector.class, realAccessibilityManager)
.getTouchExplorationStateChangeListeners()
.keySet());
- } else if (getApiLevel() >= KITKAT) {
+ } else {
listeners =
new ArrayList<>(
reflector(AccessibilityManagerReflectorN.class, realAccessibilityManager)
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityNodeInfo.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityNodeInfo.java
index a27d01c98..905baf661 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityNodeInfo.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityNodeInfo.java
@@ -1,13 +1,12 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
-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.N;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.P;
+import static android.os.Build.VERSION_CODES.R;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
import static org.robolectric.RuntimeEnvironment.getApiLevel;
import static org.robolectric.util.reflector.Reflector.reflector;
@@ -279,7 +278,7 @@ public class ShadowAccessibilityNodeInfo {
return obtain(parent);
}
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected boolean refresh() {
return refreshReturnValue;
}
@@ -316,7 +315,7 @@ public class ShadowAccessibilityNodeInfo {
return text;
}
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected AccessibilityNodeInfo getLabelFor() {
if (labelFor == null) {
return null;
@@ -333,7 +332,7 @@ public class ShadowAccessibilityNodeInfo {
labelFor = obtain(info);
}
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
protected AccessibilityNodeInfo getLabeledBy() {
if (labeledBy == null) {
return null;
@@ -593,20 +592,16 @@ public class ShadowAccessibilityNodeInfo {
newShadow.refreshReturnValue = refreshReturnValue;
newInfo.setMovementGranularities(realAccessibilityNodeInfo.getMovementGranularities());
newInfo.setPackageName(realAccessibilityNodeInfo.getPackageName());
- if (getApiLevel() >= JELLY_BEAN_MR2) {
- newInfo.setViewIdResourceName(realAccessibilityNodeInfo.getViewIdResourceName());
- newInfo.setTextSelection(
- realAccessibilityNodeInfo.getTextSelectionStart(),
- realAccessibilityNodeInfo.getTextSelectionEnd());
- }
- if (getApiLevel() >= KITKAT) {
- newInfo.setCollectionInfo(realAccessibilityNodeInfo.getCollectionInfo());
- newInfo.setCollectionItemInfo(realAccessibilityNodeInfo.getCollectionItemInfo());
- newInfo.setInputType(realAccessibilityNodeInfo.getInputType());
- newInfo.setLiveRegion(realAccessibilityNodeInfo.getLiveRegion());
- newInfo.setRangeInfo(realAccessibilityNodeInfo.getRangeInfo());
- newShadow.realAccessibilityNodeInfo.getExtras().putAll(realAccessibilityNodeInfo.getExtras());
- }
+ newInfo.setViewIdResourceName(realAccessibilityNodeInfo.getViewIdResourceName());
+ newInfo.setTextSelection(
+ realAccessibilityNodeInfo.getTextSelectionStart(),
+ realAccessibilityNodeInfo.getTextSelectionEnd());
+ newInfo.setCollectionInfo(realAccessibilityNodeInfo.getCollectionInfo());
+ newInfo.setCollectionItemInfo(realAccessibilityNodeInfo.getCollectionItemInfo());
+ newInfo.setInputType(realAccessibilityNodeInfo.getInputType());
+ newInfo.setLiveRegion(realAccessibilityNodeInfo.getLiveRegion());
+ newInfo.setRangeInfo(realAccessibilityNodeInfo.getRangeInfo());
+ newShadow.realAccessibilityNodeInfo.getExtras().putAll(realAccessibilityNodeInfo.getExtras());
if (getApiLevel() >= LOLLIPOP) {
newInfo.setMaxTextLength(realAccessibilityNodeInfo.getMaxTextLength());
newInfo.setError(realAccessibilityNodeInfo.getError());
@@ -629,6 +624,12 @@ public class ShadowAccessibilityNodeInfo {
newInfo.setTooltipText(realAccessibilityNodeInfo.getTooltipText());
newInfo.setPaneTitle(realAccessibilityNodeInfo.getPaneTitle());
}
+ if (getApiLevel() >= R) {
+ newInfo.setStateDescription(realAccessibilityNodeInfo.getStateDescription());
+ }
+ if (getApiLevel() >= UPSIDE_DOWN_CAKE) {
+ newInfo.setContainerTitle(realAccessibilityNodeInfo.getContainerTitle());
+ }
return newInfo;
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityRecord.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityRecord.java
index 7470e4dbb..8b99abe66 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityRecord.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityRecord.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static org.robolectric.util.reflector.Reflector.reflector;
import android.view.View;
@@ -80,7 +79,7 @@ public class ShadowAccessibilityRecord {
*
* @param id The id to set
*/
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
public void setWindowId(int id) {
windowId = id;
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccountManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccountManager.java
index 81b70d1c0..a51d205d1 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccountManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccountManager.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1;
import static android.os.Build.VERSION_CODES.O;
@@ -14,12 +13,12 @@ import android.accounts.AuthenticatorException;
import android.accounts.IAccountManager;
import android.accounts.OnAccountsUpdateListener;
import android.accounts.OperationCanceledException;
+import android.annotation.Nullable;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
-import androidx.annotation.Nullable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
@@ -59,6 +58,7 @@ public class ShadowAccountManager {
private Handler mainHandler;
private RoboAccountManagerFuture pendingAddFuture;
private boolean authenticationErrorOnNextResponse = false;
+ private boolean securityErrorOnNextGetAccountsByTypeCall = false;
private Intent removeAccountIntent;
@Implementation
@@ -78,6 +78,11 @@ public class ShadowAccountManager {
@Implementation
protected Account[] getAccountsByType(String type) {
+ if (securityErrorOnNextGetAccountsByTypeCall) {
+ securityErrorOnNextGetAccountsByTypeCall = false;
+ throw new SecurityException();
+ }
+
if (type == null) {
return getAccounts();
}
@@ -724,7 +729,7 @@ public class ShadowAccountManager {
return future;
}
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected Account[] getAccountsByTypeForPackage(String type, String packageName) {
List<Account> result = new ArrayList<>();
@@ -750,6 +755,16 @@ public class ShadowAccountManager {
}
/**
+ * Sets flag which if {@code true} will cause an exception to be thrown by {@link
+ * #getAccountsByType}.
+ *
+ * @param shouldThrowException should an exception be thrown or not on the next method call.
+ */
+ public void setSecurityErrorOnNextGetAccountsByTypeCall(boolean shouldThrowException) {
+ this.securityErrorOnNextGetAccountsByTypeCall = shouldThrowException;
+ }
+
+ /**
* Sets the intent to include in Bundle result from {@link #removeAccount} if Activity is given.
*
* @param removeAccountIntent the intent to surface as {@link AccountManager#KEY_INTENT}.
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowActivity.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowActivity.java
index 1d575a4b9..2c6520d3b 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowActivity.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowActivity.java
@@ -1,7 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.N;
@@ -9,8 +7,12 @@ import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.O_MR1;
import static android.os.Build.VERSION_CODES.Q;
import static android.os.Build.VERSION_CODES.S;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
import static org.robolectric.util.reflector.Reflector.reflector;
+import android.annotation.AnimRes;
+import android.annotation.ColorInt;
+import android.annotation.RequiresApi;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityOptions;
@@ -32,6 +34,7 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
import android.database.Cursor;
import android.os.Binder;
+import android.os.Build;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
@@ -42,6 +45,7 @@ import android.os.Looper;
import android.os.Parcel;
import android.text.Selection;
import android.text.SpannableStringBuilder;
+import android.util.SparseArray;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -49,7 +53,6 @@ import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
-import androidx.annotation.RequiresApi;
import com.android.internal.app.IVoiceInteractor;
import java.util.ArrayList;
import java.util.HashMap;
@@ -89,6 +92,8 @@ public class ShadowActivity extends ShadowContextThemeWrapper {
private Integer lastShownDialogId = null;
private int pendingTransitionEnterAnimResId = -1;
private int pendingTransitionExitAnimResId = -1;
+ private SparseArray<OverriddenActivityTransition> overriddenActivityTransitions =
+ new SparseArray<>();
private Object lastNonConfigurationInstance;
private Map<Integer, Dialog> dialogForId = new HashMap<>();
private ArrayList<Cursor> managedCursors = new ArrayList<>();
@@ -382,7 +387,7 @@ public class ShadowActivity extends ShadowContextThemeWrapper {
* is because `finish` modifies the members of {@link ShadowActivity#realActivity}, so
* `isFinishing` should refer to those same members.
*/
- @Implementation(minSdk = JELLY_BEAN)
+ @Implementation
protected boolean isFinishing() {
return reflector(DirectActivityReflector.class, realActivity).isFinishing();
}
@@ -475,7 +480,7 @@ public class ShadowActivity extends ShadowContextThemeWrapper {
lastIntentSenderRequest.send();
}
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected void reportFullyDrawn() {
hasReportedFullyDrawn = true;
}
@@ -571,6 +576,24 @@ public class ShadowActivity extends ShadowContextThemeWrapper {
return pendingTransitionExitAnimResId;
}
+ /**
+ * Get the overridden {@link Activity} transition, set by {@link
+ * Activity#overrideActivityTransition}.
+ *
+ * @param overrideType Use {@link Activity#OVERRIDE_TRANSITION_OPEN} to get the overridden
+ * activity transition animation details when starting/entering an activity. Use {@link
+ * Activity#OVERRIDE_TRANSITION_CLOSE} to get the overridden activity transition animation
+ * details when finishing/closing an activity.
+ * @return overridden activity transition details after calling {@link
+ * Activity#overrideActivityTransition(int, int, int, int)} or null if was not overridden.
+ * @see #clearOverrideActivityTransition(int)
+ */
+ @Nullable
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public OverriddenActivityTransition getOverriddenActivityTransition(int overrideType) {
+ return overriddenActivityTransitions.get(overrideType, null);
+ }
+
@Implementation
protected boolean onCreateOptionsMenu(Menu menu) {
optionsMenu = menu;
@@ -739,6 +762,27 @@ public class ShadowActivity extends ShadowContextThemeWrapper {
pendingTransitionExitAnimResId = exitAnim;
}
+ @Implementation(minSdk = UPSIDE_DOWN_CAKE)
+ protected void overrideActivityTransition(
+ int overrideType,
+ @AnimRes int enterAnim,
+ @AnimRes int exitAnim,
+ @ColorInt int backgroundColor) {
+ overriddenActivityTransitions.put(
+ overrideType, new OverriddenActivityTransition(enterAnim, exitAnim, backgroundColor));
+
+ reflector(DirectActivityReflector.class, realActivity)
+ .overrideActivityTransition(overrideType, enterAnim, exitAnim, backgroundColor);
+ }
+
+ @Implementation(minSdk = UPSIDE_DOWN_CAKE)
+ protected void clearOverrideActivityTransition(int overrideType) {
+ overriddenActivityTransitions.remove(overrideType);
+
+ reflector(DirectActivityReflector.class, realActivity)
+ .clearOverrideActivityTransition(overrideType);
+ }
+
public Dialog getDialogById(int dialogId) {
return dialogForId.get(dialogId);
}
@@ -916,6 +960,22 @@ public class ShadowActivity extends ShadowContextThemeWrapper {
});
}
+ /**
+ * Class to hold overridden activity transition details after calling {@link
+ * Activity#overrideActivityTransition(int, int, int, int)}
+ */
+ public static class OverriddenActivityTransition {
+ @AnimRes public final int enterAnim;
+ @AnimRes public final int exitAnim;
+ @ColorInt public final int backgroundColor;
+
+ public OverriddenActivityTransition(int enterAnim, int exitAnim, int backgroundColor) {
+ this.enterAnim = enterAnim;
+ this.exitAnim = exitAnim;
+ this.backgroundColor = backgroundColor;
+ }
+ }
+
/** Class to hold a permissions request, including its request code. */
public static class PermissionsRequest {
public final int requestCode;
@@ -987,6 +1047,11 @@ public class ShadowActivity extends ShadowContextThemeWrapper {
boolean isFinishing();
+ void overrideActivityTransition(
+ int overrideType, int enterAnim, int exitAnim, int backgroundColor);
+
+ void clearOverrideActivityTransition(int overrideType);
+
Window getWindow();
Object getLastNonConfigurationInstance();
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowActivityManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowActivityManager.java
index bd588217a..b3607ac0e 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowActivityManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowActivityManager.java
@@ -1,8 +1,6 @@
package org.robolectric.shadows;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.O;
@@ -11,6 +9,7 @@ import static android.os.Build.VERSION_CODES.R;
import static java.util.stream.Collectors.toCollection;
import static org.robolectric.util.reflector.Reflector.reflector;
+import android.annotation.RequiresApi;
import android.annotation.RequiresPermission;
import android.app.ActivityManager;
import android.app.ApplicationExitInfo;
@@ -26,7 +25,6 @@ import android.os.Process;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.SparseIntArray;
-import androidx.annotation.RequiresApi;
import com.google.common.base.Preconditions;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -91,7 +89,7 @@ public class ShadowActivityManager {
return false;
}
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
@HiddenApi
@RequiresPermission(
anyOf = {
@@ -156,7 +154,7 @@ public class ShadowActivityManager {
}
@HiddenApi
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
protected boolean switchUser(int userid) {
ShadowUserManager shadowUserManager =
Shadow.extract(context.getSystemService(Context.USER_SERVICE));
@@ -246,7 +244,7 @@ public class ShadowActivityManager {
return ReflectionHelpers.createNullProxy(IActivityManager.class);
}
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected boolean isLowRamDevice() {
if (isLowRamDeviceOverride != null) {
return isLowRamDeviceOverride;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAlarmManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAlarmManager.java
index ea551d8c1..2971b8e57 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAlarmManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAlarmManager.java
@@ -3,6 +3,8 @@ package org.robolectric.shadows;
import static android.app.AlarmManager.RTC_WAKEUP;
import static org.robolectric.util.reflector.Reflector.reflector;
+import android.annotation.Nullable;
+import android.annotation.RequiresApi;
import android.app.AlarmManager;
import android.app.AlarmManager.AlarmClockInfo;
import android.app.AlarmManager.OnAlarmListener;
@@ -14,9 +16,7 @@ import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.os.WorkSource;
-import androidx.annotation.GuardedBy;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
+import com.android.internal.annotations.GuardedBy;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
import java.util.List;
@@ -117,7 +117,7 @@ public class ShadowAlarmManager {
setImpl(type, triggerAtMs, WINDOW_HEURISTIC, intervalMs, operation, null, null, false);
}
- @Implementation(minSdk = VERSION_CODES.KITKAT)
+ @Implementation
protected void setWindow(
int type, long windowStartMs, long windowLengthMs, PendingIntent operation) {
setImpl(type, windowStartMs, windowLengthMs, 0L, operation, null, null, false);
@@ -179,7 +179,7 @@ public class ShadowAlarmManager {
setImpl(type, windowStartMs, windowLengthMs, 0L, tag, listener, executor, null, true);
}
- @Implementation(minSdk = VERSION_CODES.KITKAT)
+ @Implementation
protected void setExact(int type, long triggerAtMs, PendingIntent operation) {
setImpl(type, triggerAtMs, WINDOW_EXACT, 0L, operation, null, null, false);
}
@@ -209,7 +209,7 @@ public class ShadowAlarmManager {
setImpl(RTC_WAKEUP, info.getTriggerTime(), WINDOW_EXACT, 0L, operation, null, info, true);
}
- @Implementation(minSdk = VERSION_CODES.KITKAT)
+ @Implementation
protected void set(
int type,
long triggerAtMs,
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAmbientContextManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAmbientContextManager.java
index 9e9dda56e..aac2e77f8 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAmbientContextManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAmbientContextManager.java
@@ -1,11 +1,11 @@
package org.robolectric.shadows;
+import android.annotation.Nullable;
import android.app.PendingIntent;
import android.app.ambientcontext.AmbientContextEventRequest;
import android.app.ambientcontext.AmbientContextManager;
import android.os.Build.VERSION_CODES;
-import androidx.annotation.GuardedBy;
-import androidx.annotation.Nullable;
+import com.android.internal.annotations.GuardedBy;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAmbientDisplayConfiguration.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAmbientDisplayConfiguration.java
new file mode 100644
index 000000000..95888f800
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAmbientDisplayConfiguration.java
@@ -0,0 +1,69 @@
+package org.robolectric.shadows;
+
+import android.os.Build.VERSION_CODES;
+import android.os.SystemProperties;
+import android.text.TextUtils;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+/** Shadow for {@code AmbientDisplayConfiguration} class. */
+@Implements(
+ className = "android.hardware.display.AmbientDisplayConfiguration",
+ minSdk = VERSION_CODES.Q,
+ isInAndroidSdk = false)
+public class ShadowAmbientDisplayConfiguration {
+
+ @SuppressWarnings("NonFinalStaticField")
+ private static String dozeComponent;
+
+ @SuppressWarnings("NonFinalStaticField")
+ private static boolean dozeAlwaysOnDisplayAvailable;
+
+ @Implementation
+ protected String ambientDisplayComponent() {
+ return dozeComponent;
+ }
+
+ @Implementation
+ protected boolean alwaysOnAvailable() {
+ return (alwaysOnDisplayDebuggingEnabled() || alwaysOnDisplayAvailable())
+ && ambientDisplayAvailable();
+ }
+
+ @Implementation
+ protected boolean ambientDisplayAvailable() {
+ return !TextUtils.isEmpty(ambientDisplayComponent());
+ }
+
+ @Implementation
+ protected boolean alwaysOnDisplayDebuggingEnabled() {
+ return SystemProperties.getBoolean("debug.doze.aod", false)
+ && (SystemProperties.getInt("ro.debuggable", 0) == 1);
+ }
+
+ @Implementation
+ protected boolean alwaysOnDisplayAvailable() {
+ return dozeAlwaysOnDisplayAvailable;
+ }
+
+ /**
+ * Overrides the string format of component for doze mode. See {@link #ambientDisplayComponent()}.
+ */
+ public static void setDozeComponent(String dozeComponent) {
+ ShadowAmbientDisplayConfiguration.dozeComponent = dozeComponent;
+ }
+
+ /**
+ * Overrides the available state for always on display. See {@link #alwaysOnDisplayAvailable()}.
+ */
+ public static void setDozeAlwaysOnDisplayAvailable(boolean dozeAlwaysOnDisplayAvailable) {
+ ShadowAmbientDisplayConfiguration.dozeAlwaysOnDisplayAvailable = dozeAlwaysOnDisplayAvailable;
+ }
+
+ @Resetter
+ public static void reset() {
+ dozeComponent = null;
+ dozeAlwaysOnDisplayAvailable = false;
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAppIntegrityManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAppIntegrityManager.java
index 1065554d3..4d7eaa507 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAppIntegrityManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAppIntegrityManager.java
@@ -13,7 +13,6 @@ import org.robolectric.annotation.Implements;
@Implements(
value = AppIntegrityManager.class,
minSdk = R,
- looseSignatures = true,
isInAndroidSdk = false)
public class ShadowAppIntegrityManager {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAppOpsManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAppOpsManager.java
index 0d36bfc06..814e96a76 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAppOpsManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAppOpsManager.java
@@ -1,6 +1,5 @@
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.M;
import static android.os.Build.VERSION_CODES.P;
@@ -14,6 +13,7 @@ import static org.robolectric.util.reflector.Reflector.reflector;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresApi;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.app.AppOpsManager;
@@ -33,7 +33,6 @@ import android.os.Build;
import android.util.ArrayMap;
import android.util.LongSparseArray;
import android.util.LongSparseLongArray;
-import androidx.annotation.RequiresApi;
import com.android.internal.app.IAppOpsService;
import com.google.auto.value.AutoValue;
import com.google.common.base.Preconditions;
@@ -65,7 +64,7 @@ import org.robolectric.util.reflector.Accessor;
import org.robolectric.util.reflector.ForType;
/** Shadow for {@link AppOpsManager}. */
-@Implements(value = AppOpsManager.class, minSdk = KITKAT, looseSignatures = true)
+@Implements(value = AppOpsManager.class, looseSignatures = true)
public class ShadowAppOpsManager {
// OpEntry fields that the shadow doesn't currently allow the test to configure.
@@ -277,7 +276,7 @@ public class ShadowAppOpsManager {
}
/** Stores a fake long-running operation. It does not throw if a wrong uid is passed. */
- @Implementation(minSdk = KITKAT, maxSdk = Q)
+ @Implementation(maxSdk = Q)
protected int startOpNoThrow(int op, int uid, String packageName) {
int mode = unsafeCheckOpRawNoThrow(op, uid, packageName);
if (mode == AppOpsManager.MODE_ALLOWED) {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAppWidgetManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAppWidgetManager.java
index a8a0847a0..b1d78d1f4 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAppWidgetManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAppWidgetManager.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.L;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
@@ -219,7 +218,7 @@ public class ShadowAppWidgetManager {
}
@HiddenApi
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
protected void bindAppWidgetId(int appWidgetId, ComponentName provider, Bundle options) {
WidgetInfo widgetInfo = new WidgetInfo(provider);
widgetInfos.put(appWidgetId, widgetInfo);
@@ -246,7 +245,7 @@ public class ShadowAppWidgetManager {
* Create an internal presentation of the widget locally and store the options {@link Bundle} with
* it. This implementation doesn't trigger {@code AppWidgetProvider.onUpdate}
*/
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
protected boolean bindAppWidgetIdIfAllowed(
int appWidgetId, ComponentName provider, Bundle options) {
if (validWidgetProviderComponentName) {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplicationPackageManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplicationPackageManager.java
index 6b20e2d62..77fc0c11a 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplicationPackageManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplicationPackageManager.java
@@ -22,9 +22,6 @@ import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
import static android.content.pm.PackageManager.SIGNATURE_UNKNOWN_PACKAGE;
import static android.os.Build.VERSION_CODES.JELLY_BEAN;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
-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;
@@ -63,7 +60,6 @@ import android.content.pm.FeatureInfo;
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageDeleteObserver;
import android.content.pm.IPackageStatsObserver;
-import android.content.pm.InstallSourceInfo;
import android.content.pm.InstrumentationInfo;
import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.ModuleInfo;
@@ -71,6 +67,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.ComponentEnabledSetting;
+import android.content.pm.PackageManager.ComponentInfoFlags;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManager.OnPermissionsChangedListener;
import android.content.pm.PackageManager.PackageInfoFlags;
@@ -588,7 +585,7 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager {
return resolveInfo.activityInfo != null
|| resolveInfo.serviceInfo != null
- || (VERSION.SDK_INT >= VERSION_CODES.KITKAT && resolveInfo.providerInfo != null);
+ || resolveInfo.providerInfo != null;
}
private static boolean isFlagSet(long flags, long matchFlag) {
@@ -603,7 +600,7 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager {
}
/** Behaves as {@link #queryIntentServices(Intent, int)} and currently ignores userId. */
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
protected List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int flags, int userId) {
return queryIntentServices(intent, flags);
}
@@ -787,7 +784,7 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager {
}
/** Behaves as {@link #queryIntentActivities(Intent, int)} and currently ignores userId. */
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
protected List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent, int flags, int userId) {
return queryIntentActivities(intent, flags);
}
@@ -909,6 +906,14 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager {
ActivityInfo::new);
}
+ @Implementation(minSdk = TIRAMISU)
+ protected ActivityInfo getReceiverInfo(
+ /*ComponentName*/ Object component, /*ComponentInfoFlags*/ Object flags)
+ throws NameNotFoundException {
+ return getReceiverInfo(
+ (ComponentName) component, (int) ((ComponentInfoFlags) flags).getValue());
+ }
+
@Implementation
protected List<ResolveInfo> queryBroadcastReceivers(Intent intent, int flags) {
return this.queryIntentComponents(
@@ -922,7 +927,8 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager {
}
@Implementation(minSdk = TIRAMISU)
- protected List<ResolveInfo> queryBroadcastReceivers(Object intent, @NonNull Object flags) {
+ protected List<ResolveInfo> queryBroadcastReceivers(
+ /*Intent*/ Object intent, /*ResolveInfoFlags*/ Object flags) {
return queryBroadcastReceivers((Intent) intent, (int) ((ResolveInfoFlags) flags).getValue());
}
@@ -953,6 +959,13 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager {
ServiceInfo::new);
}
+ @Implementation(minSdk = TIRAMISU)
+ protected ServiceInfo getServiceInfo(
+ /*ComponentName*/ Object component, /*ComponentInfoFlags*/ Object flags)
+ throws NameNotFoundException {
+ return getServiceInfo((ComponentName) component, (int) ((ComponentInfoFlags) flags).getValue());
+ }
+
/**
* Modifies the component in place using.
*
@@ -1085,8 +1098,12 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager {
}
@Implementation(minSdk = R)
- protected Object getInstallSourceInfo(String packageName) {
- return (InstallSourceInfo) packageInstallSourceInfoMap.get(packageName);
+ protected Object getInstallSourceInfo(String packageName) throws NameNotFoundException {
+ if (!packageInstallSourceInfoMap.containsKey(packageName)) {
+ throw new NameNotFoundException("Package is not installed: " + packageName);
+ } else {
+ return packageInstallSourceInfoMap.get(packageName);
+ }
}
@Implementation
@@ -1133,7 +1150,7 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager {
verificationResults.put(id, verificationCode);
}
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
protected void extendVerificationTimeout(
int id, int verificationCodeAtTimeout, long millisecondsToDelay) {
synchronized (lock) {
@@ -1157,7 +1174,7 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager {
}
}
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected List<ResolveInfo> queryIntentContentProviders(Intent intent, int flags) {
return this.queryIntentComponents(
intent,
@@ -1169,7 +1186,7 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager {
ProviderInfo::new);
}
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected List<ResolveInfo> queryIntentContentProvidersAsUser(
Intent intent, int flags, int userId) {
return Collections.emptyList();
@@ -1195,7 +1212,7 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager {
});
}
- @Implementation(minSdk = JELLY_BEAN_MR1, maxSdk = M)
+ @Implementation(maxSdk = M)
protected void getPackageSizeInfo(Object pkgName, Object uid, final Object observer) {
final PackageStats packageStats = packageStatsMap.get((String) pkgName);
new Handler(Looper.getMainLooper())
@@ -1396,7 +1413,7 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager {
return null;
}
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected int getPackageUid(String packageName, int flags) throws NameNotFoundException {
Integer uid = uidForPackage.get(packageName);
if (uid == null) {
@@ -1673,7 +1690,7 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager {
return null;
}
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected List<PackageInfo> getPackagesHoldingPermissions(String[] permissions, int flags) {
synchronized (lock) {
List<PackageInfo> packageInfosWithPermissions = new ArrayList<>();
@@ -1706,7 +1723,7 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager {
}
/** Behaves as {@link #resolveActivity(Intent, int)} and currently ignores userId. */
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
protected ResolveInfo resolveActivityAsUser(Intent intent, int flags, int userId) {
return resolveActivity(intent, flags);
}
@@ -1817,7 +1834,7 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager {
}
}
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
protected Resources getResourcesForApplicationAsUser(String appPackageName, int userId)
throws NameNotFoundException {
return null;
@@ -1837,7 +1854,7 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager {
protected void installPackage(
Object packageURI, Object observer, Object flags, Object installerPackageName) {}
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
protected int installExistingPackage(String packageName) throws NameNotFoundException {
return 0;
}
@@ -1989,7 +2006,7 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager {
clearPackagePreferredActivitiesInternal(packageName, preferredActivities);
}
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected ComponentName getHomeActivities(List<ResolveInfo> outActivities) {
return null;
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscApkAssets9.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscApkAssets9.java
index 9ab0887c7..13e95f947 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscApkAssets9.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscApkAssets9.java
@@ -197,6 +197,26 @@ public class ShadowArscApkAssets9 extends ShadowApkAssets {
// return ShadowArscAssetManager9.NATIVE_APK_ASSETS_REGISTRY.getNativeObjectId(apk_assets);
}
+ // static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, const format_type_t format,
+ // jobject file_descriptor, jstring friendly_name,
+ // const jint property_flags, jobject assets_provider)
+ @Implementation(minSdk = R)
+ protected static Object nativeLoadFd(
+ Object format,
+ Object fileDescriptor,
+ Object friendlyName,
+ Object propertyFlags,
+ Object assetsProvider)
+ throws IOException {
+ CppApkAssets apkAssets = CppApkAssets.loadArscFromFd((FileDescriptor) fileDescriptor);
+ if (apkAssets == null) {
+ String errorMessage =
+ String.format("Failed to load from the file descriptor %s", fileDescriptor);
+ throw new IOException(errorMessage);
+ }
+ return Registries.NATIVE_APK_ASSETS_REGISTRY.register(apkAssets);
+ }
+
// static jstring NativeGetAssetPath(JNIEnv* env, jclass /*clazz*/, jlong ptr) {
@Implementation
protected static String nativeGetAssetPath(long ptr) {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager.java
index c0c9c7a8e..6025ee4eb 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
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;
@@ -265,21 +264,23 @@ public class ShadowArscAssetManager extends ShadowAssetManager.ArscBase {
//////////// native method implementations
-// public native final String[] list(String path)
-// throws IOException;
+ // public native final String[] list(String path)
+ // throws IOException;
-// @HiddenApi @Implementation(minSdk = VERSION_CODES.P)
-// public void setApkAssets(Object apkAssetsObjects, Object invalidateCaches) {
-// throw new UnsupportedOperationException("implement me");
-// }
-//
+ // @HiddenApi @Implementation(minSdk = VERSION_CODES.P)
+ // public void setApkAssets(Object apkAssetsObjects, Object invalidateCaches) {
+ // throw new UnsupportedOperationException("implement me");
+ // }
+ //
- @HiddenApi @Implementation(maxSdk = VERSION_CODES.JELLY_BEAN_MR1)
+ @HiddenApi
+ @Implementation(maxSdk = VERSION_CODES.JELLY_BEAN_MR1)
public int addAssetPath(String path) {
return addAssetPathNative(path);
}
- @HiddenApi @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = M)
+ @HiddenApi
+ @Implementation(maxSdk = M)
final protected int addAssetPathNative(String path) {
return addAssetPathNative(path, false);
}
@@ -1375,6 +1376,12 @@ public class ShadowArscAssetManager extends ShadowAssetManager.ArscBase {
return paths;
}
+ @VisibleForTesting
+ @Override
+ long getNativePtr() {
+ return reflector(_AssetManager_.class, realObject).getNativePtr();
+ }
+
@Override
List<AssetPath> getAssetPaths() {
return assetManagerForJavaObject().getAssetPaths();
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager10.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager10.java
index aa0272ab1..bc5200e97 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager10.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager10.java
@@ -21,7 +21,6 @@ import static org.robolectric.res.android.Util.CHECK;
import static org.robolectric.res.android.Util.JNI_FALSE;
import static org.robolectric.res.android.Util.JNI_TRUE;
import static org.robolectric.res.android.Util.isTruthy;
-import static org.robolectric.shadow.api.Shadow.invokeConstructor;
import static org.robolectric.util.reflector.Reflector.reflector;
import android.annotation.AnyRes;
@@ -38,6 +37,7 @@ import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.util.SparseArray;
import android.util.TypedValue;
+import com.google.common.annotations.VisibleForTesting;
import dalvik.system.VMRuntime;
import java.io.File;
import java.io.FileDescriptor;
@@ -76,10 +76,11 @@ import org.robolectric.res.android.ResourceTypes.Res_value;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.PerfStatsCollector;
import org.robolectric.util.ReflectionHelpers;
-import org.robolectric.util.ReflectionHelpers.ClassParameter;
import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;
import org.robolectric.util.reflector.Static;
+import org.robolectric.versioning.AndroidVersions.U;
+
// TODO: update path to released version.
// transliterated from
// https://android.googlesource.com/platform/frameworks/base/+/android-10.0.0_rXX/core/jni/android_util_AssetManager.cpp
@@ -105,7 +106,7 @@ public class ShadowArscAssetManager10 extends ShadowAssetManager.ArscBase {
private static CppAssetManager2 systemCppAssetManager2;
private static long systemCppAssetManager2Ref;
- private static boolean inNonSystemConstructor;
+ private static boolean inResourcesGetSystem;
@RealObject AssetManager realAssetManager;
@@ -229,6 +230,12 @@ public class ShadowArscAssetManager10 extends ShadowAssetManager.ArscBase {
return ApkAssetsCookie.forInt(cookie > 0 ? (cookie - 1) : kInvalidCookie);
}
+ @VisibleForTesting
+ @Override
+ long getNativePtr() {
+ return reflector(_AssetManager_.class, realAssetManager).getNativePtr();
+ }
+
// This is called by zygote (running as user root) as part of preloadResources.
// static void NativeVerifySystemIdmaps(JNIEnv* /*env*/, jclass /*clazz*/) {
@Implementation(minSdk = P, maxSdk = Q)
@@ -440,16 +447,15 @@ public class ShadowArscAssetManager10 extends ShadowAssetManager.ArscBase {
return ParcelFileDescriptor.open(asset.getFile(), ParcelFileDescriptor.MODE_READ_ONLY);
}
- /** Used for the creation of system assets. */
@Implementation(minSdk = P)
- protected void __constructor__(boolean sentinel) {
- inNonSystemConstructor = true;
+ protected static AssetManager getSystem() {
+ // The Android code of AssetManager.getSystem is locked on a static variable, so there is not
+ // a concurrency concern here.
+ inResourcesGetSystem = true;
try {
- // call real constructor so field initialization happens.
- invokeConstructor(
- AssetManager.class, realAssetManager, ClassParameter.from(boolean.class, sentinel));
+ return reflector(_AssetManager_.class).getSystem();
} finally {
- inNonSystemConstructor = false;
+ inResourcesGetSystem = false;
}
}
@@ -487,17 +493,17 @@ public class ShadowArscAssetManager10 extends ShadowAssetManager.ArscBase {
long cppAssetManagerRef;
// we want to share a single instance of the system CppAssetManager2
- if (inNonSystemConstructor) {
- CppAssetManager2 appAssetManager = new CppAssetManager2();
- cppAssetManagerRef = Registries.NATIVE_ASSET_MANAGER_REGISTRY.register(appAssetManager);
- } else {
+ if (inResourcesGetSystem) {
if (systemCppAssetManager2 == null) {
systemCppAssetManager2 = new CppAssetManager2();
systemCppAssetManager2Ref =
Registries.NATIVE_ASSET_MANAGER_REGISTRY.register(systemCppAssetManager2);
}
-
cppAssetManagerRef = systemCppAssetManager2Ref;
+
+ } else {
+ CppAssetManager2 appAssetManager = new CppAssetManager2();
+ cppAssetManagerRef = Registries.NATIVE_ASSET_MANAGER_REGISTRY.register(appAssetManager);
}
return cppAssetManagerRef;
@@ -517,7 +523,7 @@ public class ShadowArscAssetManager10 extends ShadowAssetManager.ArscBase {
// static void NativeSetApkAssets(JNIEnv* env, jclass /*clazz*/, jlong ptr,
// jobjectArray apk_assets_array, jboolean invalidate_caches) {
- @Implementation(minSdk = P)
+ @Implementation(minSdk = P, maxSdk = U.SDK_INT)
protected static void nativeSetApkAssets(
long ptr,
@NonNull android.content.res.ApkAssets[] apk_assets_array,
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager9.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager9.java
index 36d40a8ed..9bbe660c7 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager9.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowArscAssetManager9.java
@@ -20,7 +20,6 @@ import static org.robolectric.res.android.Util.CHECK;
import static org.robolectric.res.android.Util.JNI_FALSE;
import static org.robolectric.res.android.Util.JNI_TRUE;
import static org.robolectric.res.android.Util.isTruthy;
-import static org.robolectric.shadow.api.Shadow.invokeConstructor;
import static org.robolectric.util.reflector.Reflector.reflector;
import android.annotation.AnyRes;
@@ -37,6 +36,7 @@ import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.util.SparseArray;
import android.util.TypedValue;
+import com.google.common.annotations.VisibleForTesting;
import dalvik.system.VMRuntime;
import java.io.File;
import java.io.FileDescriptor;
@@ -75,10 +75,11 @@ import org.robolectric.res.android.ResourceTypes.Res_value;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.PerfStatsCollector;
import org.robolectric.util.ReflectionHelpers;
-import org.robolectric.util.ReflectionHelpers.ClassParameter;
import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;
import org.robolectric.util.reflector.Static;
+import org.robolectric.versioning.AndroidVersions.U;
+
// transliterated from
// https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/core/jni/android_util_AssetManager.cpp
@@ -99,7 +100,7 @@ public class ShadowArscAssetManager9 extends ShadowAssetManager.ArscBase {
private static CppAssetManager2 systemCppAssetManager2;
private static long systemCppAssetManager2Ref;
- private static boolean inNonSystemConstructor;
+ private static boolean inResourcesGetSystem;
@RealObject AssetManager realAssetManager;
@@ -223,6 +224,12 @@ public class ShadowArscAssetManager9 extends ShadowAssetManager.ArscBase {
return ApkAssetsCookie.forInt(cookie > 0 ? (cookie - 1) : kInvalidCookie);
}
+ @VisibleForTesting
+ @Override
+ long getNativePtr() {
+ return reflector(_AssetManager_.class, realAssetManager).getNativePtr();
+ }
+
// This is called by zygote (running as user root) as part of preloadResources.
// static void NativeVerifySystemIdmaps(JNIEnv* /*env*/, jclass /*clazz*/) {
@Implementation(minSdk = P, maxSdk = Q)
@@ -434,16 +441,15 @@ public class ShadowArscAssetManager9 extends ShadowAssetManager.ArscBase {
return ParcelFileDescriptor.open(asset.getFile(), ParcelFileDescriptor.MODE_READ_ONLY);
}
- /** Used for the creation of system assets. */
@Implementation(minSdk = P)
- protected void __constructor__(boolean sentinel) {
- inNonSystemConstructor = true;
+ protected static AssetManager getSystem() {
+ // The Android code of AssetManager.getSystem is locked on a static variable, so there is not
+ // a concurrency concern here.
+ inResourcesGetSystem = true;
try {
- // call real constructor so field initialization happens.
- invokeConstructor(
- AssetManager.class, realAssetManager, ClassParameter.from(boolean.class, sentinel));
+ return reflector(_AssetManager_.class).getSystem();
} finally {
- inNonSystemConstructor = false;
+ inResourcesGetSystem = false;
}
}
@@ -481,10 +487,8 @@ public class ShadowArscAssetManager9 extends ShadowAssetManager.ArscBase {
long cppAssetManagerRef;
// we want to share a single instance of the system CppAssetManager2
- if (inNonSystemConstructor) {
- CppAssetManager2 appAssetManager = new CppAssetManager2();
- cppAssetManagerRef = Registries.NATIVE_ASSET_MANAGER_REGISTRY.register(appAssetManager);
- } else {
+
+ if (inResourcesGetSystem) {
if (systemCppAssetManager2 == null) {
systemCppAssetManager2 = new CppAssetManager2();
systemCppAssetManager2Ref =
@@ -492,6 +496,9 @@ public class ShadowArscAssetManager9 extends ShadowAssetManager.ArscBase {
}
cppAssetManagerRef = systemCppAssetManager2Ref;
+ } else {
+ CppAssetManager2 appAssetManager = new CppAssetManager2();
+ cppAssetManagerRef = Registries.NATIVE_ASSET_MANAGER_REGISTRY.register(appAssetManager);
}
return cppAssetManagerRef;
@@ -511,7 +518,7 @@ public class ShadowArscAssetManager9 extends ShadowAssetManager.ArscBase {
// static void NativeSetApkAssets(JNIEnv* env, jclass /*clazz*/, jlong ptr,
// jobjectArray apk_assets_array, jboolean invalidate_caches) {
- @Implementation(minSdk = P)
+ @Implementation(minSdk = P, maxSdk = U.SDK_INT)
protected static void nativeSetApkAssets(
long ptr,
@NonNull android.content.res.ApkAssets[] apk_assets_array,
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAssetManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAssetManager.java
index 19c5196f0..37ba3ec4f 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAssetManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAssetManager.java
@@ -1,8 +1,10 @@
package org.robolectric.shadows;
+
import android.content.res.ApkAssets;
import android.content.res.AssetManager;
import android.util.ArraySet;
+import com.google.common.annotations.VisibleForTesting;
import java.nio.file.Path;
import java.util.Collection;
import java.util.List;
@@ -13,6 +15,7 @@ import org.robolectric.res.android.ResTable;
import org.robolectric.res.android.String8;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.reflector.Accessor;
+import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;
import org.robolectric.util.reflector.Static;
@@ -48,6 +51,9 @@ abstract public class ShadowAssetManager {
abstract Collection<Path> getAllAssetDirs();
+ @VisibleForTesting
+ abstract long getNativePtr();
+
public abstract static class ArscBase extends ShadowAssetManager {
private ResTable compileTimeResTable;
@@ -78,12 +84,16 @@ abstract public class ShadowAssetManager {
/** Accessor interface for {@link AssetManager}'s internals. */
@ForType(AssetManager.class)
interface _AssetManager_ {
-
- @Static @Accessor("sSystem")
+ @Direct
+ @Static
AssetManager getSystem();
- @Static @Accessor("sSystem")
+ @Static
+ @Accessor("sSystem")
void setSystem(AssetManager o);
+
+ @Accessor("mObject")
+ long getNativePtr();
}
/** Accessor interface for {@link AssetManager}'s internals added in API level 28. */
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAudioEffect.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAudioEffect.java
index a0997fd79..f98a8e875 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAudioEffect.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAudioEffect.java
@@ -38,7 +38,7 @@ public class ShadowAudioEffect {
private boolean isEnabled = false;
private int errorCode = SUCCESS;
- @Implementation(minSdk = VERSION_CODES.JELLY_BEAN, maxSdk = VERSION_CODES.LOLLIPOP_MR1)
+ @Implementation(maxSdk = VERSION_CODES.LOLLIPOP_MR1)
protected int native_setup(
Object audioEffectThis,
String type,
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAudioManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAudioManager.java
index 0f4cb5f98..c2e7e21c9 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAudioManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAudioManager.java
@@ -1,6 +1,5 @@
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.M;
import static android.os.Build.VERSION_CODES.N;
@@ -9,6 +8,8 @@ import static android.os.Build.VERSION_CODES.P;
import static android.os.Build.VERSION_CODES.Q;
import static android.os.Build.VERSION_CODES.R;
import static android.os.Build.VERSION_CODES.S;
+import static android.os.Build.VERSION_CODES.TIRAMISU;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
import static org.robolectric.util.ReflectionHelpers.ClassParameter.from;
import static org.robolectric.util.reflector.Reflector.reflector;
@@ -21,6 +22,7 @@ import android.media.AudioDeviceInfo;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioPlaybackConfiguration;
+import android.media.AudioProfile;
import android.media.AudioRecordingConfiguration;
import android.media.IPlayer;
import android.media.PlayerBase;
@@ -31,6 +33,7 @@ import android.os.Parcel;
import android.view.KeyEvent;
import com.android.internal.util.Preconditions;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -65,7 +68,8 @@ public class ShadowAudioManager {
AudioManager.STREAM_RING,
AudioManager.STREAM_SYSTEM,
AudioManager.STREAM_VOICE_CALL,
- AudioManager.STREAM_DTMF);
+ AudioManager.STREAM_DTMF,
+ AudioManager.STREAM_ACCESSIBILITY);
private static final int INVALID_PATCH_HANDLE = -1;
private static final float MAX_VOLUME_DB = 0;
@@ -85,6 +89,7 @@ public class ShadowAudioManager {
private final HashSet<AudioDeviceCallback> audioDeviceCallbacks = new HashSet<>();
private int ringerMode = AudioManager.RINGER_MODE_NORMAL;
private int mode = AudioManager.MODE_NORMAL;
+ private boolean lockMode = false;
private boolean bluetoothA2dpOn;
private boolean isBluetoothScoOn;
private boolean isSpeakerphoneOn;
@@ -97,12 +102,18 @@ public class ShadowAudioManager {
private final Map<String, AudioPolicy> registeredAudioPolicies = new HashMap<>();
private int audioSessionIdCounter = 1;
private final Map<AudioAttributes, ImmutableList<Object>> devicesForAttributes = new HashMap<>();
+ private final List<AudioDeviceInfo> outputDevicesWithDirectProfiles = new ArrayList<>();
private ImmutableList<Object> defaultDevicesForAttributes = ImmutableList.of();
+ private final Map<AudioAttributes, ImmutableList<AudioDeviceInfo>> audioDevicesForAttributes =
+ new HashMap<>();
private List<AudioDeviceInfo> inputDevices = new ArrayList<>();
private List<AudioDeviceInfo> outputDevices = new ArrayList<>();
private List<AudioDeviceInfo> availableCommunicationDevices = new ArrayList<>();
private AudioDeviceInfo communicationDevice = null;
+ private boolean lockCommunicationDevice = false;
private final List<KeyEvent> dispatchedMediaKeyEvents = new ArrayList<>();
+ private boolean isHotwordStreamSupportedForLookbackAudio = false;
+ private boolean isHotwordStreamSupportedWithoutLookbackAudio = false;
public ShadowAudioManager() {
for (int stream : ALL_STREAMS) {
@@ -208,8 +219,12 @@ public class ShadowAudioManager {
<= (int) ReflectionHelpers.getStaticField(AudioManager.class, "RINGER_MODE_MAX");
}
+ /** Note that this method can silently fail. See {@link lockMode}. */
@Implementation
protected void setMode(int mode) {
+ if (lockMode) {
+ return;
+ }
int previousMode = this.mode;
this.mode = mode;
if (RuntimeEnvironment.getApiLevel() >= S && mode != previousMode) {
@@ -224,6 +239,11 @@ public class ShadowAudioManager {
.dispatchAudioModeChanged(newMode);
}
+ /** Sets whether subsequent calls to {@link setMode} will succeed or not. */
+ public void lockMode(boolean lockMode) {
+ this.lockMode = lockMode;
+ }
+
@Implementation
protected int getMode() {
return this.mode;
@@ -236,6 +256,7 @@ public class ShadowAudioManager {
void dispatchAudioModeChanged(int newMode);
}
+
public void setStreamMaxVolume(int streamMaxVolume) {
streamStatus.forEach((key, value) -> value.setMaxVolume(streamMaxVolume));
}
@@ -448,6 +469,27 @@ public class ShadowAudioManager {
}
/**
+ * Returns the audio devices that would be used for the routing of the given audio attributes.
+ *
+ * <p>Devices can be added with {@link #setAudioDevicesForAttributes}. Note that {@link
+ * #setDevicesForAttributes} and {@link #setDefaultDevicesForAttributes} have no effect on the
+ * return value of this method.
+ */
+ @Implementation(minSdk = TIRAMISU)
+ @NonNull
+ protected List<AudioDeviceInfo> getAudioDevicesForAttributes(
+ @NonNull AudioAttributes attributes) {
+ ImmutableList<AudioDeviceInfo> devices = audioDevicesForAttributes.get(attributes);
+ return devices == null ? ImmutableList.of() : devices;
+ }
+
+ /** Sets the audio devices returned from {@link #getAudioDevicesForAttributes}. */
+ public void setAudioDevicesForAttributes(
+ @NonNull AudioAttributes attributes, @NonNull ImmutableList<AudioDeviceInfo> devices) {
+ audioDevicesForAttributes.put(attributes, devices);
+ }
+
+ /**
* Sets the list of connected input devices represented by {@link AudioDeviceInfo}.
*
* <p>The previous list of input devices is replaced and no notifications of the list of {@link
@@ -582,6 +624,8 @@ public class ShadowAudioManager {
* @see #removeInputDevice(AudioDeviceInfo, boolean)
* @see #removeOutputDevice(AudioDeviceInfo, boolean)
* @see #removeAvailableCommunicationDevice(AudioDeviceInfo, boolean)
+ * @see #addOutputDeviceWithDirectProfiles(AudioDeviceInfo)
+ * @see #removeOutputDeviceWithDirectProfiles(AudioDeviceInfo)
*/
@Implementation(minSdk = M)
protected void registerAudioDeviceCallback(AudioDeviceCallback callback, Handler handler) {
@@ -600,6 +644,8 @@ public class ShadowAudioManager {
* @see #removeInputDevice(AudioDeviceInfo, boolean)
* @see #removeOutputDevice(AudioDeviceInfo, boolean)
* @see #removeAvailableCommunicationDevice(AudioDeviceInfo, boolean)
+ * @see #addOutputDeviceWithDirectProfiles(AudioDeviceInfo)
+ * @see #removeOutputDeviceWithDirectProfiles(AudioDeviceInfo)
*/
@Implementation(minSdk = M)
protected void unregisterAudioDeviceCallback(AudioDeviceCallback callback) {
@@ -625,10 +671,18 @@ public class ShadowAudioManager {
return outputDevices;
}
+ /** Note that this method can silently fail. See {@link lockCommunicationDevice}. */
@Implementation(minSdk = S)
protected boolean setCommunicationDevice(AudioDeviceInfo communicationDevice) {
- this.communicationDevice = communicationDevice;
- return true;
+ if (!lockCommunicationDevice) {
+ this.communicationDevice = communicationDevice;
+ }
+ return !lockCommunicationDevice;
+ }
+
+ /** Sets whether subsequent calls to {@link setCommunicationDevice} will succeed. */
+ public void lockCommunicationDevice(boolean lockCommunicationDevice) {
+ this.lockCommunicationDevice = lockCommunicationDevice;
}
@Implementation(minSdk = S)
@@ -646,6 +700,22 @@ public class ShadowAudioManager {
return availableCommunicationDevices;
}
+ @Implementation(minSdk = UPSIDE_DOWN_CAKE)
+ protected boolean isHotwordStreamSupported(boolean lookbackAudio) {
+ if (lookbackAudio) {
+ return isHotwordStreamSupportedForLookbackAudio;
+ }
+ return isHotwordStreamSupportedWithoutLookbackAudio;
+ }
+
+ public void setHotwordStreamSupported(boolean lookbackAudio, boolean isSupported) {
+ if (lookbackAudio) {
+ isHotwordStreamSupportedForLookbackAudio = isSupported;
+ } else {
+ isHotwordStreamSupportedWithoutLookbackAudio = isSupported;
+ }
+ }
+
@Implementation(minSdk = M)
public AudioDeviceInfo[] getDevices(int flags) {
List<AudioDeviceInfo> result = new ArrayList<>();
@@ -862,6 +932,47 @@ public class ShadowAudioManager {
}
/**
+ * Returns the list of profiles supported for direct playback.
+ *
+ * <p>In this shadow-implementation the list returned are profiles set through {@link
+ * #addOutputDeviceWithDirectProfiles(AudioDeviceInfo)}, {@link
+ * #removeOutputDeviceWithDirectProfiles(AudioDeviceInfo)}.
+ */
+ @Implementation(minSdk = TIRAMISU)
+ @NonNull
+ protected List<AudioProfile> getDirectProfilesForAttributes(@NonNull AudioAttributes attributes) {
+ ImmutableSet.Builder<AudioProfile> audioProfiles = new ImmutableSet.Builder<>();
+ for (int i = 0; i < outputDevicesWithDirectProfiles.size(); i++) {
+ audioProfiles.addAll(outputDevicesWithDirectProfiles.get(i).getAudioProfiles());
+ }
+ return new ArrayList<>(audioProfiles.build());
+ }
+
+ /**
+ * Adds an output {@link AudioDeviceInfo device} with direct profiles and notifies the list of
+ * {@link AudioDeviceCallback} if the device was not present before.
+ */
+ public void addOutputDeviceWithDirectProfiles(AudioDeviceInfo outputDevice) {
+ boolean changed =
+ !this.outputDevicesWithDirectProfiles.contains(outputDevice)
+ && this.outputDevicesWithDirectProfiles.add(outputDevice);
+ if (changed) {
+ notifyAudioDeviceCallbacks(ImmutableList.of(outputDevice), /* added= */ true);
+ }
+ }
+
+ /**
+ * Removes an output {@link AudioDeviceInfo device} with direct profiles and notifies the list of
+ * {@link AudioDeviceCallback} if the device was present before.
+ */
+ public void removeOutputDeviceWithDirectProfiles(AudioDeviceInfo outputDevice) {
+ boolean changed = this.outputDevicesWithDirectProfiles.remove(outputDevice);
+ if (changed) {
+ notifyAudioDeviceCallbacks(ImmutableList.of(outputDevice), /* added= */ false);
+ }
+ }
+
+ /**
* Provides a mock like interface for the {@link AudioManager#generateAudioSessionId} method by
* returning positive distinct values, or {@link AudioManager#ERROR} if all possible values have
* already been returned.
@@ -897,7 +1008,7 @@ public class ShadowAudioManager {
* routed to a media app, this shadow method only records the events to be verified through {@link
* #getDispatchedMediaKeyEvents()}.
*/
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected void dispatchMediaKeyEvent(KeyEvent keyEvent) {
if (keyEvent == null) {
throw new NullPointerException("keyEvent is null");
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAudioTrack.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAudioTrack.java
index ecf5f16ea..14be26230 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAudioTrack.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAudioTrack.java
@@ -16,12 +16,17 @@ import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresApi;
import android.media.AudioAttributes;
+import android.media.AudioDeviceInfo;
import android.media.AudioFormat;
+import android.media.AudioRouting.OnRoutingChangedListener;
import android.media.AudioTrack;
import android.media.AudioTrack.WriteMode;
import android.media.PlaybackParams;
import android.os.Build.VERSION;
+import android.os.Handler;
import android.os.Parcel;
import android.util.Log;
import com.google.common.collect.HashMultimap;
@@ -31,8 +36,10 @@ import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CopyOnWriteArraySet;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
@@ -80,6 +87,9 @@ public class ShadowAudioTrack {
private static final Set<Integer> allowedNonPcmEncodings =
Collections.synchronizedSet(new HashSet<>());
+ private static AudioDeviceInfo routedDevice;
+ private static final Set<OnRoutingChangedListenerInfo> onRoutingChangedListeners =
+ new CopyOnWriteArraySet<>();
private static final List<OnAudioDataWrittenListener> audioDataWrittenListeners =
new CopyOnWriteArrayList<>();
private static int minBufferSize = DEFAULT_MIN_BUFFER_SIZE;
@@ -153,6 +163,25 @@ public class ShadowAudioTrack {
allowedNonPcmEncodings.clear();
}
+ /**
+ * Sets the routed device returned from {@link AudioTrack#getRoutedDevice()} and informs all
+ * registered {@link OnRoutingChangedListener}.
+ *
+ * <p>Note that this affects the routed device for all {@link AudioTrack} instances.
+ *
+ * @param routedDevice The route device, or null to reset it to unknown.
+ */
+ @RequiresApi(N)
+ public static void setRoutedDevice(@Nullable AudioDeviceInfo routedDevice) {
+ if (Objects.equals(routedDevice, ShadowAudioTrack.routedDevice)) {
+ return;
+ }
+ ShadowAudioTrack.routedDevice = routedDevice;
+ for (OnRoutingChangedListenerInfo listenerInfo : onRoutingChangedListeners) {
+ listenerInfo.callListener();
+ }
+ }
+
@Implementation(minSdk = N, maxSdk = P)
protected static int native_get_FCC_8() {
// Return the value hard-coded in native code:
@@ -289,6 +318,28 @@ public class ShadowAudioTrack {
return sizeInBytes;
}
+ @Implementation(minSdk = N)
+ protected AudioDeviceInfo getRoutedDevice() {
+ return routedDevice;
+ }
+
+ @Implementation(minSdk = N)
+ protected void addOnRoutingChangedListener(
+ @NonNull OnRoutingChangedListener listener, Handler handler) {
+ OnRoutingChangedListenerInfo listenerInfo =
+ new OnRoutingChangedListenerInfo(listener, audioTrack, handler);
+ onRoutingChangedListeners.add(listenerInfo);
+ if (routedDevice != null) {
+ listenerInfo.callListener();
+ }
+ }
+
+ @Implementation(minSdk = N)
+ protected void removeOnRoutingChangedListener(@NonNull OnRoutingChangedListener listener) {
+ onRoutingChangedListeners.removeIf(
+ registeredListener -> registeredListener.listener.equals(listener));
+ }
+
@Implementation(minSdk = M)
public void setPlaybackParams(@NonNull PlaybackParams params) {
playbackParams = checkNotNull(params, "Illegal null params");
@@ -369,6 +420,7 @@ public class ShadowAudioTrack {
audioDataWrittenListeners.clear();
clearDirectPlaybackSupportedFormats();
clearAllowedNonPcmEncodings();
+ routedDevice = null;
}
private static boolean isPcm(int encoding) {
@@ -466,4 +518,21 @@ public class ShadowAudioTrack {
return result;
}
}
+
+ private static final class OnRoutingChangedListenerInfo {
+ private final OnRoutingChangedListener listener;
+ private final AudioTrack audioTrack;
+ private final Handler handler;
+
+ public OnRoutingChangedListenerInfo(
+ OnRoutingChangedListener listener, AudioTrack audioTrack, Handler handler) {
+ this.listener = listener;
+ this.audioTrack = audioTrack;
+ this.handler = handler;
+ }
+
+ public void callListener() {
+ handler.post(() -> listener.onRoutingChanged(audioTrack));
+ }
+ }
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAutofillManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAutofillManager.java
index fea9f9e26..1a14a1d09 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAutofillManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAutofillManager.java
@@ -3,10 +3,10 @@ package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.P;
+import android.annotation.Nullable;
import android.content.ComponentName;
import android.service.autofill.FillEventHistory;
import android.view.autofill.AutofillManager;
-import androidx.annotation.Nullable;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBackgroundThread.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBackgroundThread.java
index 90e81ace2..2273a03e5 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBackgroundThread.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBackgroundThread.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static org.robolectric.util.reflector.Reflector.reflector;
import android.os.Handler;
@@ -11,7 +10,7 @@ import org.robolectric.util.reflector.Accessor;
import org.robolectric.util.reflector.ForType;
import org.robolectric.util.reflector.Static;
-@Implements(value = BackgroundThread.class, isInAndroidSdk = false, minSdk = KITKAT)
+@Implements(value = BackgroundThread.class, isInAndroidSdk = false)
public class ShadowBackgroundThread {
@Resetter
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBackupDataOutput.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBackupDataOutput.java
index 9c48306f2..b82f7c32c 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBackupDataOutput.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBackupDataOutput.java
@@ -1,8 +1,8 @@
package org.robolectric.shadows;
+import android.annotation.Nullable;
import android.app.backup.BackupDataOutput;
import android.os.Build.VERSION_CODES;
-import androidx.annotation.Nullable;
import com.google.common.collect.ImmutableList;
import java.io.FileDescriptor;
import java.util.ArrayList;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBackupManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBackupManager.java
index 42195f5eb..f0efc26a4 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBackupManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBackupManager.java
@@ -4,6 +4,7 @@ import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.M;
import android.app.backup.BackupManager;
+import android.app.backup.BackupTransport;
import android.app.backup.IBackupManagerMonitor;
import android.app.backup.IRestoreObserver;
import android.app.backup.IRestoreSession;
@@ -14,6 +15,8 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ListMultimap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -33,8 +36,9 @@ import org.robolectric.util.ReflectionHelpers.ClassParameter;
/**
* A stub implementation of {@link BackupManager} that instead of connecting to a real backup
- * transport and performing restores, stores which packages are restored from which backup set, and
- * can be verified using methods on the shadow like {@link #getPackageRestoreToken(String)}.
+ * transport and performing restores, stores which packages are restored from which backup set, what
+ * the final result should be and can be verified using methods on the shadow like {@link
+ * #getPackageRestoreToken(String)} and {@link #getPackageRestoreCount(String)}.
*/
@Implements(BackupManager.class)
public class ShadowBackupManager {
@@ -103,16 +107,27 @@ public class ShadowBackupManager {
}
/**
- * Returns the restore token for the given package, or {@code 0} if the package was not restored.
+ * Returns the last recorded restore token for the given package, or {@code 0} if the package was
+ * not restored.
*/
public long getPackageRestoreToken(String packageName) {
- Long token = serviceState.restoredPackages.get(packageName);
- return token != null ? token : 0L;
+ List<Long> result = serviceState.restoredPackages.get(packageName);
+ return result.isEmpty() ? 0L : result.get(result.size() - 1);
}
- /** Adds a restore set available to be restored. */
+ /** Returns the number of recorded restores for the given package. */
+ public int getPackageRestoreCount(String packageName) {
+ return serviceState.restoredPackages.get(packageName).size();
+ }
+
+ /** Adds a restore set available to be restored successfully. */
public void addAvailableRestoreSets(long restoreToken, List<String> packages) {
- serviceState.restoreData.put(restoreToken, packages);
+ addAvailableRestoreSets(restoreToken, packages, BackupTransport.TRANSPORT_OK);
+ }
+
+ /** Adds a restore set available to be restored and the final result of the restore session. */
+ public void addAvailableRestoreSets(long restoreToken, List<String> packages, int result) {
+ serviceState.restoreData.put(restoreToken, new RestoreData(packages, result));
}
private void enforceBackupPermission(String message) {
@@ -137,7 +152,7 @@ public class ShadowBackupManager {
for (long token : restoreTokens) {
restoreSets.add(new RestoreSet("RestoreSet-" + token, "device", token));
}
- observer.restoreSetsAvailable(restoreSets.toArray(new RestoreSet[restoreSets.size()]));
+ observer.restoreSetsAvailable(restoreSets.toArray(new RestoreSet[0]));
});
return BackupManager.SUCCESS;
}
@@ -166,10 +181,12 @@ public class ShadowBackupManager {
return restorePackages(token, observer, packages, monitor);
}
+ @Override
public int restorePackages(
long token, IRestoreObserver observer, String[] packages, IBackupManagerMonitor monitor)
throws RemoteException {
- List<String> restorePackages = new ArrayList<>(serviceState.restoreData.get(token));
+ RestoreData restoreData = serviceState.takeRestoreData(token);
+ List<String> restorePackages = new ArrayList<>(restoreData.packages);
if (packages != null) {
restorePackages.retainAll(Arrays.asList(packages));
}
@@ -179,7 +196,7 @@ public class ShadowBackupManager {
post(() -> observer.onUpdate(index, restorePackages.get(index)));
serviceState.restoredPackages.put(restorePackages.get(index), token);
}
- post(() -> observer.restoreFinished(BackupManager.SUCCESS));
+ post(() -> observer.restoreFinished(restoreData.result));
serviceState.lastRestoreToken = token;
return BackupManager.SUCCESS;
}
@@ -197,14 +214,15 @@ public class ShadowBackupManager {
if (serviceState.lastRestoreToken == 0L) {
return -1;
}
- List<String> restorePackages = serviceState.restoreData.get(serviceState.lastRestoreToken);
+ RestoreData restoreData = serviceState.takeRestoreData(serviceState.lastRestoreToken);
+ List<String> restorePackages = new ArrayList<>(restoreData.packages);
if (!restorePackages.contains(packageName)) {
return BackupManager.ERROR_PACKAGE_NOT_FOUND;
}
post(() -> observer.restoreStarting(1));
post(() -> observer.onUpdate(0, packageName));
serviceState.restoredPackages.put(packageName, serviceState.lastRestoreToken);
- post(() -> observer.restoreFinished(BackupManager.SUCCESS));
+ post(() -> observer.restoreFinished(restoreData.result));
return BackupManager.SUCCESS;
}
@@ -235,8 +253,34 @@ public class ShadowBackupManager {
boolean backupEnabled = true;
long lastRestoreToken = 0L;
final Map<String, Integer> dataChangedCount = new HashMap<>();
- final Map<Long, List<String>> restoreData = new HashMap<>();
- final Map<String, Long> restoredPackages = new HashMap<>();
+ final ListMultimap<Long, RestoreData> restoreData = ArrayListMultimap.create();
+ final ListMultimap<String, Long> restoredPackages = ArrayListMultimap.create();
+
+ /**
+ * Returns the first {@link RestoreData} matching the given token and removes it from the
+ * existing restore data if it's not the last one.
+ */
+ RestoreData takeRestoreData(long token) {
+ List<RestoreData> results = restoreData.get(token);
+ if (results.isEmpty()) {
+ return new RestoreData(new ArrayList<>(), -1);
+ }
+ RestoreData data = results.get(0);
+ if (results.size() > 1) {
+ restoreData.remove(token, data);
+ }
+ return data;
+ }
+ }
+
+ private static class RestoreData {
+ final List<String> packages;
+ final int result;
+
+ public RestoreData(List<String> packages, int result) {
+ this.packages = packages;
+ this.result = result;
+ }
}
private interface RemoteRunnable {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBinder.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBinder.java
index bb0cd86f3..e62796b32 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBinder.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBinder.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static android.os.Build.VERSION_CODES.Q;
import android.os.Binder;
@@ -80,7 +79,7 @@ public class ShadowBinder {
throw new IllegalStateException("Thread is not in a binder transcation");
}
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
protected static UserHandle getCallingUserHandle() {
if (callingUserHandle != null) {
return callingUserHandle;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBitmap.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBitmap.java
index ca55a0657..931be2006 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBitmap.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBitmap.java
@@ -20,7 +20,7 @@ import org.robolectric.versioning.AndroidVersions.U;
@Implements(value = Bitmap.class, shadowPicker = Picker.class, looseSignatures = true)
public abstract class ShadowBitmap {
- @RealObject Bitmap realBitmap;
+ @RealObject protected Bitmap realBitmap;
/**
* Returns a textual representation of the appearance of the object.
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBitmapFactory.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBitmapFactory.java
index fd2c084a5..207228df3 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBitmapFactory.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBitmapFactory.java
@@ -12,7 +12,6 @@ import android.graphics.BitmapFactory;
import android.graphics.Point;
import android.graphics.Rect;
import android.net.Uri;
-import android.os.Build;
import android.util.TypedValue;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
@@ -268,10 +267,8 @@ public class ShadowBitmapFactory {
p.y = p.y == 0 ? 1 : p.y;
}
- // Prior to KitKat the density scale will be applied by finishDecode below.
float scale =
- RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.KITKAT
- && options != null
+ options != null
&& options.inScaled
&& options.inDensity != 0
&& options.inTargetDensity != 0
@@ -301,21 +298,11 @@ public class ShadowBitmapFactory {
shadowBitmap.setMutable(options.inMutable);
}
- if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.KITKAT) {
- ReflectionHelpers.callStaticMethod(
- BitmapFactory.class,
- "setDensityFromOptions",
- ClassParameter.from(Bitmap.class, bitmap),
- ClassParameter.from(BitmapFactory.Options.class, options));
- } else {
- bitmap =
- ReflectionHelpers.callStaticMethod(
- BitmapFactory.class,
- "finishDecode",
- ClassParameter.from(Bitmap.class, bitmap),
- ClassParameter.from(Rect.class, outPadding),
- ClassParameter.from(BitmapFactory.Options.class, options));
- }
+ ReflectionHelpers.callStaticMethod(
+ BitmapFactory.class,
+ "setDensityFromOptions",
+ ClassParameter.from(Bitmap.class, bitmap),
+ ClassParameter.from(BitmapFactory.Options.class, options));
return bitmap;
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothA2dp.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothA2dp.java
index 101fee72e..77fd608d8 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothA2dp.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothA2dp.java
@@ -1,13 +1,19 @@
package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.P;
+import static android.os.Build.VERSION_CODES.R;
import static android.os.Build.VERSION_CODES.S;
+import static android.os.Build.VERSION_CODES.TIRAMISU;
+import static org.robolectric.util.reflector.Reflector.reflector;
import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothA2dp.OptionalCodecsPreferenceStatus;
import android.bluetooth.BluetoothCodecConfig;
+import android.bluetooth.BluetoothCodecStatus;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Intent;
+import android.util.Log;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.HashMap;
@@ -17,11 +23,22 @@ import javax.annotation.Nullable;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.RealObject;
+import org.robolectric.util.reflector.Direct;
+import org.robolectric.util.reflector.ForType;
/** Shadow of {@link BluetoothA2dp}. */
@Implements(BluetoothA2dp.class)
public class ShadowBluetoothA2dp {
+ private static final String TAG = "BluetoothA2dp";
+
+ @RealObject protected BluetoothA2dp realObject;
+
private final Map<BluetoothDevice, Integer> bluetoothDevices = new HashMap<>();
+ private final Map<BluetoothDevice, BluetoothCodecStatus> codecStatusMap = new HashMap<>();
+ private final Map<BluetoothDevice, BluetoothCodecConfig> codecConfigPreferenceMap =
+ new HashMap<>();
+ private final Map<BluetoothDevice, Integer> optionalCodecPreferenceStatusMap = new HashMap<>();
private int dynamicBufferSupportType = BluetoothA2dp.DYNAMIC_BUFFER_SUPPORT_NONE;
private final int[] bufferLengthMillisArray = new int[6];
private BluetoothDevice activeBluetoothDevice;
@@ -129,4 +146,60 @@ public class ShadowBluetoothA2dp {
RuntimeEnvironment.getApplication().sendBroadcast(intent);
return true;
}
+
+ @Implementation(minSdk = TIRAMISU)
+ @Nullable
+ protected BluetoothCodecStatus getCodecStatus(BluetoothDevice device) {
+ return codecStatusMap.get(device);
+ }
+
+ public void setCodecStatus(BluetoothDevice device, BluetoothCodecStatus codecStatus) {
+ codecStatusMap.put(device, codecStatus);
+ }
+
+ @Nullable
+ public BluetoothCodecConfig getCodecConfigPreference(BluetoothDevice device) {
+ return codecConfigPreferenceMap.get(device);
+ }
+
+ @Implementation(minSdk = TIRAMISU)
+ protected void setCodecConfigPreference(
+ BluetoothDevice device, BluetoothCodecConfig codecConfig) {
+ codecConfigPreferenceMap.put(device, codecConfig);
+ }
+
+ @Implementation(minSdk = R)
+ @OptionalCodecsPreferenceStatus
+ protected int isOptionalCodecsEnabled(BluetoothDevice device) {
+ verifyDeviceNotNull(device, "isOptionalCodecsEnabled");
+ if (optionalCodecPreferenceStatusMap.containsKey(device)) {
+ return optionalCodecPreferenceStatusMap.get(device);
+ } else {
+ return BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN;
+ }
+ }
+
+ @Implementation(minSdk = R)
+ protected void setOptionalCodecsEnabled(
+ BluetoothDevice device, @OptionalCodecsPreferenceStatus int value) {
+ verifyDeviceNotNull(device, "setOptionalCodecsEnabled");
+ if (value != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN
+ && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED
+ && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) {
+ Log.e(TAG, "Invalid value passed to setOptionalCodecsEnabled: " + value);
+ return;
+ }
+ optionalCodecPreferenceStatusMap.put(device, value);
+ }
+
+ @ForType(BluetoothA2dp.class)
+ private interface BluetoothA2dpReflector {
+ @Direct
+ void verifyDeviceNotNull(BluetoothDevice device, String methodName);
+ }
+
+ @Implementation(minSdk = R)
+ protected void verifyDeviceNotNull(BluetoothDevice device, String methodName) {
+ reflector(BluetoothA2dpReflector.class, realObject).verifyDeviceNotNull(device, methodName);
+ }
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothAdapter.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothAdapter.java
index 5e89a77b6..bff4fe2b7 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothAdapter.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothAdapter.java
@@ -1,8 +1,6 @@
package org.robolectric.shadows;
import static android.bluetooth.BluetoothAdapter.STATE_ON;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.O;
@@ -10,6 +8,7 @@ import static android.os.Build.VERSION_CODES.Q;
import static android.os.Build.VERSION_CODES.R;
import static android.os.Build.VERSION_CODES.S_V2;
import static android.os.Build.VERSION_CODES.TIRAMISU;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
import static org.robolectric.Shadows.shadowOf;
import static org.robolectric.util.reflector.Reflector.reflector;
@@ -101,7 +100,10 @@ public class ShadowBluetoothAdapter {
private boolean isBleScanAlwaysAvailable = true;
private boolean isMultipleAdvertisementSupported = true;
private int isLeAudioSupported = BluetoothStatusCodes.FEATURE_NOT_SUPPORTED;
+ private int isDistanceMeasurementSupported = BluetoothStatusCodes.FEATURE_NOT_SUPPORTED;
private boolean isLeExtendedAdvertisingSupported = true;
+ private boolean isLeCodedPhySupported = true;
+ private boolean isLe2MPhySupported = true;
private boolean isOverridingProxyBehavior;
private final Map<Integer, Integer> profileConnectionStateData = new HashMap<>();
private final Map<Integer, BluetoothProfile> profileProxies = new HashMap<>();
@@ -159,6 +161,19 @@ public class ShadowBluetoothAdapter {
}
/**
+ * Sets whether the distance measurement is supported or not. Minimum sdk version required is
+ * UPSIDE_DOWN_CAKE.
+ */
+ public void setDistanceMeasurementSupported(int supported) {
+ isDistanceMeasurementSupported = supported;
+ }
+
+ @Implementation(minSdk = VERSION_CODES.UPSIDE_DOWN_CAKE)
+ protected int isDistanceMeasurementSupported() {
+ return isDistanceMeasurementSupported;
+ }
+
+ /**
* @deprecated use real BluetoothLeAdvertiser instead
*/
@Deprecated
@@ -190,11 +205,16 @@ public class ShadowBluetoothAdapter {
}
@Implementation
+ @Nullable
protected Set<BluetoothDevice> getBondedDevices() {
+ // real android will return null in error conditions
+ if (bondedDevices == null) {
+ return null;
+ }
return Collections.unmodifiableSet(bondedDevices);
}
- public void setBondedDevices(Set<BluetoothDevice> bluetoothDevices) {
+ public void setBondedDevices(@Nullable Set<BluetoothDevice> bluetoothDevices) {
bondedDevices = bluetoothDevices;
}
@@ -277,12 +297,12 @@ public class ShadowBluetoothAdapter {
!= 0;
}
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected boolean startLeScan(LeScanCallback callback) {
return startLeScan(null, callback);
}
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected boolean startLeScan(UUID[] serviceUuids, LeScanCallback callback) {
if (Build.VERSION.SDK_INT >= M && !realAdapter.isLeEnabled()) {
return false;
@@ -293,7 +313,7 @@ public class ShadowBluetoothAdapter {
return true;
}
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected void stopLeScan(LeScanCallback callback) {
leScanCallbacks.remove(callback);
}
@@ -618,6 +638,28 @@ public class ShadowBluetoothAdapter {
isLeExtendedAdvertisingSupported = supported;
}
+ /** Returns the last value of {@link #setIsLeCodedPhySupported}, defaulting to true. */
+ @Implementation(minSdk = UPSIDE_DOWN_CAKE)
+ protected boolean isLeCodedPhySupported() {
+ return isLeCodedPhySupported;
+ }
+
+ /** Sets the {@link #isLeCodedPhySupported} to enable/disable LE coded phy supported featured. */
+ public void setIsLeCodedPhySupported(boolean supported) {
+ isLeCodedPhySupported = supported;
+ }
+
+ /** Returns the last value of {@link #setIsLe2MPhySupported}, defaulting to true. */
+ @Implementation(minSdk = UPSIDE_DOWN_CAKE)
+ protected boolean isLe2MPhySupported() {
+ return isLe2MPhySupported;
+ }
+
+ /** Sets the {@link #isLe2MPhySupported} to enable/disable LE 2M phy supported featured. */
+ public void setIsLe2MPhySupported(boolean supported) {
+ isLe2MPhySupported = supported;
+ }
+
@Implementation(minSdk = O)
protected int getLeMaximumAdvertisingDataLength() {
return isLeExtendedAdvertisingSupported
@@ -630,7 +672,7 @@ public class ShadowBluetoothAdapter {
// PendingIntent#isImmutable throws an NPE if the component does not exist, so verify directly
// against the flags for now.
if ((shadowOf(pendingIntent).getFlags() & PendingIntent.FLAG_IMMUTABLE) == 0) {
- throw new IllegalArgumentException("RFCOMM server PendingIntent must be marked immutable");
+ throw new IllegalArgumentException("RFCOMM servers PendingIntent must be marked immutable");
}
boolean[] isNewServerSocket = {false};
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothDevice.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothDevice.java
index d193b79b4..56b3be662 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothDevice.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothDevice.java
@@ -2,7 +2,6 @@ package org.robolectric.shadows;
import static android.bluetooth.BluetoothDevice.BOND_NONE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.KITKAT_WATCH;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.O;
@@ -30,6 +29,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
+import javax.annotation.Nullable;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
@@ -44,6 +44,15 @@ import org.robolectric.util.reflector.Static;
/** Shadow for {@link BluetoothDevice}. */
@Implements(value = BluetoothDevice.class, looseSignatures = true)
public class ShadowBluetoothDevice {
+ /**
+ * Interceptor interface for {@link BluetoothGatt} objects. Tests that require configuration of
+ * their ShadowBluetoothGatt's may inject an interceptor, which will be called with the newly
+ * constructed BluetoothGatt before {@link ShadowBluetoothGatt#connectGatt} returns.
+ */
+ public static interface BluetoothGattConnectionInterceptor {
+ public void onNewGattConnection(BluetoothGatt gatt);
+ }
+
@Deprecated // Prefer {@link android.bluetooth.BluetoothAdapter#getRemoteDevice}
public static BluetoothDevice newInstance(String address) {
return ReflectionHelpers.callConstructor(
@@ -76,6 +85,7 @@ public class ShadowBluetoothDevice {
private int batteryLevel = BluetoothDevice.BATTERY_LEVEL_BLUETOOTH_OFF;
private boolean isInSilenceMode = false;
private boolean isConnected = false;
+ @Nullable private BluetoothGattConnectionInterceptor bluetoothGattConnectionInterceptor = null;
/**
* Implements getService() in the same way the original method does, but ignores any Exceptions
@@ -180,7 +190,7 @@ public class ShadowBluetoothDevice {
* @return Value set by calling {@link ShadowBluetoothDevice#setType}. If setType has not
* previously been called, will return BluetoothDevice.DEVICE_TYPE_UNKNOWN.
*/
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected int getType() {
checkForBluetoothConnectPermission();
return type;
@@ -319,7 +329,7 @@ public class ShadowBluetoothDevice {
return fetchUuidsWithSdpCount;
}
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected BluetoothGatt connectGatt(
Context context, boolean autoConnect, BluetoothGattCallback callback) {
checkForBluetoothConnectPermission();
@@ -350,6 +360,11 @@ public class ShadowBluetoothDevice {
bluetoothGatts.add(bluetoothGatt);
ShadowBluetoothGatt shadowBluetoothGatt = Shadow.extract(bluetoothGatt);
shadowBluetoothGatt.setGattCallback(callback);
+
+ if (bluetoothGattConnectionInterceptor != null) {
+ bluetoothGattConnectionInterceptor.onNewGattConnection(bluetoothGatt);
+ }
+
return bluetoothGatt;
}
@@ -436,6 +451,15 @@ public class ShadowBluetoothDevice {
return isInSilenceMode;
}
+ /**
+ * Allows tests to intercept the {@link BluetoothDevice.connectGatt} method and set state on both
+ * BluetoothDevice and BluetoothGatt objects. This is useful for e2e testing situations where the
+ * fine-grained execution of Bluetooth connection logic is onerous.
+ */
+ public void setGattConnectionInterceptor(BluetoothGattConnectionInterceptor interceptor) {
+ bluetoothGattConnectionInterceptor = interceptor;
+ }
+
@ForType(BluetoothDevice.class)
interface BluetoothDeviceReflector {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothGatt.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothGatt.java
index 1dce5a3da..3935c8ff7 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothGatt.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothGatt.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.O_MR1;
@@ -15,6 +14,7 @@ import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
+import android.os.Build;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -32,7 +32,7 @@ import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;
/** Shadow implementation of {@link BluetoothGatt}. */
-@Implements(value = BluetoothGatt.class, minSdk = JELLY_BEAN_MR2)
+@Implements(value = BluetoothGatt.class)
public class ShadowBluetoothGatt {
private static final String NULL_CALLBACK_MSG = "BluetoothGattCallback can not be null.";
@@ -126,7 +126,7 @@ public class ShadowBluetoothGatt {
* @return true, if a {@link BluetoothGattCallback} has been set by {@link
* ShadowBluetoothGatt#setGattCallback}
*/
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected boolean connect() {
if (this.getGattCallback() != null) {
this.isConnected = true;
@@ -141,7 +141,7 @@ public class ShadowBluetoothGatt {
/**
* Disconnects an established connection, or cancels a connection attempt currently in progress.
*/
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected void disconnect() {
bluetoothGattReflector.disconnect();
if (this.isCallbackAppropriate()) {
@@ -155,7 +155,7 @@ public class ShadowBluetoothGatt {
}
/** Close this Bluetooth GATT client. */
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected void close() {
bluetoothGattReflector.close();
this.isClosed = true;
@@ -183,6 +183,21 @@ public class ShadowBluetoothGatt {
}
/**
+ * Overrides {@link BluetoothGatt#requestMtu} to always fail before {@link
+ * ShadowBlueoothGatt.setGattCallback} is called, and always succeed after.
+ */
+ @Implementation(minSdk = O)
+ protected boolean requestMtu(int mtu) {
+ if (this.bluetoothGattCallback == null) {
+ return false;
+ }
+
+ this.bluetoothGattCallback.onMtuChanged(
+ this.realBluetoothGatt, mtu, BluetoothGatt.GATT_SUCCESS);
+ return true;
+ }
+
+ /**
* Overrides {@link BluetoothGatt#discoverServices} to always return false unless there are
* discoverable services made available by {@link ShadowBluetoothGatt#addDiscoverableService}
*
@@ -241,7 +256,7 @@ public class ShadowBluetoothGatt {
@Implementation(minSdk = O)
protected boolean setCharacteristicNotification(
BluetoothGattCharacteristic characteristic, boolean enable) {
- return characteristicNotificationEnableSet.contains(characteristic) == enable;
+ return characteristicNotificationEnableSet.contains(characteristic);
}
@Implementation(minSdk = O)
@@ -264,6 +279,17 @@ public class ShadowBluetoothGatt {
return writeIncomingCharacteristic(characteristic);
}
+ @Implementation(minSdk = Build.VERSION_CODES.TIRAMISU)
+ protected int writeCharacteristic(
+ BluetoothGattCharacteristic characteristic, byte[] value, int writeType) {
+ characteristic.setValue(value);
+ boolean writeSuccessCode = writeIncomingCharacteristic(characteristic);
+ if (writeSuccessCode) {
+ return BluetoothGatt.GATT_SUCCESS;
+ }
+ return BluetoothGatt.GATT_FAILURE;
+ }
+
/**
* Reads bytes from incoming characteristic if properties are valid and callback is set. Callback
* responds with {@link BluetoothGattCallback#onCharacteristicWrite} and returns true when
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothGattServer.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothGattServer.java
index 7927da22c..b75885367 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothGattServer.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothGattServer.java
@@ -5,12 +5,17 @@ import static android.os.Build.VERSION_CODES.O;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattServer;
import android.bluetooth.BluetoothGattServerCallback;
+import android.bluetooth.BluetoothGattService;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.UUID;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.ReflectorObject;
@@ -22,8 +27,10 @@ import org.robolectric.util.reflector.ForType;
public class ShadowBluetoothGattServer {
private BluetoothGattServerCallback callback;
private final List<byte[]> responses = new ArrayList<>();
+ private final List<byte[]> writtenBytes = new ArrayList<>();
private final Set<BluetoothDevice> cancelledDevices = new HashSet<>();
private boolean isClosed;
+ private final Set<BluetoothGattService> services = new HashSet<>();
@ReflectorObject protected BluetoothGattServerReflector bluetoothGattServerReflector;
@@ -62,6 +69,53 @@ public class ShadowBluetoothGattServer {
}
/**
+ * Add a service to the GATT server.
+ *
+ * @param service service to be added to GattServer
+ */
+ @Implementation
+ protected boolean addService(BluetoothGattService service) {
+ bluetoothGattServerReflector.addService(service);
+ this.services.add(service);
+ return true;
+ }
+
+ /**
+ * Remove a service from the GATT server.
+ *
+ * @param service service to be removed from GattServer
+ */
+ @Implementation
+ protected boolean removeService(BluetoothGattService service) {
+ return this.services.remove(service);
+ }
+
+ /** Remove all services from the list of provided services. */
+ @Implementation
+ protected void clearServices() {
+ this.services.clear();
+ }
+
+ /** Returns a list of GATT services offered by this device. */
+ @Implementation
+ protected List<BluetoothGattService> getServices() {
+ return ImmutableList.copyOf(this.services);
+ }
+
+ /**
+ * Returns a {@link BluetoothGattService} from the list of services offered by this device.
+ *
+ * <p>If multiple instances of the same service (as identified by UUID) exist, the first instance
+ * of the service is returned.
+ *
+ * @param uuid uuid of service
+ */
+ @Implementation
+ protected BluetoothGattService getService(UUID uuid) {
+ return this.services.stream().filter(s -> s.getUuid().equals(uuid)).findFirst().orElse(null);
+ }
+
+ /**
* Simulate a successful Gatt Server Connection with {@link BluetoothConnectionManager}. Performs
* a {@link BluetoothGattCallback#onConnectionStateChange} if available.
*
@@ -95,6 +149,39 @@ public class ShadowBluetoothGattServer {
}
/**
+ * Simulate a Gatt characteristic write request to the Gatt Server by triggering the server
+ * callback.
+ *
+ * @param device remote device
+ * @param requestId id of the request
+ * @param characteristic characteristic to be written to
+ * @param preparedWrite true, if this write operation should be queued for later execution
+ * @param responseNeeded true, if the remote device requires a response
+ * @param offset the offset given for the value
+ * @param value the value the client wants to assign to the characteristic
+ */
+ public boolean notifyOnCharacteristicWriteRequest(
+ BluetoothDevice device,
+ int requestId,
+ BluetoothGattCharacteristic characteristic,
+ Boolean preparedWrite,
+ Boolean responseNeeded,
+ int offset,
+ byte[] value) {
+ if (this.callback == null) {
+ return false;
+ } else if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) == 0
+ && (characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE)
+ == 0) {
+ return false;
+ }
+ writtenBytes.add(value);
+ this.callback.onCharacteristicWriteRequest(
+ device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
+ return true;
+ }
+
+ /**
* Get whether the device's connection has been cancelled.
*
* @param device remote device
@@ -130,6 +217,16 @@ public class ShadowBluetoothGattServer {
this.responses.clear();
}
+ /** Get a copy of the list of bytes that have been received. */
+ public List<byte[]> getWrittenBytes() {
+ return Lists.transform(this.writtenBytes, bytes -> bytes != null ? bytes.clone() : null);
+ }
+
+ /** Clear the list of written bytes. */
+ public void clearWrittenBytes() {
+ this.writtenBytes.clear();
+ }
+
/** Get whether server has been closed. */
public boolean isClosed() {
return this.isClosed;
@@ -159,5 +256,8 @@ public class ShadowBluetoothGattServer {
@Direct
boolean sendResponse(
BluetoothDevice device, int requestId, int status, int offset, byte[] value);
+
+ @Direct
+ boolean addService(BluetoothGattService service);
}
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothHeadset.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothHeadset.java
index b612fc4d2..f0158dc70 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothHeadset.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothHeadset.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.P;
import static android.os.Build.VERSION_CODES.S;
import static java.util.stream.Collectors.toCollection;
@@ -9,6 +8,9 @@ import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothProfile;
import android.content.Intent;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.primitives.Ints;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -75,6 +77,18 @@ public class ShadowBluetoothHeadset {
return bluetoothDevices.getOrDefault(device, BluetoothProfile.STATE_DISCONNECTED);
}
+ @Implementation
+ protected List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+ ImmutableSet<Integer> statesSet = ImmutableSet.copyOf(Ints.asList(states));
+ List<BluetoothDevice> matchingDevices = new ArrayList<>();
+ for (Map.Entry<BluetoothDevice, Integer> entry : bluetoothDevices.entrySet()) {
+ if (statesSet.contains(entry.getValue())) {
+ matchingDevices.add(entry.getKey());
+ }
+ }
+ return ImmutableList.copyOf(matchingDevices);
+ }
+
/**
* Overrides behavior of {@link connect}. Returns {@code true} and adds {@code device} to the
* shadow profile's connected device list if {@code device} is currently disconnected, and returns
@@ -156,7 +170,7 @@ public class ShadowBluetoothHeadset {
* 'false' argument.
* @throws IllegalArgumentException if 'command' argument is null, per Android API
*/
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected boolean sendVendorSpecificResultCode(
BluetoothDevice device, String command, String arg) {
if (command == null) {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothLeAdvertiser.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothLeAdvertiser.java
index 191eb9e95..10bc66e0a 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothLeAdvertiser.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothLeAdvertiser.java
@@ -3,21 +3,35 @@ package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.R;
import static android.os.Build.VERSION_CODES.S;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGattServer;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.IBluetoothManager;
import android.bluetooth.le.AdvertiseCallback;
import android.bluetooth.le.AdvertiseData;
import android.bluetooth.le.AdvertiseSettings;
+import android.bluetooth.le.AdvertisingSet;
+import android.bluetooth.le.AdvertisingSetCallback;
+import android.bluetooth.le.AdvertisingSetParameters;
import android.bluetooth.le.BluetoothLeAdvertiser;
+import android.bluetooth.le.PeriodicAdvertisingParameters;
+import android.content.AttributionSource;
+import android.os.Handler;
import android.os.ParcelUuid;
+import java.util.HashMap;
import java.util.HashSet;
+import java.util.Map;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.ReflectorObject;
import org.robolectric.util.PerfStatsCollector;
+import org.robolectric.util.ReflectionHelpers;
+import org.robolectric.util.ReflectionHelpers.ClassParameter;
import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;
@@ -34,6 +48,8 @@ public class ShadowBluetoothLeAdvertiser {
private BluetoothAdapter bluetoothAdapter;
private final Set<AdvertiseCallback> advertisements = new HashSet<>();
+ private final Map<AdvertisingSetCallback, AdvertisingSet> advertisingSetMap = new HashMap<>();
+ private final AtomicInteger advertiserId = new AtomicInteger(0);
@ReflectorObject protected BluetoothLeAdvertiserReflector bluetoothLeAdvertiserReflector;
@Implementation(maxSdk = R)
@@ -118,6 +134,142 @@ public class ShadowBluetoothLeAdvertiser {
this.advertisements.remove(callback);
}
+ /**
+ * Start Bluetooth LE Advertising Set. This method returns immediately, the operation status is
+ * delivered through {@code callback}.
+ *
+ * @param parameters Advertising set parameters.
+ * @param advertiseData Advertisement data to be broadcasted.
+ * @param scanResponse Scan response associated with the advertisement data.
+ * @param periodicParameters Periodic advertisng parameters.
+ * @param periodicData Periodic advertising data.
+ * @param duration Advertising duration, in 10ms unit.
+ * @param maxExtendedAdvertisingEvents Maximum number of extended advertising events the
+ * controller shall attempt to send prior to terminating the extended advertising, even if the
+ * duration has not expired.
+ * @param gattServer GattServer the GATT server that will "own" connections derived from this
+ * advertising.
+ * @param callback Callback for advertising set.
+ * @param handler Thread upon which the callbacks will be invoked.
+ * @throws IllegalArgumentException When {@code callback} is not present.
+ */
+ @Implementation(minSdk = UPSIDE_DOWN_CAKE)
+ protected void startAdvertisingSet(
+ AdvertisingSetParameters parameters,
+ AdvertiseData advertiseData,
+ AdvertiseData scanResponse,
+ PeriodicAdvertisingParameters periodicParameters,
+ AdvertiseData periodicData,
+ int duration,
+ int maxExtendedAdvertisingEvents,
+ BluetoothGattServer gattServer,
+ AdvertisingSetCallback callback,
+ Handler handler) {
+ if (callback == null) {
+ throw new IllegalArgumentException("callback cannot be null");
+ }
+
+ boolean isConnectable = parameters.isConnectable();
+ boolean isDiscoverable = parameters.isDiscoverable();
+ boolean hasFlags = isConnectable && isDiscoverable;
+ if (parameters.isLegacy()) {
+ if (getTotalBytes(advertiseData, hasFlags) > MAX_LEGACY_ADVERTISING_DATA_BYTES) {
+ throw new IllegalArgumentException("Legacy advertising data too big");
+ }
+
+ if (getTotalBytes(scanResponse, false) > MAX_LEGACY_ADVERTISING_DATA_BYTES) {
+ throw new IllegalArgumentException("Legacy scan response data too big");
+ }
+ } else {
+ boolean supportCodedPhy = bluetoothAdapter.isLeCodedPhySupported();
+ boolean support2MPhy = bluetoothAdapter.isLe2MPhySupported();
+ int pphy = parameters.getPrimaryPhy();
+ int sphy = parameters.getSecondaryPhy();
+ if (pphy == BluetoothDevice.PHY_LE_CODED && !supportCodedPhy) {
+ throw new IllegalArgumentException("Unsupported primary PHY selected");
+ }
+
+ if ((sphy == BluetoothDevice.PHY_LE_CODED && !supportCodedPhy)
+ || (sphy == BluetoothDevice.PHY_LE_2M && !support2MPhy)) {
+ throw new IllegalArgumentException("Unsupported secondary PHY selected");
+ }
+
+ int maxData = bluetoothAdapter.getLeMaximumAdvertisingDataLength();
+ if (getTotalBytes(advertiseData, hasFlags) > maxData) {
+ throw new IllegalArgumentException("Advertising data too big");
+ }
+
+ if (getTotalBytes(scanResponse, false) > maxData) {
+ throw new IllegalArgumentException("Scan response data too big");
+ }
+
+ if (getTotalBytes(periodicData, false) > maxData) {
+ throw new IllegalArgumentException("Periodic advertising data too big");
+ }
+ }
+
+ if (maxExtendedAdvertisingEvents < 0 || maxExtendedAdvertisingEvents > 255) {
+ throw new IllegalArgumentException(
+ "maxExtendedAdvertisingEvents out of range: " + maxExtendedAdvertisingEvents);
+ }
+
+ if (maxExtendedAdvertisingEvents != 0 && !bluetoothAdapter.isLePeriodicAdvertisingSupported()) {
+ throw new IllegalArgumentException(
+ "Can't use maxExtendedAdvertisingEvents with controller that don't support "
+ + "LE Extended Advertising");
+ }
+
+ if (duration < 0 || duration > 65535) {
+ throw new IllegalArgumentException("duration out of range: " + duration);
+ }
+
+ if (advertisingSetMap.containsKey(callback)) {
+ callback.onAdvertisingSetStarted(
+ /* advertisingSet= */ null,
+ parameters.getTxPowerLevel(),
+ AdvertisingSetCallback.ADVERTISE_FAILED_ALREADY_STARTED);
+ return;
+ }
+
+ AdvertisingSet advertisingSet =
+ ReflectionHelpers.callConstructor(
+ AdvertisingSet.class,
+ ClassParameter.from(int.class, advertiserId.getAndAdd(1)),
+ ClassParameter.from(
+ IBluetoothManager.class,
+ ReflectionHelpers.createNullProxy(IBluetoothManager.class)),
+ ClassParameter.from(
+ AttributionSource.class,
+ ReflectionHelpers.callInstanceMethod(bluetoothAdapter, "getAttributionSource")));
+
+ callback.onAdvertisingSetStarted(
+ advertisingSet, parameters.getTxPowerLevel(), AdvertisingSetCallback.ADVERTISE_SUCCESS);
+
+ advertisingSetMap.put(callback, advertisingSet);
+ }
+
+ /**
+ * Used to dispose of a {@link AdvertisingSet} object, obtained with {@link
+ * BluetoothLeAdvertiser#startAdvertisingSet}.
+ *
+ * @param callback Callback for advertising set.
+ * @throws IllegalArgumentException When {@code callback} is not present.
+ */
+ @Implementation(minSdk = UPSIDE_DOWN_CAKE)
+ protected void stopAdvertisingSet(AdvertisingSetCallback callback) {
+ if (callback == null) {
+ throw new IllegalArgumentException("callback cannot be null");
+ }
+
+ if (!advertisingSetMap.containsKey(callback)) {
+ throw new IllegalArgumentException("callback not found");
+ }
+
+ callback.onAdvertisingSetStopped(advertisingSetMap.get(callback));
+
+ advertisingSetMap.remove(callback);
+ }
+
/** Returns the count of current ongoing Bluetooth LE advertising requests. */
public int getAdvertisementRequestCount() {
return this.advertisements.size();
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothLeScanner.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothLeScanner.java
index d65dfb3b4..a27e1096a 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothLeScanner.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothLeScanner.java
@@ -2,12 +2,14 @@ package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.O;
+import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.Collections.unmodifiableList;
import android.app.PendingIntent;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
@@ -26,9 +28,11 @@ import org.robolectric.annotation.Implements;
/** Adds Robolectric support for BLE scanning. */
@Implements(value = BluetoothLeScanner.class, minSdk = LOLLIPOP)
public class ShadowBluetoothLeScanner {
-
private List<ScanParams> activeScanParams = new ArrayList<>();
+ // Set of ScanResults that will be immediately returned when startScan is called.
+ private final Set<ScanResult> scanResults = new HashSet<>();
+
/**
* Encapsulates scan params passed to {@link android.bluetooth.BluetoothAdapter} startScan
* methods.
@@ -69,11 +73,19 @@ public class ShadowBluetoothLeScanner {
*/
@Implementation
protected void startScan(List<ScanFilter> filters, ScanSettings settings, ScanCallback callback) {
+ checkNotNull(callback);
+
if (filters != null) {
filters = unmodifiableList(filters);
}
activeScanParams.add(ScanParams.create(filters, settings, callback));
+
+ for (ScanResult scanResult : scanResults) {
+ if (filters == null || filters.stream().anyMatch(f -> f.matches(scanResult))) {
+ callback.onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES, scanResult);
+ }
+ }
}
/**
@@ -122,4 +134,9 @@ public class ShadowBluetoothLeScanner {
public List<ScanParams> getActiveScans() {
return Collections.unmodifiableList(activeScanParams);
}
+
+ public void addScanResult(ScanResult scanResult) {
+ checkNotNull(scanResult);
+ this.scanResults.add(scanResult);
+ }
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothManager.java
index db9d1bd62..f69bd1d4f 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothManager.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.R;
import static android.os.Build.VERSION_CODES.S;
@@ -28,7 +27,7 @@ import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.ReflectionHelpers.ClassParameter;
/** Shadow of {@link BluetoothManager} that makes the testing possible. */
-@Implements(value = BluetoothManager.class, minSdk = JELLY_BEAN_MR2)
+@Implements(value = BluetoothManager.class)
public class ShadowBluetoothManager {
private static final ImmutableIntArray VALID_STATES =
ImmutableIntArray.of(
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothServerSocket.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothServerSocket.java
index 7d8c28309..70166ddad 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothServerSocket.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothServerSocket.java
@@ -1,12 +1,9 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
-import android.os.Build;
import android.os.ParcelUuid;
import java.io.IOException;
import java.util.concurrent.BlockingQueue;
@@ -23,20 +20,12 @@ public class ShadowBluetoothServerSocket {
private boolean closed;
@SuppressLint("PrivateApi")
- @SuppressWarnings("unchecked")
public static BluetoothServerSocket newInstance(
int type, boolean auth, boolean encrypt, ParcelUuid uuid) {
- if (Build.VERSION.SDK_INT >= JELLY_BEAN_MR1) {
- return Shadow.newInstance(
- BluetoothServerSocket.class,
- new Class<?>[] {Integer.TYPE, Boolean.TYPE, Boolean.TYPE, ParcelUuid.class},
- new Object[] {type, auth, encrypt, uuid});
- } else {
- return Shadow.newInstance(
- BluetoothServerSocket.class,
- new Class<?>[] {Integer.TYPE, Boolean.TYPE, Boolean.TYPE, Integer.TYPE},
- new Object[] {type, auth, encrypt, getPort(uuid)});
- }
+ return Shadow.newInstance(
+ BluetoothServerSocket.class,
+ new Class<?>[] {Integer.TYPE, Boolean.TYPE, Boolean.TYPE, ParcelUuid.class},
+ new Object[] {type, auth, encrypt, uuid});
}
// Port ranges are valid from 1 to MAX_RFCOMM_CHANNEL.
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBroadcastPendingResult.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBroadcastPendingResult.java
index 2cd2058ee..cbd7e3eea 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBroadcastPendingResult.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBroadcastPendingResult.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1;
import static org.robolectric.RuntimeEnvironment.getApiLevel;
@@ -21,18 +20,7 @@ public final class ShadowBroadcastPendingResult {
static BroadcastReceiver.PendingResult create(int resultCode, String resultData, Bundle resultExtras, boolean ordered) {
try {
- if (getApiLevel() <= JELLY_BEAN) {
- return BroadcastReceiver.PendingResult.class
- .getConstructor(int.class, String.class, Bundle.class, int.class, boolean.class, boolean.class, IBinder.class)
- .newInstance(
- resultCode,
- resultData,
- resultExtras,
- 0 /* type */,
- ordered,
- false /*sticky*/,
- null /* ibinder token */);
- } else if (getApiLevel() <= LOLLIPOP_MR1) {
+ if (getApiLevel() <= LOLLIPOP_MR1) {
return BroadcastReceiver.PendingResult.class
.getConstructor(int.class, String.class, Bundle.class, int.class, boolean.class, boolean.class, IBinder.class, int.class)
.newInstance(
@@ -64,25 +52,7 @@ public final class ShadowBroadcastPendingResult {
static BroadcastReceiver.PendingResult createSticky(Intent intent) {
try {
- if (getApiLevel() <= JELLY_BEAN) {
- return BroadcastReceiver.PendingResult.class
- .getConstructor(
- int.class,
- String.class,
- Bundle.class,
- int.class,
- boolean.class,
- boolean.class,
- IBinder.class)
- .newInstance(
- 0 /*resultCode*/,
- intent.getDataString(),
- intent.getExtras(),
- 0 /* type */,
- false /*ordered*/,
- true /*sticky*/,
- null /* ibinder token */);
- } else if (getApiLevel() <= LOLLIPOP_MR1) {
+ if (getApiLevel() <= LOLLIPOP_MR1) {
return BroadcastReceiver.PendingResult.class
.getConstructor(
int.class,
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBugreportManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBugreportManager.java
index cea521c2c..08957d9f1 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBugreportManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBugreportManager.java
@@ -4,11 +4,11 @@ import static android.os.Build.VERSION_CODES.Q;
import static android.os.Build.VERSION_CODES.R;
import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
+import android.annotation.Nullable;
import android.os.BugreportManager;
import android.os.BugreportManager.BugreportCallback;
import android.os.BugreportParams;
import android.os.ParcelFileDescriptor;
-import androidx.annotation.Nullable;
import java.io.IOException;
import java.util.concurrent.Executor;
import org.robolectric.annotation.Implementation;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBuild.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBuild.java
index b6b1b8309..7b1516152 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBuild.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBuild.java
@@ -68,6 +68,15 @@ public class ShadowBuild {
}
/**
+ * Sets the value of the {@link Build#IS_DEBUGGABLE} field.
+ *
+ * <p>It will be reset for the next test.
+ */
+ public static void setDebuggable(Boolean isDebuggable) {
+ ReflectionHelpers.setStaticField(Build.class, "IS_DEBUGGABLE", isDebuggable);
+ }
+
+ /**
* Sets the value of the {@link Build#MODEL} field.
*
* <p>It will be reset for the next test.
@@ -176,6 +185,16 @@ public class ShadowBuild {
}
/**
+ * Sets the value of the {@link Build#SUPPORTED_32_BIT_ABIS} field. Available in Android L+.
+ *
+ * <p>It will be reset for the next test.
+ */
+ @TargetApi(LOLLIPOP)
+ public static void setSupported32BitAbis(String[] supported32BitAbis) {
+ ReflectionHelpers.setStaticField(Build.class, "SUPPORTED_32_BIT_ABIS", supported32BitAbis);
+ }
+
+ /**
* Sets the value of the {@link Build#SUPPORTED_64_BIT_ABIS} field. Available in Android L+.
*
* <p>It will be reset for the next test.
@@ -186,6 +205,16 @@ public class ShadowBuild {
}
/**
+ * Sets the value of the {@link Build#SUPPORTED_ABIS} field. Available in Android L+.
+ *
+ * <p>It will be reset for the next test.
+ */
+ @TargetApi(LOLLIPOP)
+ public static void setSupportedAbis(String[] supportedAbis) {
+ ReflectionHelpers.setStaticField(Build.class, "SUPPORTED_ABIS", supportedAbis);
+ }
+
+ /**
* Override return value from {@link Build#getRadioVersion()}
*
* @param radioVersion
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCamera.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCamera.java
index 0c2c3a895..dc36d919c 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCamera.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCamera.java
@@ -1,10 +1,8 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static org.robolectric.shadow.api.Shadow.newInstanceOf;
import android.hardware.Camera;
-import android.os.Build;
import android.view.SurfaceHolder;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
@@ -227,10 +225,7 @@ public class ShadowCamera {
Camera.CameraInfo foundCam = cameras.get(cameraId);
cameraInfo.facing = foundCam.facing;
cameraInfo.orientation = foundCam.orientation;
- // canDisableShutterSound was added in API 17.
- if (Build.VERSION.SDK_INT >= JELLY_BEAN_MR1) {
- cameraInfo.canDisableShutterSound = foundCam.canDisableShutterSound;
- }
+ cameraInfo.canDisableShutterSound = foundCam.canDisableShutterSound;
}
@Implementation
@@ -254,7 +249,7 @@ public class ShadowCamera {
}
}
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
protected boolean enableShutterSound(boolean enabled) {
if (!enabled && cameras.containsKey(id) && !cameras.get(id).canDisableShutterSound) {
return false;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCardEmulation.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCardEmulation.java
index 2bd28da36..3f6989a14 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCardEmulation.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCardEmulation.java
@@ -30,7 +30,7 @@ public class ShadowCardEmulation {
@RealObject CardEmulation cardEmulation;
- @Implementation(minSdk = Build.VERSION_CODES.KITKAT)
+ @Implementation
public boolean isDefaultServiceForCategory(ComponentName service, String category) {
return service.equals(defaultServiceForCategoryMap.get(category));
}
@@ -79,14 +79,12 @@ public class ShadowCardEmulation {
public static void reset() {
defaultServiceForCategoryMap = new HashMap<>();
preferredService = null;
- if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.KITKAT) {
- CardEmulationReflector reflector = reflector(CardEmulationReflector.class);
- reflector.setIsInitialized(false);
- reflector.setService(null);
- Map<Context, CardEmulation> cardEmus = reflector.getCardEmus();
- if (cardEmus != null) {
- cardEmus.clear();
- }
+ CardEmulationReflector reflector = reflector(CardEmulationReflector.class);
+ reflector.setIsInitialized(false);
+ reflector.setService(null);
+ Map<Context, CardEmulation> cardEmus = reflector.getCardEmus();
+ if (cardEmus != null) {
+ cardEmus.clear();
}
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowChoreographer.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowChoreographer.java
index cff9382ea..5a136ec09 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowChoreographer.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowChoreographer.java
@@ -5,6 +5,7 @@ import static com.google.common.base.Preconditions.checkState;
import static org.robolectric.shadows.ShadowLooper.looperMode;
import static org.robolectric.util.reflector.Reflector.reflector;
+import android.os.Looper;
import android.view.Choreographer;
import android.view.Choreographer.FrameCallback;
import android.view.DisplayEventReceiver;
@@ -175,5 +176,14 @@ public abstract class ShadowChoreographer {
@Accessor("mDisplayEventReceiver")
DisplayEventReceiver getReceiver();
+
+ @Direct
+ void __constructor__(Looper looper);
+
+ @Direct
+ void __constructor__(Looper looper, int vsyncSource, long layerHandle);
+
+ @Direct
+ void __constructor__(Looper looper, int vsyncSource);
}
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowClipboardManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowClipboardManager.java
index 1ff824105..e90b0a426 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowClipboardManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowClipboardManager.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.N;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.P;
@@ -48,7 +47,7 @@ public class ShadowClipboardManager {
if (clip != null) {
clip.prepareToLeaveProcess(true);
}
- } else if (getApiLevel() >= JELLY_BEAN_MR2) {
+ } else {
if (clip != null) {
ReflectionHelpers.callInstanceMethod(ClipData.class, clip, "prepareToLeaveProcess");
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCompanionDeviceManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCompanionDeviceManager.java
index ce52528d9..23449cd08 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCompanionDeviceManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCompanionDeviceManager.java
@@ -5,6 +5,7 @@ import static java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.toList;
import android.Manifest.permission;
+import android.annotation.Nullable;
import android.app.ActivityThread;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
@@ -14,7 +15,6 @@ import android.content.ComponentName;
import android.net.MacAddress;
import android.os.Build.VERSION_CODES;
import android.os.Handler;
-import androidx.annotation.Nullable;
import com.google.auto.value.AutoValue;
import com.google.common.base.Ascii;
import com.google.common.collect.ImmutableList;
@@ -281,7 +281,8 @@ public class ShadowCompanionDeviceManager {
info.isNotifyOnDeviceNearby(),
revoked,
info.getTimeApprovedMs(),
- info.getLastTimeConnectedMs(),
+ // return value of getLastTimeConnectedMs changed from a long to a Long
+ (long) ReflectionHelpers.callInstanceMethod(info, "getLastTimeConnectedMs"),
systemDataSyncFlags);
}
@@ -343,7 +344,7 @@ public class ShadowCompanionDeviceManager {
.setRevoked(false)
.setAssociatedDevice(null)
.setTimeApprovedMs(0)
- .setLastTimeConnectedMs(0)
+ .setLastTimeConnectedMs(0L)
.setSystemDataSyncFlags(DEFAULT_SYSTEMDATASYNCFLAGS);
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowConnectivityManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowConnectivityManager.java
index d60c68aae..a051af131 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowConnectivityManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowConnectivityManager.java
@@ -1,6 +1,5 @@
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.M;
import static android.os.Build.VERSION_CODES.N;
@@ -428,7 +427,7 @@ public class ShadowConnectivityManager {
*
* @param enable new status for airplane mode
*/
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected void setAirplaneMode(boolean enable) {
ShadowSettings.setAirplaneMode(enable);
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentProvider.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentProvider.java
index a824e9004..a92f53015 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentProvider.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentProvider.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.Q;
import static org.robolectric.util.reflector.Reflector.reflector;
@@ -24,7 +23,7 @@ public class ShadowContentProvider {
return callingPackage;
}
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected String getCallingPackage() {
if (callingPackage != null) {
return callingPackage;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentProviderClient.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentProviderClient.java
index 4652869c0..cf3e712a2 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentProviderClient.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentProviderClient.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static org.robolectric.util.reflector.Reflector.reflector;
import android.content.ContentProvider;
@@ -34,7 +33,7 @@ public class ShadowContentProviderClient {
private ContentProvider provider;
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
protected Bundle call(String method, String arg, Bundle extras) throws RemoteException {
return provider.call(method, arg, extras);
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentResolver.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentResolver.java
index bb2672c32..ce1681cf0 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentResolver.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentResolver.java
@@ -6,9 +6,6 @@ import static android.content.ContentResolver.QUERY_ARG_SQL_SORT_ORDER;
import static android.content.ContentResolver.SCHEME_ANDROID_RESOURCE;
import static android.content.ContentResolver.SCHEME_CONTENT;
import static android.content.ContentResolver.SCHEME_FILE;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.Q;
import static org.robolectric.util.reflector.Reflector.reflector;
@@ -84,7 +81,6 @@ public class ShadowContentResolver {
private static final Map<Uri, Supplier<OutputStream>> outputStreamMap = new HashMap<>();
private static final Map<String, List<ContentProviderOperation>> contentProviderOperations =
new HashMap<>();
- private static ContentProviderResult[] contentProviderResults;
private static final List<UriPermission> uriPermissions = new ArrayList<>();
private static final CopyOnWriteArrayList<ContentObserverEntry> contentObservers =
@@ -108,7 +104,6 @@ public class ShadowContentResolver {
inputStreamMap.clear();
outputStreamMap.clear();
contentProviderOperations.clear();
- contentProviderResults = null;
uriPermissions.clear();
contentObservers.clear();
syncableAccounts.clear();
@@ -514,7 +509,7 @@ public class ShadowContentResolver {
}
@Implementation
- protected ContentProviderResult[] applyBatch(
+ protected @NonNull ContentProviderResult[] applyBatch(
String authority, ArrayList<ContentProviderOperation> operations)
throws OperationApplicationException {
ContentProvider provider = getProvider(authority, getContext());
@@ -522,7 +517,7 @@ public class ShadowContentResolver {
return provider.applyBatch(operations);
} else {
contentProviderOperations.put(authority, operations);
- return contentProviderResults;
+ return new ContentProviderResult[0];
}
}
@@ -565,7 +560,7 @@ public class ShadowContentResolver {
}
for (Map.Entry<Account, Status> mp : map.getValue().entrySet()) {
if (isSyncActive(mp.getKey(), map.getKey())) {
- SyncInfo si = newSyncInfo(0, mp.getKey(), map.getKey(), 0);
+ SyncInfo si = new SyncInfo(0, mp.getKey(), map.getKey(), 0);
list.add(si);
}
}
@@ -573,20 +568,6 @@ public class ShadowContentResolver {
return list;
}
- private static SyncInfo newSyncInfo(
- int authorityId, Account account, String authority, long startTime) {
- if (RuntimeEnvironment.getApiLevel() >= JELLY_BEAN_MR2) {
- return new SyncInfo(authorityId, account, authority, startTime);
- } else {
- return ReflectionHelpers.callConstructor(
- SyncInfo.class,
- ClassParameter.from(int.class, authorityId),
- ClassParameter.from(Account.class, account),
- ClassParameter.from(String.class, authority),
- ClassParameter.from(long.class, startTime));
- }
- }
-
@Implementation
protected static void setIsSyncable(Account account, String authority, int syncable) {
getStatus(account, authority, true).state = syncable;
@@ -665,7 +646,7 @@ public class ShadowContentResolver {
return masterSyncAutomatically;
}
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected void takePersistableUriPermission(@NonNull Uri uri, int modeFlags) {
Objects.requireNonNull(uri, "uri may not be null");
modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
@@ -693,7 +674,7 @@ public class ShadowContentResolver {
addUriPermission(uri, modeFlags);
}
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected void releasePersistableUriPermission(@NonNull Uri uri, int modeFlags) {
Objects.requireNonNull(uri, "uri may not be null");
modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
@@ -727,7 +708,7 @@ public class ShadowContentResolver {
}
}
- @Implementation(minSdk = KITKAT)
+ @Implementation
@NonNull
protected List<UriPermission> getPersistedUriPermissions() {
return uriPermissions;
@@ -919,11 +900,6 @@ public class ShadowContentResolver {
return operations;
}
- @Deprecated
- public void setContentProviderResult(ContentProviderResult[] contentProviderResults) {
- ShadowContentResolver.contentProviderResults = contentProviderResults;
- }
-
private final Map<Uri, RuntimeException> registerContentProviderExceptions = new HashMap<>();
/** Makes {@link #registerContentObserver} throw the specified exception for the specified URI. */
@@ -951,7 +927,7 @@ public class ShadowContentResolver {
contentObservers.add(new ContentObserverEntry(uri, notifyForDescendents, observer));
}
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
protected void registerContentObserver(
Uri uri, boolean notifyForDescendents, ContentObserver observer, int userHandle) {
registerContentObserver(uri, notifyForDescendents, observer);
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextHubClient.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextHubClient.java
index b7c2b7413..a07974be0 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextHubClient.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextHubClient.java
@@ -2,13 +2,13 @@ package org.robolectric.shadows;
import static org.robolectric.util.reflector.Reflector.reflector;
+import android.annotation.RequiresApi;
import android.annotation.TargetApi;
import android.hardware.location.ContextHubClient;
import android.hardware.location.ContextHubInfo;
import android.hardware.location.ContextHubTransaction;
import android.hardware.location.NanoAppMessage;
import android.os.Build.VERSION_CODES;
-import androidx.annotation.RequiresApi;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.List;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextHubManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextHubManager.java
index 84aba53a8..9991a36ee 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextHubManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextHubManager.java
@@ -2,6 +2,7 @@ package org.robolectric.shadows;
import static org.robolectric.util.reflector.Reflector.reflector;
+import android.annotation.Nullable;
import android.app.PendingIntent;
import android.content.Context;
import android.hardware.location.ContextHubClient;
@@ -13,7 +14,6 @@ import android.hardware.location.NanoAppState;
import android.os.Build;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
-import androidx.annotation.Nullable;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Multimap;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextImpl.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextImpl.java
index 86998b8f5..d55ba45c7 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextImpl.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContextImpl.java
@@ -1,8 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.N;
@@ -177,14 +174,14 @@ public class ShadowContextImpl {
intent, /*userHandle=*/ null, receiverPermission, realContextImpl);
}
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
@RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
protected void sendBroadcastAsUser(@RequiresPermission Intent intent, UserHandle user) {
getShadowInstrumentation()
.sendBroadcastWithPermission(intent, user, /*receiverPermission=*/ null, realContextImpl);
}
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
@RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
protected void sendBroadcastAsUser(
@RequiresPermission Intent intent, UserHandle user, @Nullable String receiverPermission) {
@@ -224,7 +221,7 @@ public class ShadowContextImpl {
* Allows the test to query for the broadcasts for specific users, for everything else behaves as
* {@link #sendOrderedBroadcastAsUser}.
*/
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
protected void sendOrderedBroadcastAsUser(
Intent intent,
UserHandle userHandle,
@@ -312,7 +309,7 @@ public class ShadowContextImpl {
.registerReceiver(receiver, filter, broadcastPermission, scheduler, flags, realContextImpl);
}
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
protected Intent registerReceiverAsUser(
BroadcastReceiver receiver,
UserHandle user,
@@ -371,7 +368,7 @@ public class ShadowContextImpl {
}
// This is a private method in ContextImpl so we copy the relevant portions of it here.
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected void validateServiceIntent(Intent service) {
if (service.getComponent() == null
&& service.getPackage() == null
@@ -396,7 +393,7 @@ public class ShadowContextImpl {
this.userId = userId;
}
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected int getUserId() {
if (userId != null) {
return userId;
@@ -405,7 +402,7 @@ public class ShadowContextImpl {
}
}
- @Implementation(maxSdk = JELLY_BEAN_MR2)
+ @Implementation
protected File getExternalFilesDir(String type) {
File externalDir = Environment.getExternalStoragePublicDirectory(/* type= */ null);
if (externalDir == null) {
@@ -421,7 +418,7 @@ public class ShadowContextImpl {
return externalFilesDir;
}
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected File[] getExternalFilesDirs(String type) {
return new File[] {getExternalFilesDir(type)};
}
@@ -430,11 +427,10 @@ public class ShadowContextImpl {
public static void reset() {
String prefsCacheFieldName =
RuntimeEnvironment.getApiLevel() >= N ? "sSharedPrefsCache" : "sSharedPrefs";
- Object prefsDefaultValue = RuntimeEnvironment.getApiLevel() >= KITKAT ? null : new HashMap<>();
Class<?> contextImplClass =
ReflectionHelpers.loadClass(
ShadowContextImpl.class.getClassLoader(), "android.app.ContextImpl");
- ReflectionHelpers.setStaticField(contextImplClass, prefsCacheFieldName, prefsDefaultValue);
+ ReflectionHelpers.setStaticField(contextImplClass, prefsCacheFieldName, null);
if (RuntimeEnvironment.getApiLevel() <= VERSION_CODES.LOLLIPOP_MR1) {
HashMap<String, Object> fetchers =
@@ -450,12 +446,9 @@ public class ShadowContextImpl {
}
}
- if (RuntimeEnvironment.getApiLevel() >= KITKAT) {
-
- Object windowServiceFetcher = fetchers.get(Context.WINDOW_SERVICE);
- ReflectionHelpers.setField(
- windowServiceFetcher.getClass(), windowServiceFetcher, "mDefaultDisplay", null);
- }
+ Object windowServiceFetcher = fetchers.get(Context.WINDOW_SERVICE);
+ ReflectionHelpers.setField(
+ windowServiceFetcher.getClass(), windowServiceFetcher, "mDefaultDisplay", null);
}
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCursorWrapper.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCursorWrapper.java
index 04623f10c..9e659a93c 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCursorWrapper.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCursorWrapper.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.M;
import android.content.ContentResolver;
@@ -198,7 +197,8 @@ public class ShadowCursorWrapper implements Cursor {
wrappedCursor.setNotificationUri(contentResolver, uri);
}
- @Override @Implementation(minSdk = KITKAT)
+ @Override
+ @Implementation
public Uri getNotificationUri() {
return wrappedCursor.getNotificationUri();
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDateIntervalFormat.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDateIntervalFormat.java
index 83d5235c0..8dce4c521 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDateIntervalFormat.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDateIntervalFormat.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1;
import static android.os.Build.VERSION_CODES.TIRAMISU;
@@ -8,11 +7,11 @@ import java.text.FieldPosition;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
+import libcore.icu.DateIntervalFormat;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
-@Implements(className = "libcore.icu.DateIntervalFormat", isInAndroidSdk = false, minSdk = KITKAT,
- maxSdk = TIRAMISU)
+@Implements(value = DateIntervalFormat.class, isInAndroidSdk = false)
public class ShadowDateIntervalFormat {
private static long address;
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 84fc39858..f0313f2a8 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDatePickerDialog.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDatePickerDialog.java
@@ -7,11 +7,10 @@ import static org.robolectric.shadow.api.Shadow.invokeConstructor;
import static org.robolectric.util.ReflectionHelpers.ClassParameter;
import static org.robolectric.util.reflector.Reflector.reflector;
-import android.annotation.TargetApi;
+import android.annotation.RequiresApi;
import android.app.DatePickerDialog;
import android.app.DatePickerDialog.OnDateSetListener;
import android.content.Context;
-import androidx.annotation.RequiresApi;
import java.util.Calendar;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
@@ -64,7 +63,7 @@ public class ShadowDatePickerDialog extends ShadowAlertDialog {
}
public DatePickerDialog.OnDateSetListener getOnDateSetListenerCallback() {
- if (RuntimeEnvironment.getApiLevel() <= KITKAT) {
+ if (RuntimeEnvironment.getApiLevel() == KITKAT) {
return reflector(DatePickerDialogReflector.class, realDatePickerDialog).getCallback();
} else {
return reflector(DatePickerDialogReflector.class, realDatePickerDialog).getDateSetListener();
@@ -80,7 +79,6 @@ public class ShadowDatePickerDialog extends ShadowAlertDialog {
OnDateSetListener getDateSetListener();
/** For sdk version is equals to {@link KITKAT} */
- @TargetApi(KITKAT)
@Accessor("mCallBack")
OnDateSetListener getCallback();
}
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 2b587d1c1..e4a197af8 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyManager.java
@@ -3,8 +3,6 @@ package org.robolectric.shadows;
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME;
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS;
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
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;
@@ -215,7 +213,7 @@ public class ShadowDevicePolicyManager {
storageEncryptionStatus = DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED;
}
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected boolean isDeviceOwnerApp(String packageName) {
return deviceOwner != null && deviceOwner.getPackageName().equals(packageName);
}
@@ -351,7 +349,7 @@ public class ShadowDevicePolicyManager {
/**
* @see #setDeviceOwner(ComponentName)
*/
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected String getDeviceOwner() {
return deviceOwner != null ? deviceOwner.getPackageName() : null;
}
@@ -1274,13 +1272,13 @@ public class ShadowDevicePolicyManager {
.clearPackagePersistentPreferredActivities(packageName);
}
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
protected void setKeyguardDisabledFeatures(ComponentName admin, int which) {
enforceActiveAdmin(admin);
keyguardDisabledFeatures = which;
}
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
protected int getKeyguardDisabledFeatures(ComponentName admin) {
return keyguardDisabledFeatures;
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyResourcesManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyResourcesManager.java
index eaf90276b..70249de3c 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyResourcesManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyResourcesManager.java
@@ -2,10 +2,10 @@ package org.robolectric.shadows;
import static org.robolectric.util.reflector.Reflector.reflector;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.admin.DevicePolicyResourcesManager;
import android.os.Build.VERSION_CODES;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplay.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplay.java
index c23c5c162..6a88f769b 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplay.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplay.java
@@ -1,11 +1,8 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static org.robolectric.util.reflector.Reflector.reflector;
import android.content.Context;
-import android.content.res.Configuration;
-import android.graphics.Point;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.util.DisplayMetrics;
@@ -45,43 +42,20 @@ public class ShadowDisplay {
@RealObject Display realObject;
private Float refreshRate;
-
- // the following fields are used only for Jelly Bean...
- private String name;
- private Integer displayId;
- private Integer width;
- private Integer height;
- private Integer realWidth;
- private Integer realHeight;
- private Integer densityDpi;
- private Float xdpi;
- private Float ydpi;
private Float scaledDensity;
- private Integer rotation;
- private Integer pixelFormat;
/**
* If {@link #setScaledDensity(float)} has been called, {@link DisplayMetrics#scaledDensity} will
* be modified to reflect the value specified. Note that this is not a realistic state.
*
- * @deprecated This behavior is deprecated and will be removed in Robolectric 3.7.
+ * @deprecated This behavior is deprecated and will be removed in Robolectric 4.13.
*/
@Deprecated
@Implementation
protected void getMetrics(DisplayMetrics outMetrics) {
- if (isJB()) {
- outMetrics.density = densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
- outMetrics.densityDpi = densityDpi;
+ reflector(_Display_.class, realObject).getMetrics(outMetrics);
+ if (scaledDensity != null) {
outMetrics.scaledDensity = scaledDensity;
- outMetrics.widthPixels = width;
- outMetrics.heightPixels = height;
- outMetrics.xdpi = xdpi;
- outMetrics.ydpi = ydpi;
- } else {
- reflector(_Display_.class, realObject).getMetrics(outMetrics);
- if (scaledDensity != null) {
- outMetrics.scaledDensity = scaledDensity;
- }
}
}
@@ -89,32 +63,25 @@ public class ShadowDisplay {
* If {@link #setScaledDensity(float)} has been called, {@link DisplayMetrics#scaledDensity} will
* be modified to reflect the value specified. Note that this is not a realistic state.
*
- * @deprecated This behavior is deprecated and will be removed in Robolectric 3.7.
+ * @deprecated This behavior is deprecated and will be removed in Robolectric 4.13.
*/
@Deprecated
@Implementation
protected void getRealMetrics(DisplayMetrics outMetrics) {
- if (isJB()) {
- getMetrics(outMetrics);
- outMetrics.widthPixels = realWidth;
- outMetrics.heightPixels = realHeight;
- } else {
- reflector(_Display_.class, realObject).getRealMetrics(outMetrics);
- if (scaledDensity != null) {
- outMetrics.scaledDensity = scaledDensity;
- }
+ reflector(_Display_.class, realObject).getRealMetrics(outMetrics);
+ if (scaledDensity != null) {
+ outMetrics.scaledDensity = scaledDensity;
}
}
/**
- * If {@link #setDisplayId(int)} has been called, this method will return the specified value.
+ * Changes the scaled density for this display.
*
- * @deprecated This behavior is deprecated and will be removed in Robolectric 3.7.
+ * @deprecated This method is deprecated and will be removed in Robolectric 4.13.
*/
@Deprecated
- @Implementation
- protected int getDisplayId() {
- return displayId == null ? reflector(_Display_.class, realObject).getDisplayId() : displayId;
+ public void setScaledDensity(float scaledDensity) {
+ this.scaledDensity = scaledDensity;
}
/**
@@ -137,38 +104,6 @@ public class ShadowDisplay {
}
/**
- * If {@link #setPixelFormat(int)} has been called, this method will return the specified value.
- *
- * @deprecated This behavior is deprecated and will be removed in Robolectric 3.7.
- */
- @Deprecated
- @Implementation
- protected int getPixelFormat() {
- return pixelFormat == null
- ? reflector(_Display_.class, realObject).getPixelFormat()
- : pixelFormat;
- }
-
- @Implementation(maxSdk = JELLY_BEAN)
- protected void getSizeInternal(Point outSize, boolean doCompat) {
- outSize.x = width;
- outSize.y = height;
- }
-
- @Implementation(maxSdk = JELLY_BEAN)
- protected void getCurrentSizeRange(Point outSmallestSize, Point outLargestSize) {
- int minimum = Math.min(width, height);
- int maximum = Math.max(width, height);
- outSmallestSize.set(minimum, minimum);
- outLargestSize.set(maximum, maximum);
- }
-
- @Implementation(maxSdk = JELLY_BEAN)
- protected void getRealSize(Point outSize) {
- outSize.set(realWidth, realHeight);
- }
-
- /**
* Changes the density for this display.
*
* <p>Any registered {@link android.hardware.display.DisplayManager.DisplayListener}s will be
@@ -185,12 +120,8 @@ public class ShadowDisplay {
* notified of the change.
*/
public void setDensityDpi(int densityDpi) {
- if (isJB()) {
- this.densityDpi = densityDpi;
- } else {
- ShadowDisplayManager.changeDisplay(
- realObject.getDisplayId(), di -> di.logicalDensityDpi = densityDpi);
- }
+ ShadowDisplayManager.changeDisplay(
+ realObject.getDisplayId(), di -> di.logicalDensityDpi = densityDpi);
}
/**
@@ -200,11 +131,7 @@ public class ShadowDisplay {
* notified of the change.
*/
public void setXdpi(float xdpi) {
- if (isJB()) {
- this.xdpi = xdpi;
- } else {
- ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.physicalXDpi = xdpi);
- }
+ ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.physicalXDpi = xdpi);
}
/**
@@ -214,34 +141,7 @@ public class ShadowDisplay {
* notified of the change.
*/
public void setYdpi(float ydpi) {
- if (isJB()) {
- this.ydpi = ydpi;
- } else {
- ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.physicalYDpi = ydpi);
- }
- }
-
- /**
- * Changes the scaled density for this display.
- *
- * @deprecated This method is deprecated and will be removed in Robolectric 3.7.
- */
- @Deprecated
- public void setScaledDensity(float scaledDensity) {
- this.scaledDensity = scaledDensity;
- }
-
- /**
- * Changes the ID for this display.
- *
- * <p>Any registered {@link android.hardware.display.DisplayManager.DisplayListener}s will be
- * notified of the change.
- *
- * @deprecated This method is deprecated and will be removed in Robolectric 3.7.
- */
- @Deprecated
- public void setDisplayId(int displayId) {
- this.displayId = displayId;
+ ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.physicalYDpi = ydpi);
}
/**
@@ -251,11 +151,7 @@ public class ShadowDisplay {
* notified of the change.
*/
public void setName(String name) {
- if (isJB()) {
- this.name = name;
- } else {
- ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.name = name);
- }
+ ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.name = name);
}
/**
@@ -267,9 +163,7 @@ public class ShadowDisplay {
public void setFlags(int flags) {
reflector(_Display_.class, realObject).setFlags(flags);
- if (!isJB()) {
- ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.flags = flags);
- }
+ ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.flags = flags);
}
/**
@@ -281,11 +175,7 @@ public class ShadowDisplay {
* @param width the new width in pixels
*/
public void setWidth(int width) {
- if (isJB()) {
- this.width = width;
- } else {
- ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.appWidth = width);
- }
+ ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.appWidth = width);
}
/**
@@ -297,11 +187,7 @@ public class ShadowDisplay {
* @param height new height in pixels
*/
public void setHeight(int height) {
- if (isJB()) {
- this.height = height;
- } else {
- ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.appHeight = height);
- }
+ ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.appHeight = height);
}
/**
@@ -313,11 +199,7 @@ public class ShadowDisplay {
* @param width the new width in pixels
*/
public void setRealWidth(int width) {
- if (isJB()) {
- this.realWidth = width;
- } else {
- ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.logicalWidth = width);
- }
+ ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.logicalWidth = width);
}
/**
@@ -329,12 +211,7 @@ public class ShadowDisplay {
* @param height the new height in pixels
*/
public void setRealHeight(int height) {
- if (isJB()) {
- this.realHeight = height;
- } else {
- ShadowDisplayManager.changeDisplay(
- realObject.getDisplayId(), di -> di.logicalHeight = height);
- }
+ ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.logicalHeight = height);
}
/**
@@ -357,21 +234,7 @@ public class ShadowDisplay {
* Surface#ROTATION_180}, {@link Surface#ROTATION_270}
*/
public void setRotation(int rotation) {
- if (isJB()) {
- this.rotation = rotation;
- } else {
- ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.rotation = rotation);
- }
- }
-
- /**
- * Changes the pixel format for this display.
- *
- * @deprecated This method is deprecated and will be removed in Robolectric 3.7.
- */
- @Deprecated
- public void setPixelFormat(int pixelFormat) {
- this.pixelFormat = pixelFormat;
+ ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.rotation = rotation);
}
/**
@@ -384,9 +247,7 @@ public class ShadowDisplay {
* Display#STATE_DOZE}, {@link Display#STATE_DOZE_SUSPEND}, or {@link Display#STATE_UNKNOWN}.
*/
public void setState(int state) {
- if (!isJB()) {
- ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.state = state);
- }
+ ShadowDisplayManager.changeDisplay(realObject.getDisplayId(), di -> di.state = state);
}
/**
@@ -450,34 +311,9 @@ public class ShadowDisplay {
realObject.getDisplayId(), displayConfig -> displayConfig.displayCutout = displayCutout);
}
- private boolean isJB() {
- return RuntimeEnvironment.getApiLevel() == JELLY_BEAN;
- }
-
- void configureForJBOnly(Configuration configuration, DisplayMetrics displayMetrics) {
- int widthPx = (int) (configuration.screenWidthDp * displayMetrics.density);
- int heightPx = (int) (configuration.screenHeightDp * displayMetrics.density);
-
- name = "Built-in screen";
- displayId = 0;
- width = widthPx;
- height = heightPx;
- realWidth = widthPx;
- realHeight = heightPx;
- densityDpi = displayMetrics.densityDpi;
- xdpi = (float) displayMetrics.densityDpi;
- ydpi = (float) displayMetrics.densityDpi;
- scaledDensity = displayMetrics.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
- rotation =
- configuration.orientation == Configuration.ORIENTATION_PORTRAIT
- ? Surface.ROTATION_0
- : Surface.ROTATION_90;
- }
-
/** Reflector interface for {@link Display}'s internals. */
@ForType(Display.class)
interface _Display_ {
-
@Direct
void getMetrics(DisplayMetrics outMetrics);
@@ -485,14 +321,8 @@ public class ShadowDisplay {
void getRealMetrics(DisplayMetrics outMetrics);
@Direct
- int getDisplayId();
-
- @Direct
float getRefreshRate();
- @Direct
- int getPixelFormat();
-
@Accessor("mFlags")
void setFlags(int flags);
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayEventReceiver.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayEventReceiver.java
index 145a37736..50a53d9d3 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayEventReceiver.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayEventReceiver.java
@@ -1,7 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.KITKAT_WATCH;
import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1;
@@ -133,7 +131,7 @@ public class ShadowDisplayEventReceiver {
nativeObjRegistry.getNativeObject(receiverPtr).scheduleVsync();
}
- @Implementation(minSdk = JELLY_BEAN_MR1, maxSdk = R)
+ @Implementation(maxSdk = R)
protected void dispose(boolean finalized) {
CloseGuard closeGuard = displayEventReceiverReflector.getCloseGuard();
// Suppresses noisy CloseGuard warning
@@ -144,9 +142,7 @@ public class ShadowDisplayEventReceiver {
}
protected void onVsync() {
- if (RuntimeEnvironment.getApiLevel() <= JELLY_BEAN) {
- displayEventReceiverReflector.onVsync(ShadowSystem.nanoTime(), 1);
- } else if (RuntimeEnvironment.getApiLevel() < Q) {
+ if (RuntimeEnvironment.getApiLevel() < Q) {
displayEventReceiverReflector.onVsync(
ShadowSystem.nanoTime(), 0, /* SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN */ 1);
} else if (RuntimeEnvironment.getApiLevel() < S) {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayManager.java
index 776f501c3..e03590f32 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayManager.java
@@ -2,7 +2,6 @@ package org.robolectric.shadows;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static android.os.Build.VERSION_CODES.P;
import static java.util.Objects.requireNonNull;
import static org.robolectric.shadow.api.Shadow.extract;
@@ -10,6 +9,8 @@ import static org.robolectric.shadow.api.Shadow.invokeConstructor;
import static org.robolectric.shadows.ShadowLooper.shadowMainLooper;
import static org.robolectric.util.reflector.Reflector.reflector;
+import android.annotation.Nullable;
+import android.annotation.RequiresApi;
import android.content.Context;
import android.content.res.Configuration;
import android.hardware.display.BrightnessChangeEvent;
@@ -20,8 +21,6 @@ import android.util.DisplayMetrics;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.Surface;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
import com.google.auto.value.AutoBuilder;
import java.util.HashMap;
import java.util.List;
@@ -43,7 +42,7 @@ import org.robolectric.util.reflector.ForType;
* For tests, display properties may be changed and devices may be added or removed
* programmatically.
*/
-@Implements(value = DisplayManager.class, minSdk = JELLY_BEAN_MR1, looseSignatures = true)
+@Implements(value = DisplayManager.class, looseSignatures = true)
public class ShadowDisplayManager {
@RealObject private DisplayManager realDisplayManager;
@@ -96,12 +95,21 @@ public class ShadowDisplayManager {
return id;
}
+ static IllegalStateException configureDefaultDisplayCallstack;
+
/** internal only */
public static void configureDefaultDisplay(
Configuration configuration, DisplayMetrics displayMetrics) {
ShadowDisplayManagerGlobal shadowDisplayManagerGlobal = getShadowDisplayManagerGlobal();
- if (DisplayManagerGlobal.getInstance().getDisplayIds().length != 0) {
- throw new IllegalStateException("this method should only be called by Robolectric");
+ if (DisplayManagerGlobal.getInstance().getDisplayIds().length == 0) {
+ configureDefaultDisplayCallstack =
+ new IllegalStateException("configureDefaultDisplay should only be called once");
+ } else {
+ configureDefaultDisplayCallstack.initCause(
+ new IllegalStateException(
+ "configureDefaultDisplay was called a second time",
+ configureDefaultDisplayCallstack));
+ throw configureDefaultDisplayCallstack;
}
shadowDisplayManagerGlobal.addDisplay(
@@ -143,7 +151,7 @@ public class ShadowDisplayManager {
displayInfo.state = Display.STATE_ON;
}
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
displayInfo.getAppMetrics(displayMetrics);
}
@@ -338,10 +346,6 @@ public class ShadowDisplayManager {
}
private static ShadowDisplayManagerGlobal getShadowDisplayManagerGlobal() {
- if (Build.VERSION.SDK_INT < JELLY_BEAN_MR1) {
- throw new UnsupportedOperationException("multiple displays not supported in Jelly Bean");
- }
-
return extract(DisplayManagerGlobal.getInstance());
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayManagerGlobal.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayManagerGlobal.java
index d0fdab014..5681eb218 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayManagerGlobal.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayManagerGlobal.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static android.os.Build.VERSION_CODES.O_MR1;
import static android.os.Build.VERSION_CODES.P;
import static org.robolectric.util.reflector.Reflector.reflector;
@@ -38,7 +37,6 @@ import org.robolectric.util.reflector.ForType;
@Implements(
value = DisplayManagerGlobal.class,
isInAndroidSdk = false,
- minSdk = JELLY_BEAN_MR1,
looseSignatures = true)
public class ShadowDisplayManagerGlobal {
private static DisplayManagerGlobal instance;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDistanceMeasurementManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDistanceMeasurementManager.java
new file mode 100644
index 000000000..0f0e4836e
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDistanceMeasurementManager.java
@@ -0,0 +1,159 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothStatusCodes;
+import android.bluetooth.IBluetoothGatt;
+import android.bluetooth.le.DistanceMeasurementManager;
+import android.bluetooth.le.DistanceMeasurementMethod;
+import android.bluetooth.le.DistanceMeasurementParams;
+import android.bluetooth.le.DistanceMeasurementResult;
+import android.bluetooth.le.DistanceMeasurementSession;
+import android.content.AttributionSource;
+import android.os.CancellationSignal;
+import android.os.ParcelUuid;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.UUID;
+import java.util.concurrent.Executor;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.util.ReflectionHelpers;
+
+/** Shadow implementation of {@link DistanceMeasurementManager}. */
+@Implements(
+ value = DistanceMeasurementManager.class,
+ minSdk = UPSIDE_DOWN_CAKE,
+ isInAndroidSdk = false)
+public class ShadowDistanceMeasurementManager {
+
+ private final Map<BluetoothDevice, DistanceMeasurementSession> sessionMap = new HashMap<>();
+ private final Map<BluetoothDevice, DistanceMeasurementSession.Callback> sessionCallbackMap =
+ new HashMap<>();
+ private List<DistanceMeasurementMethod> supportedMethods = new ArrayList<>();
+
+ @Implementation
+ protected List<DistanceMeasurementMethod> getSupportedMethods() {
+ return supportedMethods;
+ }
+
+ @Implementation
+ protected CancellationSignal startMeasurementSession(
+ DistanceMeasurementParams params,
+ Executor executor,
+ DistanceMeasurementSession.Callback callback) {
+ IBluetoothGatt gatt = ReflectionHelpers.createNullProxy(IBluetoothGatt.class);
+ sessionMap.put(
+ params.getDevice(),
+ new DistanceMeasurementSession(
+ gatt,
+ new ParcelUuid(UUID.randomUUID()),
+ params,
+ executor,
+ AttributionSource.myAttributionSource(),
+ callback));
+ sessionCallbackMap.put(params.getDevice(), callback);
+
+ return new CancellationSignal();
+ }
+
+ /**
+ * Simulates {@link DistanceMeasurementSession.Callback#onResult(BluetoothDevice,
+ * DistanceMeasurementResult)}.
+ *
+ * @param device Remote {@link BluetoothDevice} to which this device is measuring distance.
+ * @param result {@link DistanceMeasurementResult} which should be passed to the callback.
+ */
+ public void simulateOnResult(BluetoothDevice device, DistanceMeasurementResult result) {
+ DistanceMeasurementSession session = sessionMap.get(device);
+ DistanceMeasurementSession.Callback sessionCallback = sessionCallbackMap.get(device);
+ if (session == null || sessionCallback == null) {
+ throw new NoSuchElementException("Session or session callback is missing.");
+ }
+ sessionCallback.onStarted(session);
+ sessionCallback.onResult(device, result);
+ }
+
+ /**
+ * Simulates {@link DistanceMeasurementSession.Callback#onStartFail(int)} with an error.
+ *
+ * @param device Remote {@link BluetoothDevice} to which this device is measuring distance.
+ * @param error Error to simulate. One of {@link DistanceMeasurementSession.Callback.Reason}.
+ */
+ public void simulateOnStartFailError(BluetoothDevice device, int error) {
+ DistanceMeasurementSession.Callback sessionCallback = sessionCallbackMap.get(device);
+ if (sessionCallback == null) {
+ throw new NoSuchElementException("Session callback is missing.");
+ }
+ sessionCallback.onStartFail(error);
+
+ sessionMap.remove(device);
+ sessionCallbackMap.remove(device);
+ }
+
+ /**
+ * Simulates {@link DistanceMeasurementSession.Callback#onStopped(DistanceMeasurementSession,
+ * int)} with an error.
+ *
+ * @param device Remote {@link BluetoothDevice} to which this device is measuring distance.
+ * @param error Error to simulate. One of {@link DistanceMeasurementSession.Callback.Reason}.
+ */
+ public void simulateOnStoppedError(BluetoothDevice device, int error) {
+ DistanceMeasurementSession session = sessionMap.get(device);
+ DistanceMeasurementSession.Callback sessionCallback = sessionCallbackMap.get(device);
+ if (session == null || sessionCallback == null) {
+ throw new NoSuchElementException("Session or session callback is missing.");
+ }
+ sessionCallback.onStarted(session);
+ sessionCallback.onStopped(session, error);
+
+ sessionMap.remove(device);
+ sessionCallbackMap.remove(device);
+ }
+
+ /**
+ * Simulates {@link DistanceMeasurementSession.Callback#onStopped(DistanceMeasurementSession,
+ * int)} without an error.
+ *
+ * @param device Remote {@link BluetoothDevice} to which this device is measuring distance.
+ */
+ public void simulateSuccessfulTermination(BluetoothDevice device) {
+ DistanceMeasurementSession session = sessionMap.get(device);
+ DistanceMeasurementSession.Callback sessionCallback = sessionCallbackMap.get(device);
+ if (session == null || sessionCallback == null) {
+ throw new NoSuchElementException("Session or session callback is missing.");
+ }
+ sessionCallback.onStarted(session);
+ sessionCallback.onStopped(session, BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST);
+
+ sessionMap.remove(device);
+ sessionCallbackMap.remove(device);
+ }
+
+ /**
+ * Simulates {@link DistanceMeasurementSession.Callback#onStopped(DistanceMeasurementSession,
+ * int)} after a timeout.
+ */
+ public void simulateTimeout(BluetoothDevice device) {
+ DistanceMeasurementSession session = sessionMap.get(device);
+ DistanceMeasurementSession.Callback sessionCallback = sessionCallbackMap.get(device);
+ if (session == null || sessionCallback == null) {
+ throw new NoSuchElementException("Session or session callback is missing.");
+ }
+ sessionCallback.onStarted(session);
+ sessionCallback.onStopped(session, BluetoothStatusCodes.ERROR_TIMEOUT);
+
+ sessionMap.remove(device);
+ sessionCallbackMap.remove(device);
+ }
+
+ /** Sets a list of supported {@link DistanceMeasurementMethod}. */
+ public void setSupportedMethods(List<DistanceMeasurementMethod> methods) {
+ supportedMethods = ImmutableList.copyOf(methods);
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDownloadManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDownloadManager.java
index aadbf8005..36830fd23 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDownloadManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDownloadManager.java
@@ -37,6 +37,8 @@ public class ShadowDownloadManager {
protected long enqueue(DownloadManager.Request request) {
queueCounter++;
requestMap.put(queueCounter, request);
+ ShadowRequest shadowRequest = Shadow.extract(request);
+ shadowRequest.setId(queueCounter);
return queueCounter;
}
@@ -141,6 +143,7 @@ public class ShadowDownloadManager {
private int status;
private long totalSize;
private long bytesSoFar;
+ private long id;
public int getStatus() {
return this.status;
@@ -166,6 +169,14 @@ public class ShadowDownloadManager {
this.bytesSoFar = bytesSoFar;
}
+ public long getId() {
+ return this.id;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+
public Uri getUri() {
return getFieldReflectively("mUri", realObject, Uri.class);
}
@@ -264,6 +275,7 @@ public class ShadowDownloadManager {
private static final int COLUMN_INDEX_TITLE = 6;
private static final int COLUMN_INDEX_TOTAL_SIZE = 7;
private static final int COLUMN_INDEX_BYTES_SO_FAR = 8;
+ private static final int COLUMN_INDEX_ID = 9;
public List<DownloadManager.Request> requests = new ArrayList<>();
private int positionIndex = -1;
@@ -322,6 +334,8 @@ public class ShadowDownloadManager {
return COLUMN_INDEX_TOTAL_SIZE;
} else if (DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR.equals(columnName)) {
return COLUMN_INDEX_BYTES_SO_FAR;
+ } else if (DownloadManager.COLUMN_ID.equals(columnName)) {
+ return COLUMN_INDEX_ID;
}
return -1;
@@ -393,6 +407,8 @@ public class ShadowDownloadManager {
return request.getTotalSize();
} else if (columnIndex == COLUMN_INDEX_BYTES_SO_FAR) {
return request.getBytesSoFar();
+ } else if (columnIndex == COLUMN_INDEX_ID) {
+ return request.getId();
}
return 0;
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowEnvironment.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowEnvironment.java
index 91a855af9..2ce293834 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowEnvironment.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowEnvironment.java
@@ -1,8 +1,6 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.Q;
@@ -122,7 +120,7 @@ public class ShadowEnvironment {
return EXTERNAL_CACHE_DIR.toFile();
}
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected static File[] buildExternalStorageAppCacheDirs(String packageName) {
Path externalStorageDirectoryPath = getExternalStorageDirectory().toPath();
// Add cache directory in path.
@@ -199,7 +197,7 @@ public class ShadowEnvironment {
return exists != null ? exists : false;
}
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected static String getStorageState(File directory) {
Path directoryPath = directory.toPath();
for (Map.Entry<Path, String> entry : storageState.entrySet()) {
@@ -291,15 +289,7 @@ public class ShadowEnvironment {
}
}
- if (RuntimeEnvironment.getApiLevel() >= JELLY_BEAN_MR1
- && RuntimeEnvironment.getApiLevel() < KITKAT) {
- if (externalDirs.size() == 1 && externalFileDir != null) {
- Environment.UserEnvironment userEnvironment =
- ReflectionHelpers.getStaticField(Environment.class, "sCurrentUser");
- reflector(_UserEnvironment_.class, userEnvironment)
- .setExternalStorageAndroidData(externalFileDir.toFile());
- }
- } else if (RuntimeEnvironment.getApiLevel() >= KITKAT && RuntimeEnvironment.getApiLevel() < M) {
+ if (RuntimeEnvironment.getApiLevel() < M) {
Environment.UserEnvironment userEnvironment =
ReflectionHelpers.getStaticField(Environment.class, "sCurrentUser");
reflector(_UserEnvironment_.class, userEnvironment)
@@ -321,8 +311,7 @@ public class ShadowEnvironment {
storageState.put(directory.toPath(), state);
}
- @Implements(className = "android.os.Environment$UserEnvironment", isInAndroidSdk = false,
- minSdk = JELLY_BEAN_MR1)
+ @Implements(className = "android.os.Environment$UserEnvironment", isInAndroidSdk = false)
public static class ShadowUserEnvironment {
@Implementation(minSdk = M)
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowFontBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowFontBuilder.java
index 9bae6df15..50f8c7d60 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowFontBuilder.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowFontBuilder.java
@@ -4,10 +4,10 @@ import static android.os.Build.VERSION_CODES.Q;
import static android.os.Build.VERSION_CODES.R;
import static android.os.Build.VERSION_CODES.S;
+import android.annotation.RequiresApi;
import android.content.res.AssetManager;
import android.graphics.fonts.Font;
import android.graphics.fonts.FontStyle;
-import androidx.annotation.RequiresApi;
import com.google.common.base.Preconditions;
import java.io.IOException;
import java.io.InputStream;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowHidlSupport.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowHidlSupport.java
index 78f88891e..82572b689 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowHidlSupport.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowHidlSupport.java
@@ -5,6 +5,7 @@ import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.versioning.AndroidVersions.V;
+/** Activates Hidl support */
@SuppressWarnings("NewApi")
@Implements(value = HidlSupport.class, isInAndroidSdk = false, minSdk = V.SDK_INT)
public class ShadowHidlSupport {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowIAppOpsService.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowIAppOpsService.java
index ee0da5c72..d23459c78 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowIAppOpsService.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowIAppOpsService.java
@@ -1,7 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
-
import android.os.IBinder;
import com.android.internal.app.IAppOpsService;
import org.robolectric.annotation.Implementation;
@@ -13,7 +11,7 @@ public class ShadowIAppOpsService {
@Implements(value = IAppOpsService.Stub.class, isInAndroidSdk = false)
public static class ShadowStub {
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
public static IAppOpsService asInterface(IBinder obj) {
return ReflectionHelpers.createNullProxy(IAppOpsService.class);
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowICU.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowICU.java
index d09d1c0e0..941e260fb 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowICU.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowICU.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.KITKAT_WATCH;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.N;
@@ -37,12 +36,18 @@ public class ShadowICU {
switch (skeleton) {
case "jmm":
return getjmmPattern(locale);
+ case "yMMMd": // This is from {@code DatePickerDefaults.YearAbbrMonthDaySkeleton}
+ return "MMM d, y";
+ case "yMMMMEEEEd": // This is from {@code DatePickerDefaults.YearMonthWeekdayDaySkeleton}
+ return "EEEE, MMMM d, y";
+ case "yMMMM": // This is from {@code DatePickerDefaults.YearMonthSkeleton}
+ return "MMMM y";
default:
return skeleton;
}
}
- @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = KITKAT_WATCH)
+ @Implementation(maxSdk = KITKAT_WATCH)
public static String getBestDateTimePattern(String skeleton, String locale) {
return skeleton;
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowIcon.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowIcon.java
index 72aa2fe32..04d255eb4 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowIcon.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowIcon.java
@@ -3,6 +3,7 @@ package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.M;
import static org.robolectric.util.reflector.Reflector.reflector;
+import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
@@ -11,7 +12,6 @@ import android.graphics.drawable.Icon.OnDrawableLoadedListener;
import android.net.Uri;
import android.os.Handler;
import android.os.Message;
-import androidx.annotation.Nullable;
import java.util.concurrent.Executor;
import org.robolectric.annotation.HiddenApi;
import org.robolectric.annotation.Implementation;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowImageReader.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowImageReader.java
index 140f664be..9431560eb 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowImageReader.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowImageReader.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.S_V2;
import static android.os.Build.VERSION_CODES.TIRAMISU;
import static org.robolectric.util.reflector.Reflector.reflector;
@@ -41,13 +40,13 @@ public class ShadowImageReader {
@RealObject private ImageReader imageReader;
private Canvas canvas;
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected void close() {
readerValid.set(false);
openedImages.clear();
}
- @Implementation(minSdk = KITKAT, maxSdk = S_V2)
+ @Implementation(maxSdk = S_V2)
protected int nativeImageSetup(Image image) {
if (!readerValid.get()) {
throw new IllegalStateException("ImageReader closed.");
@@ -75,12 +74,12 @@ public class ShadowImageReader {
return nativeImageSetup((Image) image);
}
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected void nativeReleaseImage(Image i) {
openedImages.remove(i);
}
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected Surface nativeGetSurface() {
if (surface == null) {
surface = new FakeSurface();
@@ -132,19 +131,19 @@ public class ShadowImageReader {
public static class ShadowSurfaceImage {
@RealObject Object surfaceImage;
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected int getWidth() {
ImageReader reader = ReflectionHelpers.getField(surfaceImage, "this$0");
return reader.getWidth();
}
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected int getHeight() {
ImageReader reader = ReflectionHelpers.getField(surfaceImage, "this$0");
return reader.getHeight();
}
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected int getFormat() {
ImageReader reader = ReflectionHelpers.getField(surfaceImage, "this$0");
return reader.getImageFormat();
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowImsMmTelManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowImsMmTelManager.java
index 8d447ed7c..aaa1dff22 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowImsMmTelManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowImsMmTelManager.java
@@ -4,6 +4,8 @@ import static org.robolectric.util.reflector.Reflector.reflector;
import android.Manifest;
import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.RequiresApi;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.os.Build.VERSION_CODES;
@@ -17,8 +19,6 @@ import android.telephony.ims.RegistrationManager;
import android.telephony.ims.feature.MmTelFeature.MmTelCapabilities;
import android.telephony.ims.stub.ImsRegistrationImplBase;
import android.util.ArrayMap;
-import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -41,7 +41,6 @@ import org.robolectric.util.reflector.Static;
@Implements(
value = ImsMmTelManager.class,
minSdk = VERSION_CODES.Q,
- looseSignatures = true,
isInAndroidSdk = false)
@SystemApi
public class ShadowImsMmTelManager {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInCallService.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInCallService.java
index 4ab7b896d..c3b4a3e41 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInCallService.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInCallService.java
@@ -3,6 +3,7 @@ package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.N_MR1;
import static android.os.Build.VERSION_CODES.P;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
import static org.robolectric.shadow.api.Shadow.invokeConstructor;
import static org.robolectric.util.reflector.Reflector.reflector;
@@ -18,7 +19,6 @@ import android.telecom.InCallService;
import android.telecom.ParcelableCall;
import android.telecom.Phone;
import com.android.internal.os.SomeArgs;
-import com.android.internal.telecom.IInCallAdapter;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
@@ -26,6 +26,7 @@ import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.ReflectionHelpers.ClassParameter;
import org.robolectric.util.reflector.Accessor;
+import org.robolectric.util.reflector.Constructor;
import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;
@@ -37,14 +38,23 @@ public class ShadowInCallService extends ShadowService {
private static final int MSG_SET_POST_DIAL_WAIT = 4;
private static final int MSG_ON_CONNECTION_EVENT = 9;
- private ShadowPhone shadowPhone;
private boolean canAddCall;
private boolean muted;
private int audioRoute = CallAudioState.ROUTE_EARPIECE;
private BluetoothDevice bluetoothDevice;
private int supportedRouteMask;
- @Implementation
+ /* Starting in Android V, the InCallService does not allow setting an InCallAdapter if Phone
+ * was already set. This is how the InCallService should be instantiated in tests:
+ * ```
+ * InCallServiceController serviceController =
+ * Robolectric.buildService(InCallServiceImpl.class, intent).create();
+ * IInCallService.Stub inCallServiceBinder =
+ * (IInCallService.Stub) serviceController.get().onBind(intent);
+ * inCallServiceBinder.setInCallAdapter(new InCallAdapterImpl());
+ * ```
+ * Do not rely on reflection for this and use the public APIs instead. */
+ @Implementation(maxSdk = UPSIDE_DOWN_CAKE)
protected void __constructor__() {
InCallAdapter adapter = Shadow.newInstanceOf(InCallAdapter.class);
Phone phone;
@@ -60,13 +70,17 @@ public class ShadowInCallService extends ShadowService {
ReflectionHelpers.callConstructor(
Phone.class, ClassParameter.from(InCallAdapter.class, adapter));
}
- shadowPhone = Shadow.extract(phone);
ReflectionHelpers.setField(inCallService, "mPhone", phone);
invokeConstructor(InCallService.class, inCallService);
}
+ /**
+ * @deprecated Please add calls by adding a Call using {@link
+ * android.telecom.InCallService.InCallServiceBinder}.
+ */
+ @Deprecated
public void addCall(Call call) {
- shadowPhone.addCall(call);
+ getShadowPhone().addCall(call);
}
public void addCall(ParcelableCall parcelableCall) {
@@ -96,8 +110,12 @@ public class ShadowInCallService extends ShadowService {
getHandler().obtainMessage(MSG_ON_CONNECTION_EVENT, args).sendToTarget();
}
+ /**
+ * @deprecated Please remove calls by invoking {@link Call#disconnect()}.
+ */
+ @Deprecated
public void removeCall(Call call) {
- shadowPhone.removeCall(call);
+ getShadowPhone().removeCall(call);
}
@Implementation
@@ -168,12 +186,34 @@ public class ShadowInCallService extends ShadowService {
*/
private boolean isInCallAdapterSet() {
Phone phone = reflector(ReflectorInCallService.class, inCallService).getPhone();
+ if (phone == null) {
+ return false;
+ }
InCallAdapter inCallAdapter = reflector(ReflectorPhone.class, phone).getInCallAdapter();
Object internalAdapter =
reflector(ReflectorInCallAdapter.class, inCallAdapter).getInternalInCallAdapter();
return internalAdapter != null;
}
+ private ShadowPhone getShadowPhone() {
+ if (reflector(ReflectorInCallService.class, inCallService).getPhone() == null) {
+ setPhone();
+ }
+ Phone phone = reflector(ReflectorInCallService.class, inCallService).getPhone();
+ return Shadow.extract(phone);
+ }
+
+ private void setPhone() {
+ InCallAdapter adapter = Shadow.newInstanceOf(InCallAdapter.class);
+ Phone phone;
+ if (VERSION.SDK_INT > N_MR1) {
+ phone = reflector(ReflectorPhone.class, inCallService).newInstance(adapter, "", 0);
+ } else {
+ phone = reflector(ReflectorPhone.class, inCallService).newInstance(adapter);
+ }
+ ReflectionHelpers.setField(inCallService, "mPhone", phone);
+ }
+
@ForType(InCallService.class)
interface ReflectorInCallService {
@Accessor("mHandler")
@@ -199,6 +239,12 @@ public class ShadowInCallService extends ShadowService {
interface ReflectorPhone {
@Accessor("mInCallAdapter")
InCallAdapter getInCallAdapter();
+
+ @Constructor
+ Phone newInstance(InCallAdapter inCallAdapter, String name, int type);
+
+ @Constructor
+ Phone newInstance(InCallAdapter inCallAdapter);
}
@ForType(InCallAdapter.class)
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowIncidentManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowIncidentManager.java
index d910f35c2..36c5aafac 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowIncidentManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowIncidentManager.java
@@ -2,10 +2,10 @@ package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.R;
+import android.annotation.NonNull;
import android.net.Uri;
import android.os.IncidentManager;
import android.os.IncidentManager.IncidentReport;
-import androidx.annotation.NonNull;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInputDevice.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInputDevice.java
index 70948566a..ea1efa2df 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInputDevice.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInputDevice.java
@@ -1,7 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.KITKAT;
-
import android.view.InputDevice;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
@@ -25,12 +23,12 @@ public class ShadowInputDevice {
return deviceName;
}
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected int getProductId() {
return productId;
}
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected int getVendorId() {
return vendorId;
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInputEventReceiver.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInputEventReceiver.java
index 9082a5b0e..10d9e017d 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInputEventReceiver.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInputEventReceiver.java
@@ -1,7 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-
import android.view.InputEventReceiver;
import dalvik.system.CloseGuard;
import org.robolectric.annotation.Implementation;
@@ -24,7 +22,7 @@ public class ShadowInputEventReceiver {
// ends up being rather spammy in test logs, so we no-op it.
}
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
protected void dispose(boolean finalized) {
CloseGuard closeGuard = inputEventReceiverReflector.getCloseGuard();
// Suppresses noisy CloseGuard warning
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInputManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInputManager.java
index e1e664759..30b4ea814 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInputManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInputManager.java
@@ -1,7 +1,6 @@
package org.robolectric.shadows;
import static android.os.Build.VERSION.SDK_INT;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.R;
import static android.os.Build.VERSION_CODES.TIRAMISU;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
@@ -36,7 +35,7 @@ public class ShadowInputManager {
return true;
}
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected boolean[] deviceHasKeys(int id, int[] keyCodes) {
return new boolean[keyCodes.length];
}
@@ -44,7 +43,21 @@ public class ShadowInputManager {
/** Used in {@link InputDevice#getDeviceIds()} */
@Implementation
protected int[] getInputDeviceIds() {
- return new int[0];
+ if (!ReflectionHelpers.hasField(InputManager.class, "mInputDevices")) {
+ return new int[0];
+ }
+
+ SparseArray<InputDevice> inputDevices = getInputDevices();
+ if (inputDevices == null) {
+ return new int[0];
+ }
+
+ int[] ids = new int[inputDevices.size()];
+ for (int i = 0; i < inputDevices.size(); i++) {
+ ids[i] = inputDevices.get(i).getId();
+ }
+
+ return ids;
}
@Implementation(maxSdk = TIRAMISU)
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInputMethodManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInputMethodManager.java
index dde69ced6..093bb2d12 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInputMethodManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInputMethodManager.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.N;
@@ -13,7 +12,6 @@ import static org.robolectric.util.reflector.Reflector.reflector;
import android.os.Bundle;
import android.os.IBinder;
-import android.os.Looper;
import android.os.ResultReceiver;
import android.util.SparseArray;
import android.view.View;
@@ -28,8 +26,6 @@ import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.Resetter;
-import org.robolectric.util.ReflectionHelpers;
-import org.robolectric.util.ReflectionHelpers.ClassParameter;
import org.robolectric.util.reflector.Accessor;
import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;
@@ -239,12 +235,7 @@ public class ShadowInputMethodManager {
// Android has a bug pre M where peekInstance was dereferenced without a null check:-
// https://github.com/aosp-mirror/platform_frameworks_base/commit/a046faaf38ad818e6b5e981a39fd7394cf7cee03
// So for earlier versions, just call through directly to getInstance()
- if (RuntimeEnvironment.getApiLevel() <= JELLY_BEAN_MR1) {
- return ReflectionHelpers.callStaticMethod(
- InputMethodManager.class,
- "getInstance",
- ClassParameter.from(Looper.class, Looper.getMainLooper()));
- } else if (RuntimeEnvironment.getApiLevel() <= LOLLIPOP_MR1) {
+ if (RuntimeEnvironment.getApiLevel() <= LOLLIPOP_MR1) {
return InputMethodManager.getInstance();
}
return reflector(_InputMethodManager_.class).peekInstance();
@@ -275,11 +266,7 @@ public class ShadowInputMethodManager {
public static void reset() {
int apiLevel = RuntimeEnvironment.getApiLevel();
_InputMethodManager_ _reflector = reflector(_InputMethodManager_.class);
- if (apiLevel <= JELLY_BEAN_MR1) {
- _reflector.setMInstance(null);
- } else {
- _reflector.setInstance(null);
- }
+ _reflector.setInstance(null);
if (apiLevel > P) {
_reflector.getInstanceMap().clear();
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInsetsController.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInsetsController.java
index 1c47aaba2..0cd93e68e 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInsetsController.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInsetsController.java
@@ -1,9 +1,9 @@
package org.robolectric.shadows;
+import android.annotation.RequiresApi;
import android.os.Build;
import android.view.InsetsController;
import android.view.WindowInsets;
-import androidx.annotation.RequiresApi;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.ReflectorObject;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInstrumentation.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInstrumentation.java
index c66b0622d..aff4fd06e 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInstrumentation.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInstrumentation.java
@@ -3,8 +3,6 @@ package org.robolectric.shadows;
import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.N;
@@ -16,6 +14,7 @@ import static com.google.common.util.concurrent.Futures.immediateFuture;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import static org.robolectric.util.reflector.Reflector.reflector;
+import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityThread;
import android.app.Fragment;
@@ -40,7 +39,6 @@ import android.os.Process;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Pair;
-import androidx.annotation.Nullable;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.Futures;
@@ -75,7 +73,7 @@ import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;
import org.robolectric.util.reflector.WithType;
-@Implements(value = Instrumentation.class, looseSignatures = true)
+@Implements(value = Instrumentation.class)
public class ShadowInstrumentation {
@RealObject private Instrumentation realObject;
@@ -198,7 +196,7 @@ public class ShadowInstrumentation {
*
* <p>Currently ignores the user.
*/
- @Implementation(minSdk = JELLY_BEAN_MR1, maxSdk = N_MR1)
+ @Implementation(maxSdk = N_MR1)
protected ActivityResult execStartActivity(
Context who,
IBinder contextThread,
@@ -234,7 +232,7 @@ public class ShadowInstrumentation {
ShadowWindowManagerGlobal.setInTouchMode(inTouchMode);
}
- @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = M)
+ @Implementation(maxSdk = M)
protected UiAutomation getUiAutomation() {
return getUiAutomation(0);
}
@@ -1064,14 +1062,6 @@ public class ShadowInstrumentation {
/** Reflector interface for {@link Instrumentation}'s internals. */
@ForType(Instrumentation.class)
public interface _Instrumentation_ {
- // <= JELLY_BEAN_MR1:
- void init(
- ActivityThread thread,
- Context instrContext,
- Context appContext,
- ComponentName component,
- @WithType("android.app.IInstrumentationWatcher") Object watcher);
-
// > JELLY_BEAN_MR1:
void init(
ActivityThread thread,
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLauncherApps.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLauncherApps.java
index 0cbc9fb21..d640f23fc 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLauncherApps.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLauncherApps.java
@@ -9,6 +9,8 @@ import static android.os.Build.VERSION_CODES.P;
import static android.os.Build.VERSION_CODES.Q;
import static org.robolectric.util.reflector.Reflector.reflector;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
@@ -26,8 +28,6 @@ import android.os.Looper;
import android.os.Process;
import android.os.UserHandle;
import android.util.Pair;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyAssetManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyAssetManager.java
index 33353358b..904712f50 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyAssetManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyAssetManager.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
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;
@@ -27,6 +26,7 @@ import android.os.ParcelFileDescriptor;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.util.TypedValue;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Ordering;
import dalvik.system.VMRuntime;
import java.io.ByteArrayInputStream;
@@ -621,7 +621,8 @@ public class ShadowLegacyAssetManager extends ShadowAssetManager {
return 1;
}
- @HiddenApi @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = M)
+ @HiddenApi
+ @Implementation(maxSdk = M)
final protected int addAssetPathNative(String path) {
return addAssetPathNative(path, false);
}
@@ -1488,6 +1489,12 @@ public class ShadowLegacyAssetManager extends ShadowAssetManager {
}
}
+ @VisibleForTesting
+ @Override
+ long getNativePtr() {
+ return 0;
+ }
+
@ForType(AssetManager.class)
interface AssetManagerReflector {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyBitmap.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyBitmap.java
index d345491dc..715bc87a0 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyBitmap.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyBitmap.java
@@ -1,7 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.Q;
@@ -39,7 +37,6 @@ import java.util.Arrays;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
-import org.robolectric.annotation.RealObject;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.ReflectionHelpers;
import org.robolectric.versioning.AndroidVersions.U;
@@ -55,7 +52,6 @@ public class ShadowLegacyBitmap extends ShadowBitmap {
InputStream createdFromStream;
FileDescriptor createdFromFileDescriptor;
byte[] createdFromBytes;
- @RealObject private Bitmap realBitmap;
private Bitmap createdFromBitmap;
private Bitmap scaledFromBitmap;
private int createdFromX = -1;
@@ -83,13 +79,13 @@ public class ShadowLegacyBitmap extends ShadowBitmap {
return createBitmap((DisplayMetrics) null, width, height, config);
}
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
protected static Bitmap createBitmap(
DisplayMetrics displayMetrics, int width, int height, Bitmap.Config config) {
return createBitmap(displayMetrics, width, height, config, true);
}
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
protected static Bitmap createBitmap(
DisplayMetrics displayMetrics,
int width,
@@ -199,7 +195,7 @@ public class ShadowLegacyBitmap extends ShadowBitmap {
return createBitmap(null, colors, offset, stride, width, height, config);
}
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
protected static Bitmap createBitmap(
DisplayMetrics displayMetrics,
int[] colors,
@@ -557,7 +553,7 @@ public class ShadowLegacyBitmap extends ShadowBitmap {
return newBitmap;
}
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected final int getAllocationByteCount() {
return getRowBytes() * getHeight();
}
@@ -567,7 +563,7 @@ public class ShadowLegacyBitmap extends ShadowBitmap {
return config;
}
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected void setConfig(Bitmap.Config config) {
this.config = config;
}
@@ -624,12 +620,12 @@ public class ShadowLegacyBitmap extends ShadowBitmap {
return extractAlpha();
}
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
protected final boolean hasMipMap() {
return hasMipMap;
}
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
protected final void setHasMipMap(boolean hasMipMap) {
this.hasMipMap = hasMipMap;
}
@@ -639,7 +635,7 @@ public class ShadowLegacyBitmap extends ShadowBitmap {
return width;
}
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected void setWidth(int width) {
this.width = width;
}
@@ -649,7 +645,7 @@ public class ShadowLegacyBitmap extends ShadowBitmap {
return height;
}
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected void setHeight(int height) {
this.height = height;
}
@@ -761,7 +757,7 @@ public class ShadowLegacyBitmap extends ShadowBitmap {
}
}
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected void reconfigure(int width, int height, Bitmap.Config config) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && this.config == Bitmap.Config.HARDWARE) {
throw new IllegalStateException("native-backed bitmaps may not be reconfigured");
@@ -777,12 +773,12 @@ public class ShadowLegacyBitmap extends ShadowBitmap {
bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
}
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected boolean isPremultiplied() {
return requestPremultiplied && hasAlpha();
}
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected void setPremultiplied(boolean isPremultiplied) {
this.requestPremultiplied = isPremultiplied;
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyCanvas.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyCanvas.java
index 9c63ed3d4..0db52d49f 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyCanvas.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyCanvas.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.KITKAT_WATCH;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1;
@@ -478,7 +477,7 @@ public class ShadowLegacyCanvas extends ShadowCanvas {
getNativeCanvas().restoreToCount(saveCount);
}
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected void release() {
nativeObjectRegistry.unregister(getNativeId());
canvasReflector.release();
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyLooper.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyLooper.java
index 2fb348ebd..e7069b480 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyLooper.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyLooper.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static org.robolectric.RuntimeEnvironment.isMainThread;
import static org.robolectric.shadow.api.Shadow.invokeConstructor;
import static org.robolectric.util.ReflectionHelpers.ClassParameter.from;
@@ -142,7 +141,7 @@ public class ShadowLegacyLooper extends ShadowLooper {
quitUnchecked();
}
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected void quitSafely() {
quit();
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyMatrix.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyMatrix.java
index 1401816df..b72627e5f 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyMatrix.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyMatrix.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import android.graphics.Matrix;
@@ -378,7 +377,7 @@ public class ShadowLegacyMatrix extends ShadowMatrix {
}
}
- @Implementation(minSdk = KITKAT)
+ @Implementation
@Override
public int hashCode() {
return Objects.hashCode(simpleMatrix);
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyMessageQueue.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyMessageQueue.java
index 9934fb4ef..5763ef5f8 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyMessageQueue.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyMessageQueue.java
@@ -1,7 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.KITKAT_WATCH;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1;
@@ -55,7 +53,7 @@ public class ShadowLegacyMessageQueue extends ShadowMessageQueue {
}
@HiddenApi
- @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = KITKAT_WATCH)
+ @Implementation(maxSdk = KITKAT_WATCH)
public static void nativeDestroy(int ptr) {
nativeDestroy((long) ptr);
}
@@ -64,7 +62,7 @@ public class ShadowLegacyMessageQueue extends ShadowMessageQueue {
protected static void nativeDestroy(long ptr) {}
@HiddenApi
- @Implementation(minSdk = KITKAT, maxSdk = KITKAT_WATCH)
+ @Implementation(maxSdk = KITKAT_WATCH)
public static boolean nativeIsIdling(int ptr) {
return nativeIsIdling((long) ptr);
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyPath.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyPath.java
index b4f113a4a..4f2040826 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyPath.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyPath.java
@@ -1,7 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static org.robolectric.shadow.api.Shadow.extract;
import static org.robolectric.shadows.ShadowPath.Point.Type.LINE_TO;
@@ -174,7 +172,7 @@ public class ShadowLegacyPath extends ShadowPath {
mPath.append(shadowSrc.mPath, false /*connect*/);
}
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected boolean op(Path path1, Path path2, Path.Op op) {
Log.w(TAG, "android.graphics.Path#op() not supported yet.");
return false;
@@ -406,12 +404,12 @@ public class ShadowLegacyPath extends ShadowPath {
false);
}
- @Implementation(minSdk = JELLY_BEAN)
+ @Implementation
protected void addRoundRect(RectF rect, float rx, float ry, Direction dir) {
addRoundRect(rect.left, rect.top, rect.right, rect.bottom, rx, ry, dir);
}
- @Implementation(minSdk = JELLY_BEAN)
+ @Implementation
protected void addRoundRect(RectF rect, float[] radii, Direction dir) {
if (rect == null) {
throw new NullPointerException("need rect parameter");
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacySystemClock.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacySystemClock.java
index 11c2f9620..319b89586 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacySystemClock.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacySystemClock.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static android.os.Build.VERSION_CODES.P;
import android.os.SystemClock;
@@ -59,7 +58,7 @@ public class ShadowLegacySystemClock extends ShadowSystemClock {
return uptimeMillis();
}
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
protected static long elapsedRealtimeNanos() {
return elapsedRealtime() * MILLIS_PER_NANO;
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLinux.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLinux.java
index 41a109c7e..58405af44 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLinux.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLinux.java
@@ -1,6 +1,7 @@
package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.N_MR1;
+import static android.os.Build.VERSION_CODES.R;
import android.os.Build;
import android.system.ErrnoException;
@@ -79,6 +80,18 @@ public class ShadowLinux {
}
}
+ @Implementation(minSdk = R)
+ protected FileDescriptor memfd_create(String name, int flags) throws ErrnoException {
+ try {
+ File tempFile = File.createTempFile(name, /* suffix= */ "robo_memfd");
+ tempFile.deleteOnExit();
+ RandomAccessFile randomAccessFile = new RandomAccessFile(tempFile, /* mode= */ "rw");
+ return randomAccessFile.getFD();
+ } catch (IOException e) {
+ throw new ErrnoException("memfd_create", OsConstants.EIO, e);
+ }
+ }
+
@Implementation
protected int pread(FileDescriptor fd, byte[] bytes, int byteOffset, int byteCount, long offset)
throws ErrnoException, InterruptedIOException {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocaleData.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocaleData.java
index 803495e08..ee3748664 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocaleData.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocaleData.java
@@ -1,8 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
-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;
@@ -62,19 +59,17 @@ public class ShadowLocaleData {
};
_LocaleData_ localDataReflector = reflector(_LocaleData_.class, localeData);
- if (getApiLevel() >= JELLY_BEAN_MR1) {
- localeData.tinyMonthNames =
- new String[] {"J", "F", "M", "A", "M", "J", "J", "A", "S", "O", "N", "D"};
- localeData.tinyStandAloneMonthNames = localeData.tinyMonthNames;
- localeData.tinyWeekdayNames = new String[] {"", "S", "M", "T", "W", "T", "F", "S"};
- localeData.tinyStandAloneWeekdayNames = localeData.tinyWeekdayNames;
-
- if (getApiLevel() <= R) {
- localDataReflector.setYesterday("Yesterday");
- }
- localeData.today = "Today";
- localeData.tomorrow = "Tomorrow";
+ localeData.tinyMonthNames =
+ new String[] {"J", "F", "M", "A", "M", "J", "J", "A", "S", "O", "N", "D"};
+ localeData.tinyStandAloneMonthNames = localeData.tinyMonthNames;
+ localeData.tinyWeekdayNames = new String[] {"", "S", "M", "T", "W", "T", "F", "S"};
+ localeData.tinyStandAloneWeekdayNames = localeData.tinyWeekdayNames;
+
+ if (getApiLevel() <= R) {
+ localDataReflector.setYesterday("Yesterday");
}
+ localeData.today = "Today";
+ localeData.tomorrow = "Tomorrow";
localeData.longStandAloneMonthNames = localeData.longMonthNames;
localeData.shortStandAloneMonthNames = localeData.shortMonthNames;
@@ -97,7 +92,7 @@ public class ShadowLocaleData {
if (getApiLevel() >= M) {
localeData.timeFormat_hm = "h:mm a";
localeData.timeFormat_Hm = "HH:mm";
- } else if (getApiLevel() >= JELLY_BEAN_MR2) {
+ } else {
localDataReflector.setTimeFormat12("h:mm a");
localDataReflector.setTimeFormat24("HH:mm");
}
@@ -106,7 +101,7 @@ public class ShadowLocaleData {
localDataReflector.setLongDateFormat("MMMM d, y");
localDataReflector.setMediumDateFormat("MMM d, y");
localDataReflector.setShortDateFormat("M/d/yy");
- if (getApiLevel() >= KITKAT && getApiLevel() < M) {
+ if (getApiLevel() < M) {
localDataReflector.setShortDateFormat4("M/d/yyyy");
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocaleManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocaleManager.java
index d96a1e2c4..47843b53c 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocaleManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocaleManager.java
@@ -1,11 +1,11 @@
package org.robolectric.shadows;
+import android.annotation.RequiresApi;
import android.app.LocaleManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build.VERSION_CODES;
import android.os.LocaleList;
-import androidx.annotation.RequiresApi;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
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 57fc6e943..d33e639c6 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocationManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLocationManager.java
@@ -12,6 +12,8 @@ import static android.provider.Settings.Secure.LOCATION_MODE_SENSORS_ONLY;
import static android.provider.Settings.Secure.LOCATION_PROVIDERS_ALLOWED;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import android.annotation.Nullable;
+import android.annotation.RequiresApi;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
import android.content.Context;
@@ -39,9 +41,7 @@ import android.os.WorkSource;
import android.provider.Settings.Secure;
import android.text.TextUtils;
import android.util.Log;
-import androidx.annotation.GuardedBy;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
+import com.android.internal.annotations.GuardedBy;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import java.lang.reflect.Constructor;
@@ -370,11 +370,6 @@ public class ShadowLocationManager {
@Implementation
@Nullable
protected LocationProvider getProvider(String name) {
- if (RuntimeEnvironment.getApiLevel() < VERSION_CODES.KITKAT) {
- // jelly bean has no way to properly construct a LocationProvider, we give up
- return null;
- }
-
ProviderEntry providerEntry = getProviderEntry(name);
if (providerEntry == null) {
return null;
@@ -813,7 +808,7 @@ public class ShadowLocationManager {
request.getProvider(), new RoboLocationRequest(request), executor, listener);
}
- @Implementation(minSdk = VERSION_CODES.KITKAT)
+ @Implementation
protected void requestLocationUpdates(
@Nullable LocationRequest request, LocationListener listener, Looper looper) {
if (request == null) {
@@ -833,7 +828,7 @@ public class ShadowLocationManager {
listener);
}
- @Implementation(minSdk = VERSION_CODES.KITKAT)
+ @Implementation
protected void requestLocationUpdates(
@Nullable LocationRequest request, PendingIntent pendingIntent) {
if (request == null) {
@@ -915,7 +910,6 @@ public class ShadowLocationManager {
* <p>Prior to Android S {@link LocationRequest} equality is not well defined, so prefer using
* {@link #getLegacyLocationRequests(String)} instead if equality is required for testing.
*/
- @RequiresApi(VERSION_CODES.KITKAT)
public List<LocationRequest> getLocationRequests(String provider) {
ProviderEntry providerEntry = getProviderEntry(provider);
if (providerEntry == null) {
@@ -1802,7 +1796,6 @@ public class ShadowLocationManager {
private final float minUpdateDistanceMeters;
private final boolean singleShot;
- @RequiresApi(VERSION_CODES.KITKAT)
public RoboLocationRequest(LocationRequest locationRequest) {
this.locationRequest = Objects.requireNonNull(locationRequest);
intervalMillis = 0;
@@ -1812,20 +1805,15 @@ public class ShadowLocationManager {
public RoboLocationRequest(
String provider, long intervalMillis, float minUpdateDistanceMeters, boolean singleShot) {
- if (RuntimeEnvironment.getApiLevel() >= VERSION_CODES.KITKAT) {
- locationRequest =
- LocationRequest.createFromDeprecatedProvider(
- provider, intervalMillis, minUpdateDistanceMeters, singleShot);
- } else {
- locationRequest = null;
- }
+ locationRequest =
+ LocationRequest.createFromDeprecatedProvider(
+ provider, intervalMillis, minUpdateDistanceMeters, singleShot);
this.intervalMillis = intervalMillis;
this.minUpdateDistanceMeters = minUpdateDistanceMeters;
this.singleShot = singleShot;
}
- @RequiresApi(VERSION_CODES.KITKAT)
public LocationRequest getLocationRequest() {
return (LocationRequest) Objects.requireNonNull(locationRequest);
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLog.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLog.java
index 84df122b5..82e4e43d2 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLog.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLog.java
@@ -1,6 +1,10 @@
package org.robolectric.shadows;
+import static org.robolectric.util.reflector.Reflector.reflector;
+
import android.util.Log;
+import android.util.Log.TerribleFailure;
+import android.util.Log.TerribleFailureHandler;
import com.google.common.base.Ascii;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
@@ -13,9 +17,16 @@ import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.Supplier;
+import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.Resetter;
+import org.robolectric.util.Util;
+import org.robolectric.util.reflector.Accessor;
+import org.robolectric.util.reflector.Constructor;
+import org.robolectric.util.reflector.ForType;
+import org.robolectric.util.reflector.Static;
+import org.robolectric.versioning.AndroidVersions.L;
/** Controls the behavior of {@link android.util.Log} and provides access to log messages. */
@Implements(Log.class)
@@ -103,8 +114,18 @@ public class ShadowLog {
@Implementation
protected static int wtf(String tag, String msg, Throwable throwable) {
addLog(Log.ASSERT, tag, msg, throwable);
+ // invoking the wtfHandler
+ TerribleFailure terribleFailure =
+ reflector(TerribleFailureReflector.class).newTerribleFailure(msg, throwable);
if (wtfIsFatal) {
- throw new TerribleFailure(msg, throwable);
+ Util.sneakyThrow(terribleFailure);
+ }
+ TerribleFailureHandler terribleFailureHandler = reflector(LogReflector.class).getWtfHandler();
+ if (RuntimeEnvironment.getApiLevel() >= L.SDK_INT) {
+ terribleFailureHandler.onTerribleFailure(tag, terribleFailure, false);
+ } else {
+ reflector(TerribleFailureHandlerReflector.class, terribleFailureHandler)
+ .onTerribleFailure(tag, terribleFailure);
}
return 0;
}
@@ -339,14 +360,21 @@ public class ShadowLog {
}
}
- /**
- * Failure thrown when wtf_is_fatal is true and Log.wtf is called. This is a parallel
- * implementation of framework's hidden API {@link android.util.Log#TerribleFailure}, to allow
- * tests to catch / expect these exceptions.
- */
- public static final class TerribleFailure extends RuntimeException {
- TerribleFailure(String msg, Throwable cause) {
- super(msg, cause);
- }
+ @ForType(Log.class)
+ interface LogReflector {
+ @Static
+ @Accessor("sWtfHandler")
+ TerribleFailureHandler getWtfHandler();
+ }
+
+ @ForType(TerribleFailureHandler.class)
+ interface TerribleFailureHandlerReflector {
+ void onTerribleFailure(String tag, TerribleFailure what);
+ }
+
+ @ForType(TerribleFailure.class)
+ interface TerribleFailureReflector {
+ @Constructor
+ TerribleFailure newTerribleFailure(String msg, Throwable cause);
}
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaActionSound.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaActionSound.java
index 82e405054..4af14e8b1 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaActionSound.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaActionSound.java
@@ -4,7 +4,6 @@ import static android.os.Build.VERSION_CODES.TIRAMISU;
import static org.robolectric.util.reflector.Reflector.reflector;
import android.media.MediaActionSound;
-import android.os.Build;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
@@ -16,7 +15,7 @@ import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;
/** A shadow implementation of {@link android.media.MediaActionSound}. */
-@Implements(value = MediaActionSound.class, minSdk = Build.VERSION_CODES.JELLY_BEAN)
+@Implements(value = MediaActionSound.class)
public class ShadowMediaActionSound {
@RealObject MediaActionSound realObject;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaCodec.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaCodec.java
index ed7eb8c85..3bee37b16 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaCodec.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaCodec.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.N_MR1;
import static android.os.Build.VERSION_CODES.O;
@@ -54,7 +53,7 @@ import org.robolectric.versioning.AndroidVersions.U;
* implementation will present an input buffer, which will be copied to an output buffer once
* queued, which will be subsequently presented to the callback handler.
*/
-@Implements(value = MediaCodec.class, minSdk = JELLY_BEAN, looseSignatures = true)
+@Implements(value = MediaCodec.class, looseSignatures = true)
public class ShadowMediaCodec {
private static final int DEFAULT_BUFFER_SIZE = 512;
@VisibleForTesting static final int BUFFER_COUNT = 10;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaPlayer.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaPlayer.java
index f08a53c3f..8a6926c63 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaPlayer.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaPlayer.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.N;
import static android.os.Build.VERSION_CODES.N_MR1;
@@ -606,7 +605,7 @@ public class ShadowMediaPlayer extends ShadowPlayerBase {
setDataSource(context, uri, null, null);
}
- @Implementation(minSdk = ICE_CREAM_SANDWICH, maxSdk = N_MR1)
+ @Implementation(maxSdk = N_MR1)
protected void setDataSource(Context context, Uri uri, Map<String, String> headers)
throws IOException {
setDataSource(context, uri, headers, null);
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaRouter.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaRouter.java
index 17da8e1d9..0099c95ab 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaRouter.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaRouter.java
@@ -1,15 +1,11 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-
import android.media.AudioRoutesInfo;
import android.media.MediaRouter;
import android.media.MediaRouter.RouteInfo;
import android.os.Parcel;
import android.text.TextUtils;
import javax.annotation.Nullable;
-import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.annotation.Resetter;
@@ -30,16 +26,7 @@ public class ShadowMediaRouter {
public void addBluetoothRoute() {
updateBluetoothAudioRoute(BLUETOOTH_DEVICE_NAME);
- if (RuntimeEnvironment.getApiLevel() <= JELLY_BEAN_MR1) {
- ReflectionHelpers.callInstanceMethod(
- MediaRouter.class,
- realObject,
- "selectRouteInt",
- ClassParameter.from(int.class, MediaRouter.ROUTE_TYPE_LIVE_AUDIO),
- ClassParameter.from(RouteInfo.class, getBluetoothA2dpRoute()));
- } else {
- realObject.selectRoute(MediaRouter.ROUTE_TYPE_LIVE_AUDIO, getBluetoothA2dpRoute());
- }
+ realObject.selectRoute(MediaRouter.ROUTE_TYPE_LIVE_AUDIO, getBluetoothA2dpRoute());
}
/** Removes the Bluetooth A2DP route, simulating disconnecting the Bluetooth device. */
@@ -85,9 +72,7 @@ public class ShadowMediaRouter {
private void callUpdateAudioRoutes(AudioRoutesInfo routesInfo) {
ReflectionHelpers.callInstanceMethod(
ReflectionHelpers.getStaticField(MediaRouter.class, "sStatic"),
- RuntimeEnvironment.getApiLevel() <= JELLY_BEAN
- ? "updateRoutes"
- : "updateAudioRoutes",
+ "updateAudioRoutes",
ClassParameter.from(AudioRoutesInfo.class, routesInfo));
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeAllocationRegistry.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeAllocationRegistry.java
index ab3c6e3d5..e7018f36c 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeAllocationRegistry.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeAllocationRegistry.java
@@ -12,13 +12,15 @@ import org.robolectric.shadows.ShadowNativeAllocationRegistry.Picker;
import org.robolectric.util.reflector.Accessor;
import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link NativeAllocationRegistry} that is backed by native code */
@Implements(
value = NativeAllocationRegistry.class,
minSdk = O,
isInAndroidSdk = false,
- shadowPicker = Picker.class)
+ shadowPicker = Picker.class,
+ callNativeMethodsByDefault = true)
public class ShadowNativeAllocationRegistry {
@RealObject protected NativeAllocationRegistry realNativeAllocationRegistry;
@@ -42,7 +44,7 @@ public class ShadowNativeAllocationRegistry {
!= 0;
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void applyFreeFunction(long freeFunction, long nativePtr) {
NativeAllocationRegistryNatives.applyFreeFunction(freeFunction, nativePtr);
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeAnimatedImageDrawable.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeAnimatedImageDrawable.java
index a73f0a61f..562ac12ca 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeAnimatedImageDrawable.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeAnimatedImageDrawable.java
@@ -15,11 +15,16 @@ import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.AnimatedImageDrawableNatives;
import org.robolectric.shadows.ShadowNativeAnimatedImageDrawable.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link AnimatedImageDrawable} that is backed by native code */
-@Implements(value = AnimatedImageDrawable.class, shadowPicker = Picker.class, minSdk = P)
+@Implements(
+ value = AnimatedImageDrawable.class,
+ shadowPicker = Picker.class,
+ minSdk = P,
+ callNativeMethodsByDefault = true)
public class ShadowNativeAnimatedImageDrawable extends ShadowDrawable {
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static long nCreate(
long nativeImageDecoder,
ImageDecoder decoder,
@@ -40,52 +45,52 @@ public class ShadowNativeAnimatedImageDrawable extends ShadowDrawable {
return nCreate(nativeImageDecoder, decoder, width, height, 0, false, cropRect);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long nGetNativeFinalizer() {
return AnimatedImageDrawableNatives.nGetNativeFinalizer();
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long nDraw(long nativePtr, long canvasNativePtr) {
return AnimatedImageDrawableNatives.nDraw(nativePtr, canvasNativePtr);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nSetAlpha(long nativePtr, int alpha) {
AnimatedImageDrawableNatives.nSetAlpha(nativePtr, alpha);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static int nGetAlpha(long nativePtr) {
return AnimatedImageDrawableNatives.nGetAlpha(nativePtr);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nSetColorFilter(long nativePtr, long nativeFilter) {
AnimatedImageDrawableNatives.nSetColorFilter(nativePtr, nativeFilter);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nIsRunning(long nativePtr) {
return AnimatedImageDrawableNatives.nIsRunning(nativePtr);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nStart(long nativePtr) {
return AnimatedImageDrawableNatives.nStart(nativePtr);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nStop(long nativePtr) {
return AnimatedImageDrawableNatives.nStop(nativePtr);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static int nGetRepeatCount(long nativePtr) {
return AnimatedImageDrawableNatives.nGetRepeatCount(nativePtr);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nSetRepeatCount(long nativePtr, int repeatCount) {
AnimatedImageDrawableNatives.nSetRepeatCount(nativePtr, repeatCount);
}
@@ -95,23 +100,23 @@ public class ShadowNativeAnimatedImageDrawable extends ShadowDrawable {
AnimatedImageDrawableNatives.nSetOnAnimationEndListener(nativePtr, drawable);
}
- @Implementation(minSdk = TIRAMISU)
+ @Implementation(minSdk = TIRAMISU, maxSdk = U.SDK_INT)
protected static void nSetOnAnimationEndListener(
long nativePtr, WeakReference<AnimatedImageDrawable> drawable) {
AnimatedImageDrawableNatives.nSetOnAnimationEndListener(nativePtr, drawable.get());
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long nNativeByteSize(long nativePtr) {
return AnimatedImageDrawableNatives.nNativeByteSize(nativePtr);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nSetMirrored(long nativePtr, boolean mirror) {
AnimatedImageDrawableNatives.nSetMirrored(nativePtr, mirror);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static void nSetBounds(long nativePtr, Rect rect) {
AnimatedImageDrawableNatives.nSetBounds(nativePtr, rect);
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeAnimatedVectorDrawable.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeAnimatedVectorDrawable.java
index 5286b0794..156464268 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeAnimatedVectorDrawable.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeAnimatedVectorDrawable.java
@@ -15,9 +15,14 @@ import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.shadows.ShadowNativeAnimatedVectorDrawable.Picker;
import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link AnimatedVectorDrawable} that is backed by native code */
-@Implements(value = AnimatedVectorDrawable.class, minSdk = O, shadowPicker = Picker.class)
+@Implements(
+ value = AnimatedVectorDrawable.class,
+ minSdk = O,
+ callNativeMethodsByDefault = true,
+ shadowPicker = Picker.class)
public class ShadowNativeAnimatedVectorDrawable extends ShadowDrawable {
@RealObject protected AnimatedVectorDrawable realAnimatedVectorDrawable;
@@ -44,18 +49,18 @@ public class ShadowNativeAnimatedVectorDrawable extends ShadowDrawable {
return startInitiated;
}
- @Implementation(minSdk = N)
+ @Implementation(minSdk = N, maxSdk = U.SDK_INT)
protected static long nCreateAnimatorSet() {
DefaultNativeRuntimeLoader.injectAndLoad();
return AnimatedVectorDrawableNatives.nCreateAnimatorSet();
}
- @Implementation(minSdk = N_MR1)
+ @Implementation(minSdk = N_MR1, maxSdk = U.SDK_INT)
protected static void nSetVectorDrawableTarget(long animatorPtr, long vectorDrawablePtr) {
AnimatedVectorDrawableNatives.nSetVectorDrawableTarget(animatorPtr, vectorDrawablePtr);
}
- @Implementation(minSdk = N_MR1)
+ @Implementation(minSdk = N_MR1, maxSdk = U.SDK_INT)
protected static void nAddAnimator(
long setPtr,
long propertyValuesHolder,
@@ -74,67 +79,67 @@ public class ShadowNativeAnimatedVectorDrawable extends ShadowDrawable {
repeatMode);
}
- @Implementation(minSdk = N)
+ @Implementation(minSdk = N, maxSdk = U.SDK_INT)
protected static void nSetPropertyHolderData(long nativePtr, float[] data, int length) {
AnimatedVectorDrawableNatives.nSetPropertyHolderData(nativePtr, data, length);
}
- @Implementation(minSdk = N_MR1)
+ @Implementation(minSdk = N_MR1, maxSdk = U.SDK_INT)
protected static void nSetPropertyHolderData(long nativePtr, int[] data, int length) {
AnimatedVectorDrawableNatives.nSetPropertyHolderData(nativePtr, data, length);
}
- @Implementation(minSdk = N)
+ @Implementation(minSdk = N, maxSdk = U.SDK_INT)
protected static void nStart(long animatorSetPtr, VectorDrawableAnimatorRT set, int id) {
AnimatedVectorDrawableNatives.nStart(animatorSetPtr, set, id);
}
- @Implementation(minSdk = N)
+ @Implementation(minSdk = N, maxSdk = U.SDK_INT)
protected static void nReverse(long animatorSetPtr, VectorDrawableAnimatorRT set, int id) {
AnimatedVectorDrawableNatives.nReverse(animatorSetPtr, set, id);
}
- @Implementation(minSdk = N)
+ @Implementation(minSdk = N, maxSdk = U.SDK_INT)
protected static long nCreateGroupPropertyHolder(
long nativePtr, int propertyId, float startValue, float endValue) {
return AnimatedVectorDrawableNatives.nCreateGroupPropertyHolder(
nativePtr, propertyId, startValue, endValue);
}
- @Implementation(minSdk = N)
+ @Implementation(minSdk = N, maxSdk = U.SDK_INT)
protected static long nCreatePathDataPropertyHolder(
long nativePtr, long startValuePtr, long endValuePtr) {
return AnimatedVectorDrawableNatives.nCreatePathDataPropertyHolder(
nativePtr, startValuePtr, endValuePtr);
}
- @Implementation(minSdk = N)
+ @Implementation(minSdk = N, maxSdk = U.SDK_INT)
protected static long nCreatePathColorPropertyHolder(
long nativePtr, int propertyId, int startValue, int endValue) {
return AnimatedVectorDrawableNatives.nCreatePathColorPropertyHolder(
nativePtr, propertyId, startValue, endValue);
}
- @Implementation(minSdk = N)
+ @Implementation(minSdk = N, maxSdk = U.SDK_INT)
protected static long nCreatePathPropertyHolder(
long nativePtr, int propertyId, float startValue, float endValue) {
return AnimatedVectorDrawableNatives.nCreatePathPropertyHolder(
nativePtr, propertyId, startValue, endValue);
}
- @Implementation(minSdk = N)
+ @Implementation(minSdk = N, maxSdk = U.SDK_INT)
protected static long nCreateRootAlphaPropertyHolder(
long nativePtr, float startValue, float endValue) {
return AnimatedVectorDrawableNatives.nCreateRootAlphaPropertyHolder(
nativePtr, startValue, endValue);
}
- @Implementation(minSdk = N)
+ @Implementation(minSdk = N, maxSdk = U.SDK_INT)
protected static void nEnd(long animatorSetPtr) {
AnimatedVectorDrawableNatives.nEnd(animatorSetPtr);
}
- @Implementation(minSdk = N)
+ @Implementation(minSdk = N, maxSdk = U.SDK_INT)
protected static void nReset(long animatorSetPtr) {
AnimatedVectorDrawableNatives.nReset(animatorSetPtr);
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBaseCanvas.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBaseCanvas.java
index 945575eb1..0e55090e2 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBaseCanvas.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBaseCanvas.java
@@ -28,12 +28,13 @@ import org.robolectric.versioning.AndroidVersions.U;
value = BaseCanvas.class,
minSdk = O,
shadowPicker = Picker.class,
- isInAndroidSdk = false)
+ isInAndroidSdk = false,
+ callNativeMethodsByDefault = true)
public class ShadowNativeBaseCanvas extends ShadowCanvas {
@RealObject BaseCanvas realBaseCanvas;
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static void nDrawBitmap(
long nativeCanvas,
long bitmapHandle,
@@ -54,7 +55,7 @@ public class ShadowNativeBaseCanvas extends ShadowCanvas {
bitmapDensity);
}
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static void nDrawBitmap(
long nativeCanvas,
long bitmapHandle,
@@ -85,7 +86,7 @@ public class ShadowNativeBaseCanvas extends ShadowCanvas {
bitmapDensity);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nDrawBitmap(
long nativeCanvas,
int[] colors,
@@ -153,64 +154,64 @@ public class ShadowNativeBaseCanvas extends ShadowCanvas {
bitmapDensity);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nDrawColor(long nativeCanvas, int color, int mode) {
BaseCanvasNatives.nDrawColor(nativeCanvas, color, mode);
}
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static void nDrawColor(
long nativeCanvas, long nativeColorSpace, @ColorLong long color, int mode) {
BaseCanvasNatives.nDrawColor(nativeCanvas, nativeColorSpace, color, mode);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nDrawPaint(long nativeCanvas, long nativePaint) {
BaseCanvasNatives.nDrawPaint(nativeCanvas, nativePaint);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nDrawPoint(long canvasHandle, float x, float y, long paintHandle) {
BaseCanvasNatives.nDrawPoint(canvasHandle, x, y, paintHandle);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nDrawPoints(
long canvasHandle, float[] pts, int offset, int count, long paintHandle) {
BaseCanvasNatives.nDrawPoints(canvasHandle, pts, offset, count, paintHandle);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nDrawLine(
long nativeCanvas, float startX, float startY, float stopX, float stopY, long nativePaint) {
BaseCanvasNatives.nDrawLine(nativeCanvas, startX, startY, stopX, stopY, nativePaint);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nDrawLines(
long canvasHandle, float[] pts, int offset, int count, long paintHandle) {
BaseCanvasNatives.nDrawLines(canvasHandle, pts, offset, count, paintHandle);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nDrawRect(
long nativeCanvas, float left, float top, float right, float bottom, long nativePaint) {
BaseCanvasNatives.nDrawRect(nativeCanvas, left, top, right, bottom, nativePaint);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nDrawOval(
long nativeCanvas, float left, float top, float right, float bottom, long nativePaint) {
BaseCanvasNatives.nDrawOval(nativeCanvas, left, top, right, bottom, nativePaint);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nDrawCircle(
long nativeCanvas, float cx, float cy, float radius, long nativePaint) {
BaseCanvasNatives.nDrawCircle(nativeCanvas, cx, cy, radius, nativePaint);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nDrawArc(
long nativeCanvas,
float left,
@@ -225,7 +226,7 @@ public class ShadowNativeBaseCanvas extends ShadowCanvas {
nativeCanvas, left, top, right, bottom, startAngle, sweep, useCenter, nativePaint);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nDrawRoundRect(
long nativeCanvas,
float left,
@@ -238,7 +239,7 @@ public class ShadowNativeBaseCanvas extends ShadowCanvas {
BaseCanvasNatives.nDrawRoundRect(nativeCanvas, left, top, right, bottom, rx, ry, nativePaint);
}
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static void nDrawDoubleRoundRect(
long nativeCanvas,
float outerLeft,
@@ -271,7 +272,7 @@ public class ShadowNativeBaseCanvas extends ShadowCanvas {
nativePaint);
}
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static void nDrawDoubleRoundRect(
long nativeCanvas,
float outerLeft,
@@ -300,17 +301,17 @@ public class ShadowNativeBaseCanvas extends ShadowCanvas {
nativePaint);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nDrawPath(long nativeCanvas, long nativePath, long nativePaint) {
BaseCanvasNatives.nDrawPath(nativeCanvas, nativePath, nativePaint);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nDrawRegion(long nativeCanvas, long nativeRegion, long nativePaint) {
BaseCanvasNatives.nDrawRegion(nativeCanvas, nativeRegion, nativePaint);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nDrawNinePatch(
long nativeCanvas,
long nativeBitmap,
@@ -335,7 +336,7 @@ public class ShadowNativeBaseCanvas extends ShadowCanvas {
bitmapDensity);
}
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static void nDrawBitmapMatrix(
long nativeCanvas, long bitmapHandle, long nativeMatrix, long nativePaint) {
BaseCanvasNatives.nDrawBitmapMatrix(nativeCanvas, bitmapHandle, nativeMatrix, nativePaint);
@@ -348,7 +349,7 @@ public class ShadowNativeBaseCanvas extends ShadowCanvas {
nativeCanvas, bitmap.getNativeInstance(), nativeMatrix, nativePaint);
}
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static void nDrawBitmapMesh(
long nativeCanvas,
long bitmapHandle,
@@ -394,7 +395,7 @@ public class ShadowNativeBaseCanvas extends ShadowCanvas {
nativePaint);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nDrawVertices(
long nativeCanvas,
int mode,
@@ -425,7 +426,7 @@ public class ShadowNativeBaseCanvas extends ShadowCanvas {
nativePaint);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static void nDrawGlyphs(
long nativeCanvas,
int[] glyphIds,
@@ -446,7 +447,7 @@ public class ShadowNativeBaseCanvas extends ShadowCanvas {
nativePaint);
}
- @Implementation(minSdk = P)
+ @Implementation(minSdk = P, maxSdk = U.SDK_INT)
protected static void nDrawText(
long nativeCanvas,
char[] text,
@@ -461,7 +462,7 @@ public class ShadowNativeBaseCanvas extends ShadowCanvas {
BaseCanvasNatives.nDrawText(nativeCanvas, text, index, count, x, y, flags, nativePaint);
}
- @Implementation(minSdk = P)
+ @Implementation(minSdk = P, maxSdk = U.SDK_INT)
protected static void nDrawText(
long nativeCanvas,
String text,
@@ -510,7 +511,7 @@ public class ShadowNativeBaseCanvas extends ShadowCanvas {
nativeCanvas, text, start, end, x, y, flags, nativePaint, nativeTypeface);
}
- @Implementation(minSdk = P)
+ @Implementation(minSdk = P, maxSdk = U.SDK_INT)
protected static void nDrawTextRun(
long nativeCanvas,
String text,
@@ -532,7 +533,7 @@ public class ShadowNativeBaseCanvas extends ShadowCanvas {
* The signature of this method is the same from SDK levels O and above, but the last native
* pointer changed from a Typeface pointer to a MeasuredParagraph pointer in P.
*/
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nDrawTextRun(
long nativeCanvas,
char[] text,
@@ -605,7 +606,7 @@ public class ShadowNativeBaseCanvas extends ShadowCanvas {
nativeTypeface);
}
- @Implementation(minSdk = P)
+ @Implementation(minSdk = P, maxSdk = U.SDK_INT)
protected static void nDrawTextOnPath(
long nativeCanvas,
char[] text,
@@ -622,7 +623,7 @@ public class ShadowNativeBaseCanvas extends ShadowCanvas {
nativeCanvas, text, index, count, nativePath, hOffset, vOffset, bidiFlags, nativePaint);
}
- @Implementation(minSdk = P)
+ @Implementation(minSdk = P, maxSdk = U.SDK_INT)
protected static void nDrawTextOnPath(
long nativeCanvas,
String text,
@@ -686,7 +687,7 @@ public class ShadowNativeBaseCanvas extends ShadowCanvas {
BaseCanvasNatives.nPunchHole(renderer, left, top, right, bottom, rx, ry);
}
- @Implementation(minSdk = U.SDK_INT)
+ @Implementation(minSdk = U.SDK_INT, maxSdk = U.SDK_INT)
protected static void nPunchHole(
long renderer,
float left,
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBaseRecordingCanvas.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBaseRecordingCanvas.java
index c0a8b0101..005b51905 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBaseRecordingCanvas.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBaseRecordingCanvas.java
@@ -24,7 +24,7 @@ import org.robolectric.versioning.AndroidVersions.U;
isInAndroidSdk = false)
public class ShadowNativeBaseRecordingCanvas extends ShadowNativeCanvas {
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDrawBitmap(
long nativeCanvas,
long bitmapHandle,
@@ -45,7 +45,7 @@ public class ShadowNativeBaseRecordingCanvas extends ShadowNativeCanvas {
bitmapDensity);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDrawBitmap(
long nativeCanvas,
long bitmapHandle,
@@ -76,7 +76,7 @@ public class ShadowNativeBaseRecordingCanvas extends ShadowNativeCanvas {
bitmapDensity);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDrawBitmap(
long nativeCanvas,
int[] colors,
@@ -92,64 +92,64 @@ public class ShadowNativeBaseRecordingCanvas extends ShadowNativeCanvas {
nativeCanvas, colors, offset, stride, x, y, width, height, hasAlpha, nativePaintOrZero);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDrawColor(long nativeCanvas, int color, int mode) {
BaseRecordingCanvasNatives.nDrawColor(nativeCanvas, color, mode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDrawColor(
long nativeCanvas, long nativeColorSpace, @ColorLong long color, int mode) {
BaseRecordingCanvasNatives.nDrawColor(nativeCanvas, nativeColorSpace, color, mode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDrawPaint(long nativeCanvas, long nativePaint) {
BaseRecordingCanvasNatives.nDrawPaint(nativeCanvas, nativePaint);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDrawPoint(long canvasHandle, float x, float y, long paintHandle) {
BaseRecordingCanvasNatives.nDrawPoint(canvasHandle, x, y, paintHandle);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDrawPoints(
long canvasHandle, float[] pts, int offset, int count, long paintHandle) {
BaseRecordingCanvasNatives.nDrawPoints(canvasHandle, pts, offset, count, paintHandle);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDrawLine(
long nativeCanvas, float startX, float startY, float stopX, float stopY, long nativePaint) {
BaseRecordingCanvasNatives.nDrawLine(nativeCanvas, startX, startY, stopX, stopY, nativePaint);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDrawLines(
long canvasHandle, float[] pts, int offset, int count, long paintHandle) {
BaseRecordingCanvasNatives.nDrawLines(canvasHandle, pts, offset, count, paintHandle);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDrawRect(
long nativeCanvas, float left, float top, float right, float bottom, long nativePaint) {
BaseRecordingCanvasNatives.nDrawRect(nativeCanvas, left, top, right, bottom, nativePaint);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDrawOval(
long nativeCanvas, float left, float top, float right, float bottom, long nativePaint) {
BaseRecordingCanvasNatives.nDrawOval(nativeCanvas, left, top, right, bottom, nativePaint);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDrawCircle(
long nativeCanvas, float cx, float cy, float radius, long nativePaint) {
BaseRecordingCanvasNatives.nDrawCircle(nativeCanvas, cx, cy, radius, nativePaint);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDrawArc(
long nativeCanvas,
float left,
@@ -164,7 +164,7 @@ public class ShadowNativeBaseRecordingCanvas extends ShadowNativeCanvas {
nativeCanvas, left, top, right, bottom, startAngle, sweep, useCenter, nativePaint);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDrawRoundRect(
long nativeCanvas,
float left,
@@ -178,7 +178,7 @@ public class ShadowNativeBaseRecordingCanvas extends ShadowNativeCanvas {
nativeCanvas, left, top, right, bottom, rx, ry, nativePaint);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDrawDoubleRoundRect(
long nativeCanvas,
float outerLeft,
@@ -211,7 +211,7 @@ public class ShadowNativeBaseRecordingCanvas extends ShadowNativeCanvas {
nativePaint);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDrawDoubleRoundRect(
long nativeCanvas,
float outerLeft,
@@ -240,17 +240,17 @@ public class ShadowNativeBaseRecordingCanvas extends ShadowNativeCanvas {
nativePaint);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDrawPath(long nativeCanvas, long nativePath, long nativePaint) {
BaseRecordingCanvasNatives.nDrawPath(nativeCanvas, nativePath, nativePaint);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDrawRegion(long nativeCanvas, long nativeRegion, long nativePaint) {
BaseRecordingCanvasNatives.nDrawRegion(nativeCanvas, nativeRegion, nativePaint);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDrawNinePatch(
long nativeCanvas,
long nativeBitmap,
@@ -275,14 +275,14 @@ public class ShadowNativeBaseRecordingCanvas extends ShadowNativeCanvas {
bitmapDensity);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDrawBitmapMatrix(
long nativeCanvas, long bitmapHandle, long nativeMatrix, long nativePaint) {
BaseRecordingCanvasNatives.nDrawBitmapMatrix(
nativeCanvas, bitmapHandle, nativeMatrix, nativePaint);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDrawBitmapMesh(
long nativeCanvas,
long bitmapHandle,
@@ -305,7 +305,7 @@ public class ShadowNativeBaseRecordingCanvas extends ShadowNativeCanvas {
nativePaint);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDrawVertices(
long nativeCanvas,
int mode,
@@ -336,7 +336,7 @@ public class ShadowNativeBaseRecordingCanvas extends ShadowNativeCanvas {
nativePaint);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static void nDrawGlyphs(
long nativeCanvas,
int[] glyphIds,
@@ -357,7 +357,7 @@ public class ShadowNativeBaseRecordingCanvas extends ShadowNativeCanvas {
nativePaint);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDrawText(
long nativeCanvas,
char[] text,
@@ -371,7 +371,7 @@ public class ShadowNativeBaseRecordingCanvas extends ShadowNativeCanvas {
nativeCanvas, text, index, count, x, y, flags, nativePaint);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDrawText(
long nativeCanvas,
String text,
@@ -414,7 +414,7 @@ public class ShadowNativeBaseRecordingCanvas extends ShadowNativeCanvas {
nativeCanvas, text, start, end, x, y, flags, nativePaint, nativeTypeface);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDrawTextRun(
long nativeCanvas,
String text,
@@ -434,7 +434,7 @@ public class ShadowNativeBaseRecordingCanvas extends ShadowNativeCanvas {
* The signature of this method is the same from SDK levels O and above, but the last native
* pointer changed from a Typeface pointer to a MeasuredParagraph pointer in P.
*/
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nDrawTextRun(
long nativeCanvas,
char[] text,
@@ -503,7 +503,7 @@ public class ShadowNativeBaseRecordingCanvas extends ShadowNativeCanvas {
nativeTypeface);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDrawTextOnPath(
long nativeCanvas,
char[] text,
@@ -518,7 +518,7 @@ public class ShadowNativeBaseRecordingCanvas extends ShadowNativeCanvas {
nativeCanvas, text, index, count, nativePath, hOffset, vOffset, bidiFlags, nativePaint);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDrawTextOnPath(
long nativeCanvas,
String text,
@@ -576,7 +576,7 @@ public class ShadowNativeBaseRecordingCanvas extends ShadowNativeCanvas {
BaseRecordingCanvasNatives.nPunchHole(renderer, left, top, right, bottom, rx, ry);
}
- @Implementation(minSdk = U.SDK_INT)
+ @Implementation(minSdk = U.SDK_INT, maxSdk = U.SDK_INT)
protected static void nPunchHole(
long renderer,
float left,
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmap.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmap.java
index a1ff96f25..140ba87ac 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmap.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmap.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.N;
@@ -28,7 +27,6 @@ import java.util.List;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
-import org.robolectric.annotation.RealObject;
import org.robolectric.annotation.Resetter;
import org.robolectric.nativeruntime.BitmapNatives;
import org.robolectric.nativeruntime.ColorSpaceRgbNatives;
@@ -40,11 +38,13 @@ import org.robolectric.util.reflector.Static;
import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link Bitmap} that is backed by native code */
-@Implements(value = Bitmap.class, looseSignatures = true, minSdk = O, isInAndroidSdk = false)
+@Implements(
+ value = Bitmap.class,
+ looseSignatures = true,
+ minSdk = O,
+ isInAndroidSdk = false,
+ callNativeMethodsByDefault = true)
public class ShadowNativeBitmap extends ShadowBitmap {
-
- @RealObject Bitmap realBitmap;
-
private int createdFromResId;
private static final List<Long> colorSpaceAllocationsP =
@@ -55,7 +55,7 @@ public class ShadowNativeBitmap extends ShadowBitmap {
this.createdFromResId = createdFromResId;
}
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static Bitmap nativeCreate(
int[] colors,
int offset,
@@ -100,75 +100,75 @@ public class ShadowNativeBitmap extends ShadowBitmap {
colors, offset, stride, width, height, nativeConfig, mutable, colorSpacePtr);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static Bitmap nativeCopy(long nativeSrcBitmap, int nativeConfig, boolean isMutable) {
return BitmapNatives.nativeCopy(nativeSrcBitmap, nativeConfig, isMutable);
}
- @Implementation(minSdk = M)
+ @Implementation(minSdk = M, maxSdk = U.SDK_INT)
protected static Bitmap nativeCopyAshmem(long nativeSrcBitmap) {
return BitmapNatives.nativeCopyAshmem(nativeSrcBitmap);
}
- @Implementation(minSdk = N)
+ @Implementation(minSdk = N, maxSdk = U.SDK_INT)
protected static Bitmap nativeCopyAshmemConfig(long nativeSrcBitmap, int nativeConfig) {
return BitmapNatives.nativeCopyAshmemConfig(nativeSrcBitmap, nativeConfig);
}
- @Implementation(minSdk = N)
+ @Implementation(minSdk = N, maxSdk = U.SDK_INT)
protected static long nativeGetNativeFinalizer() {
return BitmapNatives.nativeGetNativeFinalizer();
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static Object nativeRecycle(Object nativeBitmap) {
BitmapNatives.nativeRecycle((long) nativeBitmap);
return true;
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nativeReconfigure(
long nativeBitmap, int width, int height, int config, boolean isPremultiplied) {
BitmapNatives.nativeReconfigure(nativeBitmap, width, height, config, isPremultiplied);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static boolean nativeCompress(
long nativeBitmap, int format, int quality, OutputStream stream, byte[] tempStorage) {
return BitmapNatives.nativeCompress(nativeBitmap, format, quality, stream, tempStorage);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static void nativeErase(long nativeBitmap, int color) {
BitmapNatives.nativeErase(nativeBitmap, color);
}
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static void nativeErase(long nativeBitmap, long colorSpacePtr, long color) {
BitmapNatives.nativeErase(nativeBitmap, colorSpacePtr, color);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static int nativeRowBytes(long nativeBitmap) {
return BitmapNatives.nativeRowBytes(nativeBitmap);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static int nativeConfig(long nativeBitmap) {
return BitmapNatives.nativeConfig(nativeBitmap);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static int nativeGetPixel(long nativeBitmap, int x, int y) {
return BitmapNatives.nativeGetPixel(nativeBitmap, x, y);
}
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static long nativeGetColor(long nativeBitmap, int x, int y) {
return BitmapNatives.nativeGetColor(nativeBitmap, x, y);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static void nativeGetPixels(
long nativeBitmap,
int[] pixels,
@@ -181,12 +181,12 @@ public class ShadowNativeBitmap extends ShadowBitmap {
BitmapNatives.nativeGetPixels(nativeBitmap, pixels, offset, stride, x, y, width, height);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static void nativeSetPixel(long nativeBitmap, int x, int y, int color) {
BitmapNatives.nativeSetPixel(nativeBitmap, x, y, color);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static void nativeSetPixels(
long nativeBitmap,
int[] colors,
@@ -199,85 +199,85 @@ public class ShadowNativeBitmap extends ShadowBitmap {
BitmapNatives.nativeSetPixels(nativeBitmap, colors, offset, stride, x, y, width, height);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nativeCopyPixelsToBuffer(long nativeBitmap, Buffer dst) {
BitmapNatives.nativeCopyPixelsToBuffer(nativeBitmap, dst);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nativeCopyPixelsFromBuffer(long nativeBitmap, Buffer src) {
BitmapNatives.nativeCopyPixelsFromBuffer(nativeBitmap, src);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static int nativeGenerationId(long nativeBitmap) {
return BitmapNatives.nativeGenerationId(nativeBitmap);
}
// returns a new bitmap built from the native bitmap's alpha, and the paint
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static Bitmap nativeExtractAlpha(long nativeBitmap, long nativePaint, int[] offsetXY) {
return BitmapNatives.nativeExtractAlpha(nativeBitmap, nativePaint, offsetXY);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nativeHasAlpha(long nativeBitmap) {
return BitmapNatives.nativeHasAlpha(nativeBitmap);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static boolean nativeIsPremultiplied(long nativeBitmap) {
return BitmapNatives.nativeIsPremultiplied(nativeBitmap);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static void nativeSetPremultiplied(long nativeBitmap, boolean isPremul) {
BitmapNatives.nativeSetPremultiplied(nativeBitmap, isPremul);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static void nativeSetHasAlpha(
long nativeBitmap, boolean hasAlpha, boolean requestPremul) {
BitmapNatives.nativeSetHasAlpha(nativeBitmap, hasAlpha, requestPremul);
}
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nativeHasMipMap(long nativeBitmap) {
return BitmapNatives.nativeHasMipMap(nativeBitmap);
}
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nativeSetHasMipMap(long nativeBitmap, boolean hasMipMap) {
BitmapNatives.nativeSetHasMipMap(nativeBitmap, hasMipMap);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nativeSameAs(long nativeBitmap0, long nativeBitmap1) {
return BitmapNatives.nativeSameAs(nativeBitmap0, nativeBitmap1);
}
- @Implementation(minSdk = N_MR1)
+ @Implementation(minSdk = N_MR1, maxSdk = U.SDK_INT)
protected static void nativePrepareToDraw(long nativeBitmap) {
BitmapNatives.nativePrepareToDraw(nativeBitmap);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static int nativeGetAllocationByteCount(long nativeBitmap) {
return BitmapNatives.nativeGetAllocationByteCount(nativeBitmap);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static Bitmap nativeCopyPreserveInternalConfig(long nativeBitmap) {
return BitmapNatives.nativeCopyPreserveInternalConfig(nativeBitmap);
}
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static Bitmap nativeWrapHardwareBufferBitmap(
HardwareBuffer buffer, long nativeColorSpace) {
return BitmapNatives.nativeWrapHardwareBufferBitmap(buffer, nativeColorSpace);
}
- @Implementation(minSdk = R)
+ @Implementation(minSdk = R, maxSdk = U.SDK_INT)
protected static HardwareBuffer nativeGetHardwareBuffer(long nativeBitmap) {
return BitmapNatives.nativeGetHardwareBuffer(nativeBitmap);
}
@@ -310,41 +310,51 @@ public class ShadowNativeBitmap extends ShadowBitmap {
return true;
}
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static ColorSpace nativeComputeColorSpace(long nativePtr) {
return BitmapNatives.nativeComputeColorSpace(nativePtr);
}
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static void nativeSetColorSpace(long nativePtr, long nativeColorSpace) {
BitmapNatives.nativeSetColorSpace(nativePtr, nativeColorSpace);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nativeIsSRGB(long nativePtr) {
return BitmapNatives.nativeIsSRGB(nativePtr);
}
- @Implementation(minSdk = P)
+ @Implementation(minSdk = P, maxSdk = U.SDK_INT)
protected static boolean nativeIsSRGBLinear(long nativePtr) {
return BitmapNatives.nativeIsSRGBLinear(nativePtr);
}
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static void nativeSetImmutable(long nativePtr) {
BitmapNatives.nativeSetImmutable(nativePtr);
}
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static boolean nativeIsImmutable(long nativePtr) {
return BitmapNatives.nativeIsImmutable(nativePtr);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static boolean nativeIsBackedByAshmem(long nativePtr) {
return BitmapNatives.nativeIsBackedByAshmem(nativePtr);
}
+ /**
+ * This is called by {@link Bitmap#getGainmap} to check if a Gainmap exists for the Bitmap. This
+ * method must be present in Android U and below to avoid an UnsatisfiedLinkError.
+ */
+ @Implementation(minSdk = U.SDK_INT)
+ protected static Object nativeExtractGainmap(Object nativePtr) {
+ // No-op implementation
+ return null;
+ }
+
@ForType(ColorSpace.class)
interface ColorSpaceReflector {
@Accessor("ILLUMINANT_D50_XYZ")
@@ -426,6 +436,11 @@ public class ShadowNativeBitmap extends ShadowBitmap {
return bitmap;
}
+ @Implementation(minSdk = O, maxSdk = P)
+ protected static void nativeCopyColorSpace(long srcBitmap, long dstBitmap) {
+ BitmapNatives.nativeCopyColorSpaceP(srcBitmap, dstBitmap);
+ }
+
@Override
public Bitmap getCreatedFromBitmap() {
throw new UnsupportedOperationException("Legacy ShadowBitmap APIs are not supported");
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmapFactory.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmapFactory.java
index 54213d87c..06d3cb7e8 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmapFactory.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmapFactory.java
@@ -21,13 +21,15 @@ import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowNativeBitmapFactory.Picker;
import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link BitmapFactory} that is backed by native code */
@Implements(
value = BitmapFactory.class,
minSdk = O,
shadowPicker = Picker.class,
- isInAndroidSdk = false)
+ isInAndroidSdk = false,
+ callNativeMethodsByDefault = true)
public class ShadowNativeBitmapFactory {
static {
@@ -46,6 +48,12 @@ public class ShadowNativeBitmapFactory {
return bitmap;
}
+ /**
+ * The real implementation of {@link BitmapFactory#decodeStream(InputStream, Rect, Options)}
+ * checks if the stream is an {@link android.content.res.AssetManager.AssetInputStream} object and
+ * subsequently passes in native asset ids into native code. Until native resources are
+ * implemented, this has to be shadowed.
+ */
@Implementation
protected static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {
reflector(BitmapFactoryOptionsReflector.class).validate(opts);
@@ -55,7 +63,7 @@ public class ShadowNativeBitmapFactory {
return bitmap;
}
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static Bitmap nativeDecodeStream(
InputStream is,
byte[] storage,
@@ -73,7 +81,7 @@ public class ShadowNativeBitmapFactory {
return nativeDecodeStream(is, storage, padding, opts, nativeInBitmap(opts), 0);
}
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static Bitmap nativeDecodeFileDescriptor(
FileDescriptor fd, Rect padding, Options opts, long inBitmapHandle, long colorSpaceHandle) {
return BitmapFactoryNatives.nativeDecodeFileDescriptor(
@@ -86,7 +94,7 @@ public class ShadowNativeBitmapFactory {
return nativeDecodeFileDescriptor(fd, padding, opts, nativeInBitmap(opts), 0);
}
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static Bitmap nativeDecodeAsset(
long nativeAsset, Rect padding, Options opts, long inBitmapHandle, long colorSpaceHandle) {
return BitmapFactoryNatives.nativeDecodeAsset(
@@ -98,7 +106,7 @@ public class ShadowNativeBitmapFactory {
return nativeDecodeAsset(nativeAsset, padding, opts, nativeInBitmap(opts), 0);
}
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static Bitmap nativeDecodeByteArray(
byte[] data,
int offset,
@@ -115,7 +123,7 @@ public class ShadowNativeBitmapFactory {
return nativeDecodeByteArray(data, offset, length, opts, nativeInBitmap(opts), 0);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nativeIsSeekable(FileDescriptor fd) {
return BitmapFactoryNatives.nativeIsSeekable(fd);
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmapShader.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmapShader.java
index 7b04f9190..b44b63258 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmapShader.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmapShader.java
@@ -16,10 +16,13 @@ import org.robolectric.nativeruntime.BitmapShaderNatives;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.shadows.ShadowNativeBitmapShader.Picker;
import org.robolectric.versioning.AndroidVersions.U;
-import org.robolectric.versioning.AndroidVersions.V;
/** Shadow for {@link BitmapShader} that is backed by native code */
-@Implements(value = BitmapShader.class, minSdk = O, shadowPicker = Picker.class)
+@Implements(
+ value = BitmapShader.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ callNativeMethodsByDefault = true)
public class ShadowNativeBitmapShader {
@Implementation(minSdk = O, maxSdk = P)
@@ -62,20 +65,6 @@ public class ShadowNativeBitmapShader {
return nativeCreate(nativeMatrix, bitmapHandle, shaderTileModeX, shaderTileModeY, filter);
}
- @Implementation(minSdk = V.SDK_INT)
- protected static long nativeCreate(
- long nativeMatrix,
- long bitmapHandle,
- int shaderTileModeX,
- int shaderTileModeY,
- /* Ignored */ int maxAniso,
- boolean filter,
- boolean isDirectSampled,
- /* Ignored */ long overrideGainmapHandle) {
- return nativeCreate(
- nativeMatrix, bitmapHandle, shaderTileModeX, shaderTileModeY, filter, isDirectSampled);
- }
-
/** Shadow picker for {@link BitmapShader}. */
public static final class Picker extends GraphicsShadowPicker<Object> {
public Picker() {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBlendModeColorFilter.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBlendModeColorFilter.java
index f4cbf9901..b82a6b0fc 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBlendModeColorFilter.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBlendModeColorFilter.java
@@ -9,12 +9,17 @@ import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.BlendModeColorFilterNatives;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.shadows.ShadowNativeBlendModeColorFilter.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link BlendModeColorFilter} that is backed by native code */
-@Implements(value = BlendModeColorFilter.class, minSdk = O, shadowPicker = Picker.class)
+@Implements(
+ value = BlendModeColorFilter.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ callNativeMethodsByDefault = true)
public class ShadowNativeBlendModeColorFilter {
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static long native_CreateBlendModeFilter(int srcColor, int blendmode) {
DefaultNativeRuntimeLoader.injectAndLoad();
return BlendModeColorFilterNatives.native_CreateBlendModeFilter(srcColor, blendmode);
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBlurMaskFilter.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBlurMaskFilter.java
index 77d4a9da2..0eac16508 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBlurMaskFilter.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBlurMaskFilter.java
@@ -8,12 +8,17 @@ import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.BlurMaskFilterNatives;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.shadows.ShadowNativeBlurMaskFilter.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link BlurMaskFilter} that is backed by native code */
-@Implements(value = BlurMaskFilter.class, minSdk = O, shadowPicker = Picker.class)
+@Implements(
+ value = BlurMaskFilter.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ callNativeMethodsByDefault = true)
public class ShadowNativeBlurMaskFilter {
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nativeConstructor(float radius, int style) {
DefaultNativeRuntimeLoader.injectAndLoad();
return BlurMaskFilterNatives.nativeConstructor(radius, style);
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCanvas.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCanvas.java
index c2dffb8eb..788199ad1 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCanvas.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCanvas.java
@@ -13,27 +13,32 @@ import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.CanvasNatives;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link Canvas} that is backed by native code */
-@Implements(value = Canvas.class, minSdk = O, isInAndroidSdk = false)
+@Implements(
+ value = Canvas.class,
+ minSdk = O,
+ isInAndroidSdk = false,
+ callNativeMethodsByDefault = true)
public class ShadowNativeCanvas extends ShadowNativeBaseCanvas {
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nFreeCaches() {
CanvasNatives.nFreeCaches();
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nFreeTextLayoutCaches() {
CanvasNatives.nFreeTextLayoutCaches();
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nGetNativeFinalizer() {
return CanvasNatives.nGetNativeFinalizer();
}
- @Implementation(minSdk = P)
+ @Implementation(minSdk = P, maxSdk = U.SDK_INT)
protected static void nSetCompatibilityVersion(int apiLevel) {
CanvasNatives.nSetCompatibilityVersion(apiLevel);
}
@@ -43,7 +48,7 @@ public class ShadowNativeCanvas extends ShadowNativeBaseCanvas {
return nInitRaster(bitmap != null ? bitmap.getNativeInstance() : 0);
}
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static long nInitRaster(long bitmapHandle) {
DefaultNativeRuntimeLoader.injectAndLoad();
return CanvasNatives.nInitRaster(bitmapHandle);
@@ -54,37 +59,37 @@ public class ShadowNativeCanvas extends ShadowNativeBaseCanvas {
CanvasNatives.nSetBitmap(canvasHandle, bitmap != null ? bitmap.getNativeInstance() : 0);
}
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static void nSetBitmap(long canvasHandle, long bitmapHandle) {
CanvasNatives.nSetBitmap(canvasHandle, bitmapHandle);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nGetClipBounds(long nativeCanvas, Rect bounds) {
return CanvasNatives.nGetClipBounds(nativeCanvas, bounds);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nIsOpaque(long canvasHandle) {
return CanvasNatives.nIsOpaque(canvasHandle);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static int nGetWidth(long canvasHandle) {
return CanvasNatives.nGetWidth(canvasHandle);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static int nGetHeight(long canvasHandle) {
return CanvasNatives.nGetHeight(canvasHandle);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static int nSave(long canvasHandle, int saveFlags) {
return CanvasNatives.nSave(canvasHandle, saveFlags);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static int nSaveLayer(
long nativeCanvas, float l, float t, float r, float b, long nativePaint) {
return CanvasNatives.nSaveLayer(nativeCanvas, l, t, r, b, nativePaint);
@@ -96,7 +101,7 @@ public class ShadowNativeCanvas extends ShadowNativeBaseCanvas {
return nSaveLayer(nativeCanvas, l, t, r, b, nativePaint);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static int nSaveLayerAlpha(
long nativeCanvas, float l, float t, float r, float b, int alpha) {
return CanvasNatives.nSaveLayerAlpha(nativeCanvas, l, t, r, b, alpha);
@@ -108,88 +113,88 @@ public class ShadowNativeCanvas extends ShadowNativeBaseCanvas {
return nSaveLayerAlpha(nativeCanvas, l, t, r, b, alpha);
}
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static int nSaveUnclippedLayer(long nativeCanvas, int l, int t, int r, int b) {
return CanvasNatives.nSaveUnclippedLayer(nativeCanvas, l, t, r, b);
}
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static void nRestoreUnclippedLayer(long nativeCanvas, int saveCount, long nativePaint) {
CanvasNatives.nRestoreUnclippedLayer(nativeCanvas, saveCount, nativePaint);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nRestore(long canvasHandle) {
return CanvasNatives.nRestore(canvasHandle);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nRestoreToCount(long canvasHandle, int saveCount) {
CanvasNatives.nRestoreToCount(canvasHandle, saveCount);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static int nGetSaveCount(long canvasHandle) {
return CanvasNatives.nGetSaveCount(canvasHandle);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nTranslate(long canvasHandle, float dx, float dy) {
CanvasNatives.nTranslate(canvasHandle, dx, dy);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nScale(long canvasHandle, float sx, float sy) {
CanvasNatives.nScale(canvasHandle, sx, sy);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nRotate(long canvasHandle, float degrees) {
CanvasNatives.nRotate(canvasHandle, degrees);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSkew(long canvasHandle, float sx, float sy) {
CanvasNatives.nSkew(canvasHandle, sx, sy);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nConcat(long nativeCanvas, long nativeMatrix) {
CanvasNatives.nConcat(nativeCanvas, nativeMatrix);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetMatrix(long nativeCanvas, long nativeMatrix) {
CanvasNatives.nSetMatrix(nativeCanvas, nativeMatrix);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nClipRect(
long nativeCanvas, float left, float top, float right, float bottom, int regionOp) {
return CanvasNatives.nClipRect(nativeCanvas, left, top, right, bottom, regionOp);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nClipPath(long nativeCanvas, long nativePath, int regionOp) {
return CanvasNatives.nClipPath(nativeCanvas, nativePath, regionOp);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetDrawFilter(long nativeCanvas, long nativeFilter) {
CanvasNatives.nSetDrawFilter(nativeCanvas, nativeFilter);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nGetMatrix(long nativeCanvas, long nativeMatrix) {
CanvasNatives.nGetMatrix(nativeCanvas, nativeMatrix);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nQuickReject(long nativeCanvas, long nativePath) {
return CanvasNatives.nQuickReject(nativeCanvas, nativePath);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nQuickReject(
long nativeCanvas, float left, float top, float right, float bottom) {
return CanvasNatives.nQuickReject(nativeCanvas, left, top, right, bottom);
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCanvasProperty.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCanvasProperty.java
index 8bb4f1698..62069392e 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCanvasProperty.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCanvasProperty.java
@@ -8,22 +8,24 @@ import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.CanvasPropertyNatives;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.shadows.ShadowNativeCanvasProperty.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link CanvasProperty} that is backed by native code */
@Implements(
value = CanvasProperty.class,
minSdk = O,
shadowPicker = Picker.class,
- isInAndroidSdk = false)
+ isInAndroidSdk = false,
+ callNativeMethodsByDefault = true)
public class ShadowNativeCanvasProperty<T> {
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long nCreateFloat(float initialValue) {
DefaultNativeRuntimeLoader.injectAndLoad();
return CanvasPropertyNatives.nCreateFloat(initialValue);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long nCreatePaint(long initialValuePaintPtr) {
DefaultNativeRuntimeLoader.injectAndLoad();
return CanvasPropertyNatives.nCreatePaint(initialValuePaintPtr);
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColor.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColor.java
index 05e3aa3fa..d7d58dd13 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColor.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColor.java
@@ -8,18 +8,24 @@ import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.ColorNatives;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.shadows.ShadowNativeColor.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link Color} that is backed by native code */
-@Implements(value = Color.class, minSdk = O, shadowPicker = Picker.class, isInAndroidSdk = false)
+@Implements(
+ value = Color.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ isInAndroidSdk = false,
+ callNativeMethodsByDefault = true)
public class ShadowNativeColor {
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nativeRGBToHSV(int red, int greed, int blue, float[] hsv) {
DefaultNativeRuntimeLoader.injectAndLoad();
ColorNatives.nativeRGBToHSV(red, greed, blue, hsv);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static int nativeHSVToColor(int alpha, float[] hsv) {
DefaultNativeRuntimeLoader.injectAndLoad();
return ColorNatives.nativeHSVToColor(alpha, hsv);
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorFilter.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorFilter.java
index ed641a2f0..22ed5a44c 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorFilter.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorFilter.java
@@ -8,12 +8,17 @@ import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.ColorFilterNatives;
import org.robolectric.shadows.ShadowNativeColorFilter.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link ColorFilter} that is backed by native code */
-@Implements(value = ColorFilter.class, minSdk = O, shadowPicker = Picker.class)
+@Implements(
+ value = ColorFilter.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ callNativeMethodsByDefault = true)
public class ShadowNativeColorFilter {
- @Implementation(minSdk = O_MR1)
+ @Implementation(minSdk = O_MR1, maxSdk = U.SDK_INT)
protected static long nativeGetFinalizer() {
return ColorFilterNatives.nativeGetFinalizer();
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorMatrixColorFilter.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorMatrixColorFilter.java
index 307303bea..5ecaeea36 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorMatrixColorFilter.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorMatrixColorFilter.java
@@ -8,12 +8,17 @@ import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.ColorMatrixColorFilterNatives;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.shadows.ShadowNativeColorMatrixColorFilter.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link ColorMatrixColorFilter} that is backed by native code */
-@Implements(value = ColorMatrixColorFilter.class, minSdk = O, shadowPicker = Picker.class)
+@Implements(
+ value = ColorMatrixColorFilter.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ callNativeMethodsByDefault = true)
public class ShadowNativeColorMatrixColorFilter {
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nativeColorMatrixFilter(float[] array) {
DefaultNativeRuntimeLoader.injectAndLoad();
return ColorMatrixColorFilterNatives.nativeColorMatrixFilter(array);
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorSpace.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorSpace.java
new file mode 100644
index 000000000..98d04c5fe
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorSpace.java
@@ -0,0 +1,32 @@
+package org.robolectric.shadows;
+
+import android.graphics.ColorSpace;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowNativeColorSpace.Picker;
+import org.robolectric.versioning.AndroidVersions.V;
+
+/** Shadow for {@link ColorSpace} that defers its static initializer. */
+@Implements(
+ value = ColorSpace.class,
+ minSdk = V.SDK_INT,
+ isInAndroidSdk = false,
+ shadowPicker = Picker.class)
+public class ShadowNativeColorSpace {
+
+ /**
+ * The {@link ColorSpace} static initializer invokes its own native methods in its constructor
+ * when it initializes the named color spaces. This has to be deferred starting in Android V.
+ */
+ @Implementation(minSdk = V.SDK_INT)
+ protected static void __staticInitializer__() {
+ // deferred
+ }
+
+ /** Shadow picker for {@link ColorSpace}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(null, ShadowNativeColorSpace.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorSpaceRgb.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorSpaceRgb.java
index 6bb12eeb2..54c824fe2 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorSpaceRgb.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorSpaceRgb.java
@@ -6,24 +6,29 @@ import static android.os.Build.VERSION_CODES.Q;
import android.graphics.ColorSpace;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.RealObject;
import org.robolectric.nativeruntime.ColorSpaceRgbNatives;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.shadows.ShadowNativeColorSpaceRgb.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link ColorSpace.Rgb} that is backed by native code */
@Implements(
value = ColorSpace.Rgb.class,
minSdk = O,
shadowPicker = Picker.class,
- isInAndroidSdk = false)
-public class ShadowNativeColorSpaceRgb {
+ isInAndroidSdk = false,
+ callNativeMethodsByDefault = true)
+public class ShadowNativeColorSpaceRgb extends ShadowNativeColorSpace {
- @Implementation(minSdk = Q)
+ @RealObject ColorSpace.Rgb colorSpaceRgb;
+
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static long nativeGetNativeFinalizer() {
return ColorSpaceRgbNatives.nativeGetNativeFinalizer();
}
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static long nativeCreate(
float a, float b, float c, float d, float e, float f, float g, float[] xyz) {
DefaultNativeRuntimeLoader.injectAndLoad();
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeComposePathEffect.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeComposePathEffect.java
index 6bf1e3ff4..10fb22bfd 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeComposePathEffect.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeComposePathEffect.java
@@ -8,12 +8,17 @@ import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.ComposePathEffectNatives;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.shadows.ShadowNativeComposePathEffect.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link ComposePathEffect} that is backed by native code */
-@Implements(value = ComposePathEffect.class, minSdk = O, shadowPicker = Picker.class)
+@Implements(
+ value = ComposePathEffect.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ callNativeMethodsByDefault = true)
public class ShadowNativeComposePathEffect {
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nativeCreate(long nativeOuterpe, long nativeInnerpe) {
DefaultNativeRuntimeLoader.injectAndLoad();
return ComposePathEffectNatives.nativeCreate(nativeOuterpe, nativeInnerpe);
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeComposeShader.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeComposeShader.java
index b5e5b4a25..4beb5ed23 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeComposeShader.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeComposeShader.java
@@ -8,12 +8,17 @@ import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.ComposeShaderNatives;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.shadows.ShadowNativeComposeShader.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link ComposeShader} that is backed by native code */
-@Implements(value = ComposeShader.class, minSdk = O, shadowPicker = Picker.class)
+@Implements(
+ value = ComposeShader.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ callNativeMethodsByDefault = true)
public class ShadowNativeComposeShader {
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nativeCreate(
long nativeMatrix, long nativeShaderA, long nativeShaderB, int porterDuffMode) {
DefaultNativeRuntimeLoader.injectAndLoad();
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCornerPathEffect.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCornerPathEffect.java
index 7c306298b..bb771cb37 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCornerPathEffect.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCornerPathEffect.java
@@ -8,12 +8,17 @@ import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.CornerPathEffectNatives;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.shadows.ShadowNativeCornerPathEffect.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link CornerPathEffect} that is backed by native code */
-@Implements(value = CornerPathEffect.class, minSdk = O, shadowPicker = Picker.class)
+@Implements(
+ value = CornerPathEffect.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ callNativeMethodsByDefault = true)
public class ShadowNativeCornerPathEffect {
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nativeCreate(float radius) {
DefaultNativeRuntimeLoader.injectAndLoad();
return CornerPathEffectNatives.nativeCreate(radius);
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCursorWindow.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCursorWindow.java
index 71e0de7ee..f7c242326 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCursorWindow.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCursorWindow.java
@@ -11,12 +11,13 @@ import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.CursorWindowNatives;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link CursorWindow} that is backed by native code */
-@Implements(value = CursorWindow.class, isInAndroidSdk = false)
+@Implements(value = CursorWindow.class, isInAndroidSdk = false, callNativeMethodsByDefault = true)
public class ShadowNativeCursorWindow extends ShadowCursorWindow {
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static Number nativeCreate(String name, int cursorWindowSize) {
DefaultNativeRuntimeLoader.injectAndLoad();
long result = CursorWindowNatives.nativeCreate(name, cursorWindowSize);
@@ -32,7 +33,7 @@ public class ShadowNativeCursorWindow extends ShadowCursorWindow {
PreLPointers.remove(windowPtr);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static void nativeDispose(long windowPtr) {
CursorWindowNatives.nativeDispose(windowPtr);
}
@@ -42,7 +43,7 @@ public class ShadowNativeCursorWindow extends ShadowCursorWindow {
return nativeGetName(PreLPointers.get(windowPtr));
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static String nativeGetName(long windowPtr) {
return CursorWindowNatives.nativeGetName(windowPtr);
}
@@ -52,7 +53,7 @@ public class ShadowNativeCursorWindow extends ShadowCursorWindow {
return nativeGetBlob(PreLPointers.get(windowPtr), row, column);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static byte[] nativeGetBlob(long windowPtr, int row, int column) {
return CursorWindowNatives.nativeGetBlob(windowPtr, row, column);
}
@@ -62,12 +63,12 @@ public class ShadowNativeCursorWindow extends ShadowCursorWindow {
return nativeGetString(PreLPointers.get(windowPtr), row, column);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static String nativeGetString(long windowPtr, int row, int column) {
return CursorWindowNatives.nativeGetString(windowPtr, row, column);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static void nativeCopyStringToBuffer(
long windowPtr, int row, int column, CharArrayBuffer buffer) {
CursorWindowNatives.nativeCopyStringToBuffer(windowPtr, row, column, buffer);
@@ -78,7 +79,7 @@ public class ShadowNativeCursorWindow extends ShadowCursorWindow {
return nativePutBlob(PreLPointers.get(windowPtr), value, row, column);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static boolean nativePutBlob(long windowPtr, byte[] value, int row, int column) {
// Real Android will crash in native code if putBlob is called with a null value.
Preconditions.checkNotNull(value);
@@ -90,7 +91,7 @@ public class ShadowNativeCursorWindow extends ShadowCursorWindow {
return nativePutString(PreLPointers.get(windowPtr), value, row, column);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static boolean nativePutString(long windowPtr, String value, int row, int column) {
// Real Android will crash in native code if putString is called with a null value.
Preconditions.checkNotNull(value);
@@ -102,7 +103,7 @@ public class ShadowNativeCursorWindow extends ShadowCursorWindow {
nativeClear(PreLPointers.get(windowPtr));
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static void nativeClear(long windowPtr) {
CursorWindowNatives.nativeClear(windowPtr);
}
@@ -112,7 +113,7 @@ public class ShadowNativeCursorWindow extends ShadowCursorWindow {
return nativeGetNumRows(PreLPointers.get(windowPtr));
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static int nativeGetNumRows(long windowPtr) {
return CursorWindowNatives.nativeGetNumRows(windowPtr);
}
@@ -122,7 +123,7 @@ public class ShadowNativeCursorWindow extends ShadowCursorWindow {
return nativeSetNumColumns(PreLPointers.get(windowPtr), columnNum);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static boolean nativeSetNumColumns(long windowPtr, int columnNum) {
return CursorWindowNatives.nativeSetNumColumns(windowPtr, columnNum);
}
@@ -132,12 +133,12 @@ public class ShadowNativeCursorWindow extends ShadowCursorWindow {
return nativeAllocRow(PreLPointers.get(windowPtr));
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static boolean nativeAllocRow(long windowPtr) {
return CursorWindowNatives.nativeAllocRow(windowPtr);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static void nativeFreeLastRow(long windowPtr) {
CursorWindowNatives.nativeFreeLastRow(windowPtr);
}
@@ -147,7 +148,7 @@ public class ShadowNativeCursorWindow extends ShadowCursorWindow {
return nativeGetType(PreLPointers.get(windowPtr), row, column);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static int nativeGetType(long windowPtr, int row, int column) {
return CursorWindowNatives.nativeGetType(windowPtr, row, column);
}
@@ -157,7 +158,7 @@ public class ShadowNativeCursorWindow extends ShadowCursorWindow {
return nativeGetLong(PreLPointers.get(windowPtr), row, column);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static long nativeGetLong(long windowPtr, int row, int column) {
return CursorWindowNatives.nativeGetLong(windowPtr, row, column);
}
@@ -167,7 +168,7 @@ public class ShadowNativeCursorWindow extends ShadowCursorWindow {
return nativeGetDouble(PreLPointers.get(windowPtr), row, column);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static double nativeGetDouble(long windowPtr, int row, int column) {
return CursorWindowNatives.nativeGetDouble(windowPtr, row, column);
}
@@ -177,7 +178,7 @@ public class ShadowNativeCursorWindow extends ShadowCursorWindow {
return nativePutLong(PreLPointers.get(windowPtr), value, row, column);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static boolean nativePutLong(long windowPtr, long value, int row, int column) {
return CursorWindowNatives.nativePutLong(windowPtr, value, row, column);
}
@@ -187,7 +188,7 @@ public class ShadowNativeCursorWindow extends ShadowCursorWindow {
return nativePutDouble(PreLPointers.get(windowPtr), value, row, column);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static boolean nativePutDouble(long windowPtr, double value, int row, int column) {
return CursorWindowNatives.nativePutDouble(windowPtr, value, row, column);
}
@@ -197,7 +198,7 @@ public class ShadowNativeCursorWindow extends ShadowCursorWindow {
return nativePutNull(PreLPointers.get(windowPtr), row, column);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static boolean nativePutNull(long windowPtr, int row, int column) {
return CursorWindowNatives.nativePutNull(windowPtr, row, column);
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeDashPathEffect.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeDashPathEffect.java
index d8ad1eb3f..d3e6c4bc8 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeDashPathEffect.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeDashPathEffect.java
@@ -8,12 +8,17 @@ import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.DashPathEffectNatives;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.shadows.ShadowNativeDashPathEffect.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link DashPathEffect} that is backed by native code */
-@Implements(value = DashPathEffect.class, minSdk = O, shadowPicker = Picker.class)
+@Implements(
+ value = DashPathEffect.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ callNativeMethodsByDefault = true)
public class ShadowNativeDashPathEffect {
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nativeCreate(float[] intervals, float phase) {
DefaultNativeRuntimeLoader.injectAndLoad();
return DashPathEffectNatives.nativeCreate(intervals, phase);
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeDiscretePathEffect.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeDiscretePathEffect.java
index b7f5e3ed6..4f1f6f930 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeDiscretePathEffect.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeDiscretePathEffect.java
@@ -8,12 +8,17 @@ import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.nativeruntime.DiscretePathEffectNatives;
import org.robolectric.shadows.ShadowNativeDiscretePathEffect.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link DiscretePathEffect} that is backed by native code */
-@Implements(value = DiscretePathEffect.class, minSdk = O, shadowPicker = Picker.class)
+@Implements(
+ value = DiscretePathEffect.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ callNativeMethodsByDefault = true)
public class ShadowNativeDiscretePathEffect {
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nativeCreate(float length, float deviation) {
DefaultNativeRuntimeLoader.injectAndLoad();
return DiscretePathEffectNatives.nativeCreate(length, deviation);
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeEmbossMaskFilter.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeEmbossMaskFilter.java
index 52ed28e42..ace282210 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeEmbossMaskFilter.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeEmbossMaskFilter.java
@@ -8,12 +8,17 @@ import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.nativeruntime.EmbossMaskFilterNatives;
import org.robolectric.shadows.ShadowNativeEmbossMaskFilter.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link EmbossMaskFilter} that is backed by native code */
-@Implements(value = EmbossMaskFilter.class, minSdk = O, shadowPicker = Picker.class)
+@Implements(
+ value = EmbossMaskFilter.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ callNativeMethodsByDefault = true)
public class ShadowNativeEmbossMaskFilter {
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nativeConstructor(
float[] direction, float ambient, float specular, float blurRadius) {
DefaultNativeRuntimeLoader.injectAndLoad();
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFont.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFont.java
index 2c64de3cd..23f5b7f86 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFont.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFont.java
@@ -29,82 +29,97 @@ import org.robolectric.nativeruntime.FontNatives;
import org.robolectric.shadows.ShadowNativeFont.Picker;
import org.robolectric.util.reflector.Accessor;
import org.robolectric.util.reflector.ForType;
+import org.robolectric.versioning.AndroidVersions.U;
+import org.robolectric.versioning.AndroidVersions.V;
/** Shadow for {@link Font} that is backed by native code */
-@Implements(value = Font.class, minSdk = P, shadowPicker = Picker.class, isInAndroidSdk = false)
+@Implements(
+ value = Font.class,
+ minSdk = P,
+ shadowPicker = Picker.class,
+ isInAndroidSdk = false,
+ callNativeMethodsByDefault = true)
public class ShadowNativeFont {
- @Implementation(minSdk = S)
+
+ /**
+ * {@link android.graphics.fonts.Font} invokes its own native methods in its static initializer.
+ * This must be deferred starting in Android V.
+ */
+ @Implementation(minSdk = V.SDK_INT)
+ protected static void __staticInitializer__() {}
+
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static long nGetMinikinFontPtr(long font) {
return FontNatives.nGetMinikinFontPtr(font);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static long nCloneFont(long font) {
return FontNatives.nCloneFont(font);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static ByteBuffer nNewByteBuffer(long font) {
return FontNatives.nNewByteBuffer(font);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static long nGetBufferAddress(long font) {
return FontNatives.nGetBufferAddress(font);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static int nGetSourceId(long font) {
return FontNatives.nGetSourceId(font);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static long nGetReleaseNativeFont() {
DefaultNativeRuntimeLoader.injectAndLoad();
return FontNatives.nGetReleaseNativeFont();
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static float nGetGlyphBounds(long font, int glyphId, long paint, RectF rect) {
return FontNatives.nGetGlyphBounds(font, glyphId, paint, rect);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static float nGetFontMetrics(long font, long paint, Paint.FontMetrics metrics) {
return FontNatives.nGetFontMetrics(font, paint, metrics);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static String nGetFontPath(long fontPtr) {
return FontNatives.nGetFontPath(fontPtr);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static String nGetLocaleList(long familyPtr) {
return FontNatives.nGetLocaleList(familyPtr);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static int nGetPackedStyle(long fontPtr) {
return FontNatives.nGetPackedStyle(fontPtr);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static int nGetIndex(long fontPtr) {
return FontNatives.nGetIndex(fontPtr);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static int nGetAxisCount(long fontPtr) {
return FontNatives.nGetAxisCount(fontPtr);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static long nGetAxisInfo(long fontPtr, int i) {
return FontNatives.nGetAxisInfo(fontPtr, i);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static long[] nGetAvailableFontSet() {
return FontNatives.nGetAvailableFontSet();
}
@@ -114,7 +129,8 @@ public class ShadowNativeFont {
value = Font.Builder.class,
minSdk = P,
shadowPicker = ShadowNativeFontBuilder.Picker.class,
- isInAndroidSdk = false)
+ isInAndroidSdk = false,
+ callNativeMethodsByDefault = true)
public static class ShadowNativeFontBuilder {
@RealObject Font.Builder realFontBuilder;
@@ -162,18 +178,18 @@ public class ShadowNativeFont {
}
}
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static long nInitBuilder() {
DefaultNativeRuntimeLoader.injectAndLoad();
return FontBuilderNatives.nInitBuilder();
}
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static void nAddAxis(long builderPtr, int tag, float value) {
FontBuilderNatives.nAddAxis(builderPtr, tag, value);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static long nBuild(
long builderPtr,
ByteBuffer buffer,
@@ -206,7 +222,7 @@ public class ShadowNativeFont {
return FontNatives.nGetReleaseNativeFont();
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static long nClone(
long fontPtr, long builderPtr, int weight, boolean italic, int ttcIndex) {
return FontBuilderNatives.nClone(fontPtr, builderPtr, weight, italic, ttcIndex);
@@ -223,6 +239,12 @@ public class ShadowNativeFont {
return assetToBuffer(am, path, isAsset, cookie);
}
+ /** RNG does not support native assets */
+ @Implementation(minSdk = Q, maxSdk = Q)
+ protected static long nGetReleaseNativeAssetFunc() {
+ return 0;
+ }
+
@ForType(Font.Builder.class)
interface FontBuilderReflector {
@Accessor("mBuffer")
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontFamily.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontFamily.java
index 7a78e471f..495e22cb5 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontFamily.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontFamily.java
@@ -14,15 +14,28 @@ import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.nativeruntime.FontFamilyNatives;
import org.robolectric.shadows.ShadowNativeFontFamily.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
+import org.robolectric.versioning.AndroidVersions.V;
/** Shadow for {@link FontFamily} that is backed by native code */
@Implements(
value = FontFamily.class,
minSdk = O,
isInAndroidSdk = false,
- shadowPicker = Picker.class)
+ shadowPicker = Picker.class,
+ callNativeMethodsByDefault = true)
public class ShadowNativeFontFamily {
- @Implementation(minSdk = O)
+
+ /**
+ * {@link android.graphics.FontFamily} invokes its own native methods in its static initializer.
+ * This must be deferred starting in Android V.
+ */
+ @Implementation(minSdk = V.SDK_INT)
+ protected static void __staticInitializer__() {
+ // deferred
+ }
+
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
public static long nInitBuilder(String langs, int variant) {
DefaultNativeRuntimeLoader.injectAndLoad();
return FontFamilyNatives.nInitBuilder(langs, variant);
@@ -33,25 +46,25 @@ public class ShadowNativeFontFamily {
FontFamilyNatives.nAllowUnsupportedFont(builderPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nCreateFamily(long mBuilderPtr) {
return FontFamilyNatives.nCreateFamily(mBuilderPtr);
}
- @Implementation(minSdk = P)
+ @Implementation(minSdk = P, maxSdk = U.SDK_INT)
protected static long nGetBuilderReleaseFunc() {
DefaultNativeRuntimeLoader.injectAndLoad();
return FontFamilyNatives.nGetBuilderReleaseFunc();
}
- @Implementation(minSdk = P)
+ @Implementation(minSdk = P, maxSdk = U.SDK_INT)
protected static long nGetFamilyReleaseFunc() {
return FontFamilyNatives.nGetFamilyReleaseFunc();
}
// By passing -1 to weight argument, the weight value is resolved by OS/2 table in the font.
// By passing -1 to italic argument, the italic value is resolved by OS/2 table in the font.
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nAddFont(
long builderPtr, ByteBuffer font, int ttcIndex, int weight, int isItalic) {
return FontFamilyNatives.nAddFont(builderPtr, font, ttcIndex, weight, isItalic);
@@ -75,18 +88,23 @@ public class ShadowNativeFontFamily {
}
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nAddFontWeightStyle(
long builderPtr, ByteBuffer font, int ttcIndex, int weight, int isItalic) {
return FontFamilyNatives.nAddFontWeightStyle(builderPtr, font, ttcIndex, weight, isItalic);
}
// The added axis values are only valid for the next nAddFont* method call.
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nAddAxisValue(long builderPtr, int tag, float value) {
FontFamilyNatives.nAddAxisValue(builderPtr, tag, value);
}
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static void nAbort(long mBuilderPtr) {
+ // no-op
+ }
+
/** Shadow picker for {@link FontFamily}. */
public static final class Picker extends GraphicsShadowPicker<Object> {
public Picker() {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontFileUtil.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontFileUtil.java
index a38e280aa..23909c3bb 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontFileUtil.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontFileUtil.java
@@ -10,27 +10,29 @@ import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.nativeruntime.FontFileUtilNatives;
import org.robolectric.shadows.ShadowNativeFontFileUtil.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link FontFileUtil} that is backed by native code */
@Implements(
value = FontFileUtil.class,
isInAndroidSdk = false,
minSdk = Q,
- shadowPicker = Picker.class)
+ shadowPicker = Picker.class,
+ callNativeMethodsByDefault = true)
public class ShadowNativeFontFileUtil {
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static long nGetFontRevision(ByteBuffer buffer, int index) {
DefaultNativeRuntimeLoader.injectAndLoad();
return FontFileUtilNatives.nGetFontRevision(buffer, index);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static String nGetFontPostScriptName(ByteBuffer buffer, int index) {
DefaultNativeRuntimeLoader.injectAndLoad();
return FontFileUtilNatives.nGetFontPostScriptName(buffer, index);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static int nIsPostScriptType1Font(ByteBuffer buffer, int index) {
DefaultNativeRuntimeLoader.injectAndLoad();
return FontFileUtilNatives.nIsPostScriptType1Font(buffer, index);
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontsFontFamily.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontsFontFamily.java
index 365b6a9a1..d2174f977 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontsFontFamily.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontsFontFamily.java
@@ -19,24 +19,26 @@ import org.robolectric.versioning.AndroidVersions.V;
value = FontFamily.class,
minSdk = Q,
shadowPicker = Picker.class,
- isInAndroidSdk = false)
+ isInAndroidSdk = false,
+ callNativeMethodsByDefault = true)
public class ShadowNativeFontsFontFamily {
- @Implementation(minSdk = S)
+
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static int nGetFontSize(long family) {
return FontsFontFamilyNatives.nGetFontSize(family);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static long nGetFont(long family, int i) {
return FontsFontFamilyNatives.nGetFont(family, i);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static String nGetLangTags(long family) {
return FontsFontFamilyNatives.nGetLangTags(family);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static int nGetVariant(long family) {
return FontsFontFamilyNatives.nGetVariant(family);
}
@@ -46,15 +48,20 @@ public class ShadowNativeFontsFontFamily {
value = FontFamily.Builder.class,
minSdk = Q,
shadowPicker = ShadowNativeFontFamilyBuilder.Picker.class,
- isInAndroidSdk = false)
+ isInAndroidSdk = false,
+ callNativeMethodsByDefault = true)
public static class ShadowNativeFontFamilyBuilder {
- @Implementation
+
+ @Implementation(minSdk = V.SDK_INT)
+ protected static void __staticInitializer__() {}
+
+ @Implementation(maxSdk = U.SDK_INT)
protected static long nInitBuilder() {
DefaultNativeRuntimeLoader.injectAndLoad();
return FontFamilyBuilderNatives.nInitBuilder();
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nAddFont(long builderPtr, long fontPtr) {
FontFamilyBuilderNatives.nAddFont(builderPtr, fontPtr);
}
@@ -75,18 +82,7 @@ public class ShadowNativeFontsFontFamily {
return FontFamilyBuilderNatives.nBuild(builderPtr, langTags, variant, isCustomFallback);
}
- @Implementation(minSdk = V.SDK_INT)
- protected static long nBuild(
- long builderPtr,
- String langTags,
- int variant,
- boolean isCustomFallback,
- boolean isDefaultFallback,
- int variableFamilyType) {
- return FontFamilyBuilderNatives.nBuild(builderPtr, langTags, variant, isCustomFallback);
- }
-
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long nGetReleaseNativeFamily() {
return FontFamilyBuilderNatives.nGetReleaseNativeFamily();
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeHardwareRenderer.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeHardwareRenderer.java
index 408ea1c27..6c63df06c 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeHardwareRenderer.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeHardwareRenderer.java
@@ -3,6 +3,7 @@ package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.Q;
import static android.os.Build.VERSION_CODES.R;
import static android.os.Build.VERSION_CODES.S;
+import static android.os.Build.VERSION_CODES.S_V2;
import static android.os.Build.VERSION_CODES.TIRAMISU;
import android.graphics.Bitmap;
@@ -26,50 +27,51 @@ import org.robolectric.versioning.AndroidVersions.U;
value = HardwareRenderer.class,
minSdk = Q,
looseSignatures = true,
- shadowPicker = Picker.class)
+ shadowPicker = Picker.class,
+ callNativeMethodsByDefault = true)
public class ShadowNativeHardwareRenderer {
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void disableVsync() {
HardwareRendererNatives.disableVsync();
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void preload() {
HardwareRendererNatives.preload();
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static boolean isWebViewOverlaysEnabled() {
return HardwareRendererNatives.isWebViewOverlaysEnabled();
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void setupShadersDiskCache(String cacheFile, String skiaCacheFile) {
HardwareRendererNatives.setupShadersDiskCache(cacheFile, skiaCacheFile);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nRotateProcessStatsBuffer() {
HardwareRendererNatives.nRotateProcessStatsBuffer();
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nSetProcessStatsBuffer(int fd) {
HardwareRendererNatives.nSetProcessStatsBuffer(fd);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static int nGetRenderThreadTid(long nativeProxy) {
return HardwareRendererNatives.nGetRenderThreadTid(nativeProxy);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long nCreateRootRenderNode() {
DefaultNativeRuntimeLoader.injectAndLoad();
return HardwareRendererNatives.nCreateRootRenderNode();
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static long nCreateProxy(boolean translucent, long rootRenderNode) {
return HardwareRendererNatives.nCreateProxy(translucent, rootRenderNode);
}
@@ -85,194 +87,194 @@ public class ShadowNativeHardwareRenderer {
return nCreateProxy((boolean) translucent, (long) rootRenderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDeleteProxy(long nativeProxy) {
HardwareRendererNatives.nDeleteProxy(nativeProxy);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nLoadSystemProperties(long nativeProxy) {
return HardwareRendererNatives.nLoadSystemProperties(nativeProxy);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nSetName(long nativeProxy, String name) {
HardwareRendererNatives.nSetName(nativeProxy, name);
}
- @Implementation(minSdk = R)
+ @Implementation(minSdk = R, maxSdk = U.SDK_INT)
protected static void nSetSurface(long nativeProxy, Surface window, boolean discardBuffer) {
HardwareRendererNatives.nSetSurface(nativeProxy, window, discardBuffer);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static void nSetSurfaceControl(long nativeProxy, long nativeSurfaceControl) {
HardwareRendererNatives.nSetSurfaceControl(nativeProxy, nativeSurfaceControl);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nPause(long nativeProxy) {
return HardwareRendererNatives.nPause(nativeProxy);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nSetStopped(long nativeProxy, boolean stopped) {
HardwareRendererNatives.nSetStopped(nativeProxy, stopped);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nSetLightGeometry(
long nativeProxy, float lightX, float lightY, float lightZ, float lightRadius) {
HardwareRendererNatives.nSetLightGeometry(nativeProxy, lightX, lightY, lightZ, lightRadius);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nSetLightAlpha(
long nativeProxy, float ambientShadowAlpha, float spotShadowAlpha) {
HardwareRendererNatives.nSetLightAlpha(nativeProxy, ambientShadowAlpha, spotShadowAlpha);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nSetOpaque(long nativeProxy, boolean opaque) {
HardwareRendererNatives.nSetOpaque(nativeProxy, opaque);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static Object nSetColorMode(long nativeProxy, int colorMode) {
HardwareRendererNatives.nSetColorMode(nativeProxy, colorMode);
return null;
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static void nSetSdrWhitePoint(long nativeProxy, float whitePoint) {
HardwareRendererNatives.nSetSdrWhitePoint(nativeProxy, whitePoint);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static void nSetIsHighEndGfx(boolean isHighEndGfx) {
HardwareRendererNatives.nSetIsHighEndGfx(isHighEndGfx);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static int nSyncAndDrawFrame(long nativeProxy, long[] frameInfo, int size) {
return HardwareRendererNatives.nSyncAndDrawFrame(nativeProxy, frameInfo, size);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDestroy(long nativeProxy, long rootRenderNode) {
HardwareRendererNatives.nDestroy(nativeProxy, rootRenderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nRegisterAnimatingRenderNode(long rootRenderNode, long animatingNode) {
HardwareRendererNatives.nRegisterAnimatingRenderNode(rootRenderNode, animatingNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nRegisterVectorDrawableAnimator(long rootRenderNode, long animator) {
HardwareRendererNatives.nRegisterVectorDrawableAnimator(rootRenderNode, animator);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long nCreateTextureLayer(long nativeProxy) {
return HardwareRendererNatives.nCreateTextureLayer(nativeProxy);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nBuildLayer(long nativeProxy, long node) {
HardwareRendererNatives.nBuildLayer(nativeProxy, node);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nCopyLayerInto(long nativeProxy, long layer, long bitmapHandle) {
return HardwareRendererNatives.nCopyLayerInto(nativeProxy, layer, bitmapHandle);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nPushLayerUpdate(long nativeProxy, long layer) {
HardwareRendererNatives.nPushLayerUpdate(nativeProxy, layer);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nCancelLayerUpdate(long nativeProxy, long layer) {
HardwareRendererNatives.nCancelLayerUpdate(nativeProxy, layer);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDetachSurfaceTexture(long nativeProxy, long layer) {
HardwareRendererNatives.nDetachSurfaceTexture(nativeProxy, layer);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDestroyHardwareResources(long nativeProxy) {
HardwareRendererNatives.nDestroyHardwareResources(nativeProxy);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nTrimMemory(int level) {
HardwareRendererNatives.nTrimMemory(level);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nOverrideProperty(String name, String value) {
HardwareRendererNatives.nOverrideProperty(name, value);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nFence(long nativeProxy) {
HardwareRendererNatives.nFence(nativeProxy);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nStopDrawing(long nativeProxy) {
HardwareRendererNatives.nStopDrawing(nativeProxy);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nNotifyFramePending(long nativeProxy) {
HardwareRendererNatives.nNotifyFramePending(nativeProxy);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDumpProfileInfo(long nativeProxy, FileDescriptor fd, int dumpFlags) {
HardwareRendererNatives.nDumpProfileInfo(nativeProxy, fd, dumpFlags);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nAddRenderNode(long nativeProxy, long rootRenderNode, boolean placeFront) {
HardwareRendererNatives.nAddRenderNode(nativeProxy, rootRenderNode, placeFront);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nRemoveRenderNode(long nativeProxy, long rootRenderNode) {
HardwareRendererNatives.nRemoveRenderNode(nativeProxy, rootRenderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDrawRenderNode(long nativeProxy, long rootRenderNode) {
HardwareRendererNatives.nDrawRenderNode(nativeProxy, rootRenderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nSetContentDrawBounds(
long nativeProxy, int left, int top, int right, int bottom) {
HardwareRendererNatives.nSetContentDrawBounds(nativeProxy, left, top, right, bottom);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nSetPictureCaptureCallback(
long nativeProxy, PictureCapturedCallback callback) {
HardwareRendererNatives.nSetPictureCaptureCallback(nativeProxy, callback);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static void nSetASurfaceTransactionCallback(Object nativeProxy, Object callback) {
// Requires looseSignatures because ASurfaceTransactionCallback is S+.
HardwareRendererNatives.nSetASurfaceTransactionCallback(
(long) nativeProxy, (ASurfaceTransactionCallback) callback);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static void nSetPrepareSurfaceControlForWebviewCallback(
Object nativeProxy, Object callback) {
// Need to use loose signatures here as PrepareSurfaceControlForWebviewCallback is S+.
@@ -280,23 +282,23 @@ public class ShadowNativeHardwareRenderer {
(long) nativeProxy, (PrepareSurfaceControlForWebviewCallback) callback);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nSetFrameCallback(long nativeProxy, FrameDrawingCallback callback) {
HardwareRendererNatives.nSetFrameCallback(nativeProxy, callback);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nSetFrameCompleteCallback(
long nativeProxy, FrameCompleteCallback callback) {
HardwareRendererNatives.nSetFrameCompleteCallback(nativeProxy, callback);
}
- @Implementation(minSdk = R)
+ @Implementation(minSdk = R, maxSdk = U.SDK_INT)
protected static void nAddObserver(long nativeProxy, long nativeObserver) {
HardwareRendererNatives.nAddObserver(nativeProxy, nativeObserver);
}
- @Implementation(minSdk = R)
+ @Implementation(minSdk = R, maxSdk = U.SDK_INT)
protected static void nRemoveObserver(long nativeProxy, long nativeObserver) {
HardwareRendererNatives.nRemoveObserver(nativeProxy, nativeObserver);
}
@@ -308,38 +310,43 @@ public class ShadowNativeHardwareRenderer {
surface, srcLeft, srcTop, srcRight, srcBottom, bitmapHandle);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static Bitmap nCreateHardwareBitmap(long renderNode, int width, int height) {
return HardwareRendererNatives.nCreateHardwareBitmap(renderNode, width, height);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nSetHighContrastText(boolean enabled) {
HardwareRendererNatives.nSetHighContrastText(enabled);
}
- @Implementation(minSdk = Q, maxSdk = S)
+ @Implementation(minSdk = Q, maxSdk = S_V2)
protected static void nHackySetRTAnimationsEnabled(boolean enabled) {
DefaultNativeRuntimeLoader.injectAndLoad();
HardwareRendererNatives.nHackySetRTAnimationsEnabled(enabled);
}
- @Implementation
+ @Implementation(minSdk = TIRAMISU, maxSdk = U.SDK_INT)
+ protected static void nSetRtAnimationsEnabled(boolean enabled) {
+ nHackySetRTAnimationsEnabled(enabled);
+ }
+
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nSetDebuggingEnabled(boolean enabled) {
HardwareRendererNatives.nSetDebuggingEnabled(enabled);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nSetIsolatedProcess(boolean enabled) {
HardwareRendererNatives.nSetIsolatedProcess(enabled);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nSetContextPriority(int priority) {
HardwareRendererNatives.nSetContextPriority(priority);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nAllocateBuffers(long nativeProxy) {
HardwareRendererNatives.nAllocateBuffers(nativeProxy);
}
@@ -351,7 +358,7 @@ public class ShadowNativeHardwareRenderer {
// TODO(brettchabot): add support for V nSetForceDark(long, int)
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static void nSetDisplayDensityDpi(int densityDpi) {
HardwareRendererNatives.nSetDisplayDensityDpi(densityDpi);
}
@@ -373,7 +380,7 @@ public class ShadowNativeHardwareRenderer {
presentationDeadlineNanos);
}
- @Implementation(minSdk = U.SDK_INT)
+ @Implementation(minSdk = U.SDK_INT, maxSdk = U.SDK_INT)
protected static void nInitDisplayInfo(
int width,
int height,
@@ -392,6 +399,11 @@ public class ShadowNativeHardwareRenderer {
presentationDeadlineNanos);
}
+ @Implementation(maxSdk = R)
+ protected static void nSetWideGamut(long nativeProxy, boolean wideGamut) {
+ // No-op
+ }
+
/** Shadow picker for {@link HardwareRenderer}. */
public static final class Picker extends GraphicsShadowPicker<Object> {
public Picker() {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeHardwareRendererObserver.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeHardwareRendererObserver.java
index 97b05eb18..8f57d456f 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeHardwareRendererObserver.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeHardwareRendererObserver.java
@@ -13,19 +13,21 @@ import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.nativeruntime.HardwareRendererObserverNatives;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowNativeHardwareRendererObserver.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link HardwareRendererObserver} that is backed by native code */
@Implements(
value = HardwareRendererObserver.class,
minSdk = R,
shadowPicker = Picker.class,
- isInAndroidSdk = false)
+ isInAndroidSdk = false,
+ callNativeMethodsByDefault = true)
public class ShadowNativeHardwareRendererObserver {
public HardwareRendererObserverNatives hardwareRendererObserverNatives =
new HardwareRendererObserverNatives();
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static int nGetNextBuffer(long nativePtr, long[] data) {
return HardwareRendererObserverNatives.nGetNextBuffer(nativePtr, data);
}
@@ -41,7 +43,7 @@ public class ShadowNativeHardwareRendererObserver {
return hardwareRendererObserverNatives.nCreateObserver(waitForPresentTime);
}
- @Implementation(minSdk = TIRAMISU)
+ @Implementation(minSdk = TIRAMISU, maxSdk = U.SDK_INT)
protected static long nCreateObserver(
WeakReference<HardwareRendererObserver> observer, boolean waitForPresentTime) {
HardwareRendererObserver hardwareRendererObserver = observer.get();
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeImageDecoder.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeImageDecoder.java
index 4913c9f24..886f5755d 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeImageDecoder.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeImageDecoder.java
@@ -4,6 +4,7 @@ import static android.os.Build.VERSION_CODES.P;
import static android.os.Build.VERSION_CODES.Q;
import static android.os.Build.VERSION_CODES.R;
import static android.os.Build.VERSION_CODES.S;
+import static org.robolectric.util.reflector.Reflector.reflector;
import android.content.res.AssetManager.AssetInputStream;
import android.graphics.Bitmap;
@@ -18,14 +19,22 @@ import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
+import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.nativeruntime.ImageDecoderNatives;
import org.robolectric.shadows.ShadowNativeImageDecoder.Picker;
+import org.robolectric.util.reflector.ForType;
+import org.robolectric.util.reflector.Static;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link android.graphics.ImageDecoder} that is backed by native code */
-@Implements(value = ImageDecoder.class, minSdk = P, shadowPicker = Picker.class)
+@Implements(
+ value = ImageDecoder.class,
+ minSdk = P,
+ shadowPicker = Picker.class,
+ callNativeMethodsByDefault = true)
public class ShadowNativeImageDecoder {
static {
@@ -52,7 +61,12 @@ public class ShadowNativeImageDecoder {
if (ais.read() != -1) {
throw new IOException("Unable to access full contents of asset");
}
- return nCreate(buffer, 0, bytesRead, preferAnimation, source);
+ if (RuntimeEnvironment.getApiLevel() > U.SDK_INT) {
+ return reflector(ImageDecoderReflector.class)
+ .nCreate(buffer, 0, bytesRead, preferAnimation, source);
+ } else {
+ return nCreate(buffer, 0, bytesRead, preferAnimation, source);
+ }
}
@Implementation(minSdk = P, maxSdk = Q)
@@ -72,7 +86,7 @@ public class ShadowNativeImageDecoder {
return nCreate(buffer, position, limit, false, src);
}
- @Implementation(minSdk = R)
+ @Implementation(minSdk = R, maxSdk = U.SDK_INT)
protected static ImageDecoder nCreate(
ByteBuffer buffer, int position, int limit, boolean preferAnimation, Source src)
throws IOException {
@@ -85,7 +99,7 @@ public class ShadowNativeImageDecoder {
return nCreate(data, offset, length, false, src);
}
- @Implementation(minSdk = R)
+ @Implementation(minSdk = R, maxSdk = U.SDK_INT)
protected static ImageDecoder nCreate(
byte[] data, int offset, int length, boolean preferAnimation, Source src) throws IOException {
return ImageDecoderNatives.nCreate(data, offset, length, preferAnimation, src);
@@ -97,7 +111,7 @@ public class ShadowNativeImageDecoder {
return nCreate(is, storage, false, src);
}
- @Implementation(minSdk = R)
+ @Implementation(minSdk = R, maxSdk = U.SDK_INT)
protected static ImageDecoder nCreate(
InputStream is, byte[] storage, boolean preferAnimation, Source src) throws IOException {
return ImageDecoderNatives.nCreate(is, storage, preferAnimation, src);
@@ -108,7 +122,7 @@ public class ShadowNativeImageDecoder {
throw new UnsupportedEncodingException();
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static ImageDecoder nCreate(
FileDescriptor fd, long length, boolean preferAnimation, Source src) throws IOException {
return ImageDecoderNatives.nCreate(fd, length, preferAnimation, src);
@@ -145,7 +159,7 @@ public class ShadowNativeImageDecoder {
/* extended = */ false);
}
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static Bitmap nDecodeBitmap(
long nativePtr,
ImageDecoder decoder,
@@ -177,31 +191,38 @@ public class ShadowNativeImageDecoder {
extended);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static Size nGetSampledSize(long nativePtr, int sampleSize) {
return ImageDecoderNatives.nGetSampledSize(nativePtr, sampleSize);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nGetPadding(long nativePtr, Rect outRect) {
ImageDecoderNatives.nGetPadding(nativePtr, outRect);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nClose(long nativePtr) {
ImageDecoderNatives.nClose(nativePtr);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static String nGetMimeType(long nativePtr) {
return ImageDecoderNatives.nGetMimeType(nativePtr);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static ColorSpace nGetColorSpace(long nativePtr) {
return ImageDecoderNatives.nGetColorSpace(nativePtr);
}
+ @ForType(ImageDecoder.class)
+ interface ImageDecoderReflector {
+ @Static
+ ImageDecoder nCreate(
+ ByteBuffer buffer, int position, int limit, boolean preferAnimation, Source src);
+ }
+
/** Shadow picker for {@link ImageDecoder}. */
public static final class Picker extends GraphicsShadowPicker<Object> {
public Picker() {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeImageReader.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeImageReader.java
index 72d5d0cf6..633f1062c 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeImageReader.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeImageReader.java
@@ -17,6 +17,7 @@ import org.robolectric.util.reflector.Accessor;
import org.robolectric.util.reflector.ForType;
import org.robolectric.versioning.AndroidVersions.T;
import org.robolectric.versioning.AndroidVersions.U;
+import org.robolectric.versioning.AndroidVersions.V;
/** Shadow for {@link ImageReader} that is backed by native code */
@Implements(
@@ -24,9 +25,19 @@ import org.robolectric.versioning.AndroidVersions.U;
minSdk = Q,
looseSignatures = true,
isInAndroidSdk = false,
- shadowPicker = Picker.class)
+ shadowPicker = Picker.class,
+ callNativeMethodsByDefault = true)
public class ShadowNativeImageReader {
+ /**
+ * The {@link ImageReader} static initializer invokes its own native methods in static
+ * initializer. This has to be deferred starting in Android V.
+ */
+ @Implementation(minSdk = V.SDK_INT)
+ protected static void __staticInitializer__() {
+ // deferred
+ }
+
@ReflectorObject private ImageReaderReflector imageReaderReflector;
private final ImageReaderNatives natives = new ImageReaderNatives();
@@ -37,7 +48,7 @@ public class ShadowNativeImageReader {
imageReaderReflector.setMemberNativeContext(natives.mNativeContext);
}
- @Implementation(minSdk = T.SDK_INT)
+ @Implementation(minSdk = T.SDK_INT, maxSdk = U.SDK_INT)
protected synchronized void nativeInit(
Object weakSelf,
int w,
@@ -56,17 +67,17 @@ public class ShadowNativeImageReader {
imageReaderReflector.setMemberNativeContext(natives.mNativeContext);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected void nativeClose() {
natives.nativeClose();
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected void nativeReleaseImage(Image i) {
natives.nativeReleaseImage(i);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected Surface nativeGetSurface() {
return natives.nativeGetSurface();
}
@@ -76,7 +87,7 @@ public class ShadowNativeImageReader {
return natives.nativeDetachImage(i);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected void nativeDiscardFreeBuffers() {
natives.nativeDiscardFreeBuffers();
}
@@ -94,14 +105,14 @@ public class ShadowNativeImageReader {
return natives.nativeImageSetup(i);
}
- @Implementation(minSdk = U.SDK_INT)
+ @Implementation(minSdk = U.SDK_INT, maxSdk = U.SDK_INT)
protected Object nativeImageSetup(Object i) {
// Note: reverted to Q-S API
return natives.nativeImageSetup((Image) i);
}
/** We use a class initializer to allow the native code to cache some field offsets. */
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nativeClassInit() {
DefaultNativeRuntimeLoader.injectAndLoad();
ImageReaderNatives.nativeClassInit();
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeImageReaderSurfaceImage.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeImageReaderSurfaceImage.java
index ec0e83188..d6665c18f 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeImageReaderSurfaceImage.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeImageReaderSurfaceImage.java
@@ -4,11 +4,13 @@ import static android.os.Build.VERSION_CODES.Q;
import static android.os.Build.VERSION_CODES.R;
import static android.os.Build.VERSION_CODES.S;
+import android.hardware.HardwareBuffer;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.nativeruntime.ImageReaderSurfaceImageNatives;
import org.robolectric.shadows.ShadowImageReader.ShadowSurfaceImage;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@code ImageReader.SurfaceImage} that is backed by native code. */
@Implements(
@@ -16,7 +18,8 @@ import org.robolectric.shadows.ShadowImageReader.ShadowSurfaceImage;
minSdk = Q,
looseSignatures = true,
isInAndroidSdk = false,
- shadowPicker = ShadowNativeImageReaderSurfaceImage.Picker.class)
+ shadowPicker = ShadowNativeImageReaderSurfaceImage.Picker.class,
+ callNativeMethodsByDefault = true)
public class ShadowNativeImageReaderSurfaceImage {
@RealObject private Object realSurfaceImage;
@@ -28,29 +31,34 @@ public class ShadowNativeImageReaderSurfaceImage {
realSurfaceImage, (int) numPlanes, (int) readerFormat, /* readerUsage= */ 0);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected synchronized /*SurfacePlane[]*/ Object nativeCreatePlanes(
/*int*/ Object numPlanes, /*int*/ Object readerFormat, /*long*/ Object readerUsage) {
return ImageReaderSurfaceImageNatives.nativeSurfaceImageCreatePlanes(
realSurfaceImage, (int) numPlanes, (int) readerFormat, (long) readerUsage);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected synchronized int nativeGetWidth() {
return ImageReaderSurfaceImageNatives.nativeSurfaceImageGetWidth(realSurfaceImage);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected synchronized int nativeGetHeight() {
return ImageReaderSurfaceImageNatives.nativeSurfaceImageGetHeight(realSurfaceImage);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected synchronized int nativeGetFormat(int readerFormat) {
return ImageReaderSurfaceImageNatives.nativeSurfaceImageGetFormat(
realSurfaceImage, readerFormat);
}
+ @Implementation
+ protected synchronized HardwareBuffer nativeGetHardwareBuffer() {
+ return null; // TODO(hoisie): add an implementation
+ }
+
/** Shadow picker for {@code ImageReader.SurfaceImage}. */
public static final class Picker extends GraphicsShadowPicker<Object> {
public Picker() {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeInterpolator.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeInterpolator.java
index 21a292c89..3d775ba75 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeInterpolator.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeInterpolator.java
@@ -8,40 +8,45 @@ import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.nativeruntime.InterpolatorNatives;
import org.robolectric.shadows.ShadowNativeInterpolator.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link Interpolator} that is backed by native code */
-@Implements(value = Interpolator.class, minSdk = O, shadowPicker = Picker.class)
+@Implements(
+ value = Interpolator.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ callNativeMethodsByDefault = true)
public class ShadowNativeInterpolator {
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long nativeConstructor(int valueCount, int frameCount) {
DefaultNativeRuntimeLoader.injectAndLoad();
return InterpolatorNatives.nativeConstructor(valueCount, frameCount);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nativeDestructor(long nativeInstance) {
InterpolatorNatives.nativeDestructor(nativeInstance);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nativeReset(long nativeInstance, int valueCount, int frameCount) {
InterpolatorNatives.nativeReset(nativeInstance, valueCount, frameCount);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nativeSetKeyFrame(
long nativeInstance, int index, int msec, float[] values, float[] blend) {
InterpolatorNatives.nativeSetKeyFrame(nativeInstance, index, msec, values, blend);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nativeSetRepeatMirror(
long nativeInstance, float repeatCount, boolean mirror) {
InterpolatorNatives.nativeSetRepeatMirror(nativeInstance, repeatCount, mirror);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static int nativeTimeToValues(long nativeInstance, int msec, float[] values) {
return InterpolatorNatives.nativeTimeToValues(nativeInstance, msec, values);
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLightingColorFilter.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLightingColorFilter.java
index 88e4ea5c3..9ddb07337 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLightingColorFilter.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLightingColorFilter.java
@@ -8,12 +8,17 @@ import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.nativeruntime.LightingColorFilterNatives;
import org.robolectric.shadows.ShadowNativeLightingColorFilter.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link LightingColorFilter} that is backed by native code */
-@Implements(value = LightingColorFilter.class, minSdk = O, shadowPicker = Picker.class)
+@Implements(
+ value = LightingColorFilter.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ callNativeMethodsByDefault = true)
public class ShadowNativeLightingColorFilter {
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long native_CreateLightingFilter(int mul, int add) {
DefaultNativeRuntimeLoader.injectAndLoad();
return LightingColorFilterNatives.native_CreateLightingFilter(mul, add);
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLineBreaker.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLineBreaker.java
index 7133f83a7..ce0b92f7e 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLineBreaker.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLineBreaker.java
@@ -14,32 +14,30 @@ import org.robolectric.versioning.AndroidVersions.U;
import org.robolectric.versioning.AndroidVersions.V;
/** Shadow for {@link LineBreaker} that is backed by native code */
-@Implements(value = LineBreaker.class, minSdk = Q, shadowPicker = Picker.class)
+@Implements(
+ value = LineBreaker.class,
+ minSdk = Q,
+ shadowPicker = Picker.class,
+ callNativeMethodsByDefault = true)
public class ShadowNativeLineBreaker {
+
+ @Implementation(minSdk = V.SDK_INT)
+ protected static void __staticInitializer__() {}
+
@Implementation(maxSdk = U.SDK_INT)
protected static long nInit(
int breakStrategy, int hyphenationFrequency, boolean isJustified, int[] indents) {
return LineBreakerNatives.nInit(breakStrategy, hyphenationFrequency, isJustified, indents);
}
- @Implementation(minSdk = V.SDK_INT)
- protected static long nInit(
- int breakStrategy,
- int hyphenationFrequency,
- boolean isJustified,
- int[] indents,
- boolean useBoundsForWidth) {
- return nInit(breakStrategy, hyphenationFrequency, isJustified, indents);
- }
-
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long nGetReleaseFunc() {
// Called first by the static initializer.
DefaultNativeRuntimeLoader.injectAndLoad();
return LineBreakerNatives.nGetReleaseFunc();
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long nComputeLineBreaks(
long nativePtr,
char[] text,
@@ -65,37 +63,37 @@ public class ShadowNativeLineBreaker {
}
// Result accessors
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static int nGetLineCount(long ptr) {
return LineBreakerNatives.nGetLineCount(ptr);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static int nGetLineBreakOffset(long ptr, int idx) {
return LineBreakerNatives.nGetLineBreakOffset(ptr, idx);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static float nGetLineWidth(long ptr, int idx) {
return LineBreakerNatives.nGetLineWidth(ptr, idx);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static float nGetLineAscent(long ptr, int idx) {
return LineBreakerNatives.nGetLineAscent(ptr, idx);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static float nGetLineDescent(long ptr, int idx) {
return LineBreakerNatives.nGetLineDescent(ptr, idx);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static int nGetLineFlag(long ptr, int idx) {
return LineBreakerNatives.nGetLineFlag(ptr, idx);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long nGetReleaseResultFunc() {
return LineBreakerNatives.nGetReleaseResultFunc();
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLinearGradient.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLinearGradient.java
index c3458fcf5..d0e263959 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLinearGradient.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLinearGradient.java
@@ -7,14 +7,23 @@ import static android.os.Build.VERSION_CODES.Q;
import android.graphics.LinearGradient;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.RealObject;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.nativeruntime.LinearGradientNatives;
import org.robolectric.shadows.ShadowNativeLinearGradient.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link LinearGradient} that is backed by native code */
-@Implements(value = LinearGradient.class, minSdk = O, shadowPicker = Picker.class)
+@Implements(
+ value = LinearGradient.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ callNativeMethodsByDefault = true)
public class ShadowNativeLinearGradient {
- @Implementation(minSdk = Q)
+
+ @RealObject LinearGradient realLinearGradient;
+
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected long nativeCreate(
long matrix,
float x0,
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMaskFilter.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMaskFilter.java
index 97b18ac52..cbc1b7009 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMaskFilter.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMaskFilter.java
@@ -7,12 +7,17 @@ import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.MaskFilterNatives;
import org.robolectric.shadows.ShadowNativeMaskFilter.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link MaskFilter} that is backed by native code */
-@Implements(value = MaskFilter.class, minSdk = O, shadowPicker = Picker.class)
+@Implements(
+ value = MaskFilter.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ callNativeMethodsByDefault = true)
public class ShadowNativeMaskFilter {
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nativeDestructor(long nativeFilter) {
MaskFilterNatives.nativeDestructor(nativeFilter);
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMatrix.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMatrix.java
index e840968db..61c4e7db5 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMatrix.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMatrix.java
@@ -12,39 +12,54 @@ import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.nativeruntime.MatrixNatives;
+import org.robolectric.versioning.AndroidVersions.U;
+import org.robolectric.versioning.AndroidVersions.V;
/** Shadow for {@link Matrix} that is backed by native code */
-@Implements(value = Matrix.class, minSdk = O, isInAndroidSdk = false)
+@Implements(
+ value = Matrix.class,
+ minSdk = O,
+ isInAndroidSdk = false,
+ callNativeMethodsByDefault = true)
public class ShadowNativeMatrix extends ShadowMatrix {
+ /**
+ * The {@link Matrix} static initializer invokes its own native methods. This has to be deferred
+ * starting in Android V.
+ */
+ @Implementation(minSdk = V.SDK_INT)
+ protected static void __staticInitializer__() {
+ // deferred
+ }
+
@Implementation(minSdk = LOLLIPOP, maxSdk = N_MR1)
protected static long native_create(long nSrcOrZero) {
return nCreate(nSrcOrZero);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nCreate(long nSrcOrZero) {
DefaultNativeRuntimeLoader.injectAndLoad();
return MatrixNatives.nCreate(nSrcOrZero);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nGetNativeFinalizer() {
return MatrixNatives.nGetNativeFinalizer();
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nSetRectToRect(long nObject, RectF src, RectF dst, int stf) {
return MatrixNatives.nSetRectToRect(nObject, src, dst, stf);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nSetPolyToPoly(
long nObject, float[] src, int srcIndex, float[] dst, int dstIndex, int pointCount) {
return MatrixNatives.nSetPolyToPoly(nObject, src, srcIndex, dst, dstIndex, pointCount);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nMapPoints(
long nObject,
float[] dst,
@@ -56,188 +71,188 @@ public class ShadowNativeMatrix extends ShadowMatrix {
MatrixNatives.nMapPoints(nObject, dst, dstIndex, src, srcIndex, ptCount, isPts);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nMapRect(long nObject, RectF dst, RectF src) {
return MatrixNatives.nMapRect(nObject, dst, src);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nGetValues(long nObject, float[] values) {
MatrixNatives.nGetValues(nObject, values);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetValues(long nObject, float[] values) {
MatrixNatives.nSetValues(nObject, values);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nIsIdentity(long nObject) {
return MatrixNatives.nIsIdentity(nObject);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nIsAffine(long nObject) {
return MatrixNatives.nIsAffine(nObject);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nRectStaysRect(long nObject) {
return MatrixNatives.nRectStaysRect(nObject);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nReset(long nObject) {
MatrixNatives.nReset(nObject);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSet(long nObject, long nOther) {
MatrixNatives.nSet(nObject, nOther);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetTranslate(long nObject, float dx, float dy) {
MatrixNatives.nSetTranslate(nObject, dx, dy);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetScale(long nObject, float sx, float sy, float px, float py) {
MatrixNatives.nSetScale(nObject, sx, sy, px, py);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetScale(long nObject, float sx, float sy) {
MatrixNatives.nSetScale(nObject, sx, sy);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetRotate(long nObject, float degrees, float px, float py) {
MatrixNatives.nSetRotate(nObject, degrees, px, py);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetRotate(long nObject, float degrees) {
MatrixNatives.nSetRotate(nObject, degrees);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetSinCos(
long nObject, float sinValue, float cosValue, float px, float py) {
MatrixNatives.nSetSinCos(nObject, sinValue, cosValue, px, py);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetSinCos(long nObject, float sinValue, float cosValue) {
MatrixNatives.nSetSinCos(nObject, sinValue, cosValue);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetSkew(long nObject, float kx, float ky, float px, float py) {
MatrixNatives.nSetSkew(nObject, kx, ky, px, py);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetSkew(long nObject, float kx, float ky) {
MatrixNatives.nSetSkew(nObject, kx, ky);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetConcat(long nObject, long nA, long nB) {
MatrixNatives.nSetConcat(nObject, nA, nB);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nPreTranslate(long nObject, float dx, float dy) {
MatrixNatives.nPreTranslate(nObject, dx, dy);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nPreScale(long nObject, float sx, float sy, float px, float py) {
MatrixNatives.nPreScale(nObject, sx, sy, px, py);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nPreScale(long nObject, float sx, float sy) {
MatrixNatives.nPreScale(nObject, sx, sy);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nPreRotate(long nObject, float degrees, float px, float py) {
MatrixNatives.nPreRotate(nObject, degrees, px, py);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nPreRotate(long nObject, float degrees) {
MatrixNatives.nPreRotate(nObject, degrees);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nPreSkew(long nObject, float kx, float ky, float px, float py) {
MatrixNatives.nPreSkew(nObject, kx, ky, px, py);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nPreSkew(long nObject, float kx, float ky) {
MatrixNatives.nPreSkew(nObject, kx, ky);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nPreConcat(long nObject, long nOtherMatrix) {
MatrixNatives.nPreConcat(nObject, nOtherMatrix);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nPostTranslate(long nObject, float dx, float dy) {
MatrixNatives.nPostTranslate(nObject, dx, dy);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nPostScale(long nObject, float sx, float sy, float px, float py) {
MatrixNatives.nPostScale(nObject, sx, sy, px, py);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nPostScale(long nObject, float sx, float sy) {
MatrixNatives.nPostScale(nObject, sx, sy);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nPostRotate(long nObject, float degrees, float px, float py) {
MatrixNatives.nPostRotate(nObject, degrees, px, py);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nPostRotate(long nObject, float degrees) {
MatrixNatives.nPostRotate(nObject, degrees);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nPostSkew(long nObject, float kx, float ky, float px, float py) {
MatrixNatives.nPostSkew(nObject, kx, ky, px, py);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nPostSkew(long nObject, float kx, float ky) {
MatrixNatives.nPostSkew(nObject, kx, ky);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nPostConcat(long nObject, long nOtherMatrix) {
MatrixNatives.nPostConcat(nObject, nOtherMatrix);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nInvert(long nObject, long nInverse) {
return MatrixNatives.nInvert(nObject, nInverse);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static float nMapRadius(long nObject, float radius) {
return MatrixNatives.nMapRadius(nObject, radius);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nEquals(long nA, long nB) {
return MatrixNatives.nEquals(nA, nB);
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMeasuredText.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMeasuredText.java
index 321db9ed8..d22cd3834 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMeasuredText.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMeasuredText.java
@@ -18,31 +18,35 @@ import org.robolectric.versioning.AndroidVersions.U;
import org.robolectric.versioning.AndroidVersions.V;
/** Shadow for {@link MeasuredText} that is backed by native code */
-@Implements(value = MeasuredText.class, minSdk = Q, shadowPicker = Picker.class)
+@Implements(
+ value = MeasuredText.class,
+ minSdk = Q,
+ shadowPicker = Picker.class,
+ callNativeMethodsByDefault = true)
public class ShadowNativeMeasuredText {
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static float nGetWidth(
/* Non Zero */ long nativePtr, @IntRange(from = 0) int start, @IntRange(from = 0) int end) {
return MeasuredTextNatives.nGetWidth(nativePtr, start, end);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static /* Non Zero */ long nGetReleaseFunc() {
DefaultNativeRuntimeLoader.injectAndLoad();
return MeasuredTextNatives.nGetReleaseFunc();
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static int nGetMemoryUsage(/* Non Zero */ long nativePtr) {
return MeasuredTextNatives.nGetMemoryUsage(nativePtr);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nGetBounds(long nativePtr, char[] buf, int start, int end, Rect rect) {
MeasuredTextNatives.nGetBounds(nativePtr, buf, start, end, rect);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static float nGetCharWidthAt(long nativePtr, int offset) {
return MeasuredTextNatives.nGetCharWidthAt(nativePtr, offset);
}
@@ -51,9 +55,20 @@ public class ShadowNativeMeasuredText {
@Implements(
value = MeasuredText.Builder.class,
minSdk = Q,
- shadowPicker = ShadowNativeMeasuredTextBuilder.Picker.class)
+ shadowPicker = ShadowNativeMeasuredTextBuilder.Picker.class,
+ callNativeMethodsByDefault = true)
public static class ShadowNativeMeasuredTextBuilder {
- @Implementation
+
+ /**
+ * The {@link MeasuredText.Builder} static initializer invokes its own native methods. This has
+ * to be deferred starting in Android V.
+ */
+ @Implementation(minSdk = V.SDK_INT)
+ protected static void __staticInitializer__() {
+ // deferred
+ }
+
+ @Implementation(maxSdk = U.SDK_INT)
protected static /* Non Zero */ long nInitBuilder() {
return MeasuredTextBuilderNatives.nInitBuilder();
}
@@ -80,20 +95,7 @@ public class ShadowNativeMeasuredText {
MeasuredTextBuilderNatives.nAddStyleRun(nativeBuilderPtr, paintPtr, start, end, isRtl);
}
- @Implementation(minSdk = V.SDK_INT)
- protected static void nAddStyleRun(
- /* Non Zero */ long nativeBuilderPtr,
- /* Non Zero */ long paintPtr,
- int lineBreakStyle,
- int lineBreakWordStyle,
- /* Ignored */ boolean hyphenation,
- int start,
- int end,
- boolean isRtl) {
- MeasuredTextBuilderNatives.nAddStyleRun(nativeBuilderPtr, paintPtr, start, end, isRtl);
- }
-
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nAddReplacementRun(
/* Non Zero */ long nativeBuilderPtr,
/* Non Zero */ long paintPtr,
@@ -126,21 +128,7 @@ public class ShadowNativeMeasuredText {
nativeBuilderPtr, hintMtPtr, text, computeHyphenation, computeLayout);
}
- @Implementation(minSdk = V.SDK_INT)
- protected static long nBuildMeasuredText(
- /* Non Zero */ long nativeBuilderPtr,
- long hintMtPtr,
- char[] text,
- boolean computeHyphenation,
- boolean computeLayout,
- boolean computeBounds,
- /** ignored */
- boolean fastHyphenationMode) {
- return MeasuredTextBuilderNatives.nBuildMeasuredText(
- nativeBuilderPtr, hintMtPtr, text, computeHyphenation, computeLayout);
- }
-
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr) {
MeasuredTextBuilderNatives.nFreeBuilder(nativeBuilderPtr);
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeNativeInterpolatorFactory.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeNativeInterpolatorFactory.java
index 21c80e5c1..1eb917ea6 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeNativeInterpolatorFactory.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeNativeInterpolatorFactory.java
@@ -8,70 +8,72 @@ import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.nativeruntime.NativeInterpolatorFactoryNatives;
import org.robolectric.shadows.ShadowNativeNativeInterpolatorFactory.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link NativeInterpolatorFactory} that is backed by native code */
@Implements(
value = NativeInterpolatorFactory.class,
minSdk = R,
shadowPicker = Picker.class,
- isInAndroidSdk = false)
+ isInAndroidSdk = false,
+ callNativeMethodsByDefault = true)
public class ShadowNativeNativeInterpolatorFactory {
static {
DefaultNativeRuntimeLoader.injectAndLoad();
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long createAccelerateDecelerateInterpolator() {
return NativeInterpolatorFactoryNatives.createAccelerateDecelerateInterpolator();
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long createAccelerateInterpolator(float factor) {
return NativeInterpolatorFactoryNatives.createAccelerateInterpolator(factor);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long createAnticipateInterpolator(float tension) {
return NativeInterpolatorFactoryNatives.createAnticipateInterpolator(tension);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long createAnticipateOvershootInterpolator(float tension) {
return NativeInterpolatorFactoryNatives.createAnticipateOvershootInterpolator(tension);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long createBounceInterpolator() {
return NativeInterpolatorFactoryNatives.createBounceInterpolator();
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long createCycleInterpolator(float cycles) {
return NativeInterpolatorFactoryNatives.createCycleInterpolator(cycles);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long createDecelerateInterpolator(float factor) {
return NativeInterpolatorFactoryNatives.createDecelerateInterpolator(factor);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long createLinearInterpolator() {
return NativeInterpolatorFactoryNatives.createLinearInterpolator();
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long createOvershootInterpolator(float tension) {
return NativeInterpolatorFactoryNatives.createOvershootInterpolator(tension);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long createPathInterpolator(float[] x, float[] y) {
return NativeInterpolatorFactoryNatives.createPathInterpolator(x, y);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long createLutInterpolator(float[] values) {
return NativeInterpolatorFactoryNatives.createLutInterpolator(values);
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeNinePatch.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeNinePatch.java
index 11e5ba867..5b53d454b 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeNinePatch.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeNinePatch.java
@@ -10,33 +10,35 @@ import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.nativeruntime.NinePatchNatives;
import org.robolectric.shadows.ShadowNativeNinePatch.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link NinePatch} that is backed by native code */
@Implements(
value = NinePatch.class,
minSdk = O,
shadowPicker = Picker.class,
- isInAndroidSdk = false)
+ isInAndroidSdk = false,
+ callNativeMethodsByDefault = true)
public class ShadowNativeNinePatch {
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean isNinePatchChunk(byte[] chunk) {
DefaultNativeRuntimeLoader.injectAndLoad();
return NinePatchNatives.isNinePatchChunk(chunk);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long validateNinePatchChunk(byte[] chunk) {
DefaultNativeRuntimeLoader.injectAndLoad();
return NinePatchNatives.validateNinePatchChunk(chunk);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nativeFinalize(long chunk) {
NinePatchNatives.nativeFinalize(chunk);
}
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static long nativeGetTransparentRegion(long bitmapHandle, long chunk, Rect location) {
return NinePatchNatives.nativeGetTransparentRegion(bitmapHandle, chunk, location);
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePaint.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePaint.java
index 34cb9ebd9..7d2dda662 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePaint.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePaint.java
@@ -6,20 +6,18 @@ import static android.os.Build.VERSION_CODES.P;
import static android.os.Build.VERSION_CODES.Q;
import static android.os.Build.VERSION_CODES.TIRAMISU;
+import android.annotation.ColorInt;
+import android.annotation.ColorLong;
import android.graphics.Paint;
import android.graphics.Paint.FontMetrics;
import android.graphics.Paint.FontMetricsInt;
import android.graphics.Rect;
-import android.graphics.RectF;
-import androidx.annotation.ColorInt;
-import androidx.annotation.ColorLong;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.nativeruntime.PaintNatives;
import org.robolectric.shadows.ShadowNativePaint.Picker;
import org.robolectric.versioning.AndroidVersions.U;
-import org.robolectric.versioning.AndroidVersions.V;
/** Shadow for {@link Paint} that is backed by native code */
@Implements(
@@ -27,18 +25,19 @@ import org.robolectric.versioning.AndroidVersions.V;
value = Paint.class,
looseSignatures = true,
shadowPicker = Picker.class,
- isInAndroidSdk = false)
+ isInAndroidSdk = false,
+ callNativeMethodsByDefault = true)
public class ShadowNativePaint {
// nGetTextRunCursor methods are non-static
private PaintNatives paintNatives = new PaintNatives();
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nGetNativeFinalizer() {
return PaintNatives.nGetNativeFinalizer();
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nInit() {
DefaultNativeRuntimeLoader.injectAndLoad();
// This native code calls Typeface::resolveDefault, which requires Typeface clinit to have run.
@@ -57,13 +56,13 @@ public class ShadowNativePaint {
PaintNatives.nSetEndHyphenEdit(paintPtr, hyphen);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nInitWithPaint(long paint) {
DefaultNativeRuntimeLoader.injectAndLoad();
return PaintNatives.nInitWithPaint(paint);
}
- @Implementation(minSdk = P)
+ @Implementation(minSdk = P, maxSdk = U.SDK_INT)
protected static int nBreakText(
long nObject,
char[] text,
@@ -75,7 +74,7 @@ public class ShadowNativePaint {
return PaintNatives.nBreakText(nObject, text, index, count, maxWidth, bidiFlags, measuredWidth);
}
- @Implementation(minSdk = P)
+ @Implementation(minSdk = P, maxSdk = U.SDK_INT)
protected static int nBreakText(
long nObject,
String text,
@@ -114,7 +113,7 @@ public class ShadowNativePaint {
nObject, typefacePtr, text, measureForwards, maxWidth, bidiFlags, measuredWidth);
}
- @Implementation(minSdk = P)
+ @Implementation(minSdk = P, maxSdk = U.SDK_INT)
protected static float nGetTextAdvances(
long paintPtr,
char[] text,
@@ -137,7 +136,7 @@ public class ShadowNativePaint {
advancesIndex);
}
- @Implementation(minSdk = P)
+ @Implementation(minSdk = P, maxSdk = U.SDK_INT)
protected static float nGetTextAdvances(
long paintPtr,
String text,
@@ -202,7 +201,7 @@ public class ShadowNativePaint {
advancesIndex);
}
- @Implementation(minSdk = P)
+ @Implementation(minSdk = P, maxSdk = U.SDK_INT)
protected int nGetTextRunCursor(
long paintPtr,
char[] text,
@@ -215,7 +214,7 @@ public class ShadowNativePaint {
paintPtr, text, contextStart, contextLength, dir, offset, cursorOpt);
}
- @Implementation(minSdk = P)
+ @Implementation(minSdk = P, maxSdk = U.SDK_INT)
protected int nGetTextRunCursor(
long paintPtr,
String text,
@@ -256,7 +255,7 @@ public class ShadowNativePaint {
paintPtr, typefacePtr, text, contextStart, contextEnd, dir, offset, cursorOpt);
}
- @Implementation(minSdk = P)
+ @Implementation(minSdk = P, maxSdk = U.SDK_INT)
protected static void nGetTextPath(
long paintPtr,
int bidiFlags,
@@ -269,7 +268,7 @@ public class ShadowNativePaint {
PaintNatives.nGetTextPath(paintPtr, bidiFlags, text, index, count, x, y, path);
}
- @Implementation(minSdk = P)
+ @Implementation(minSdk = P, maxSdk = U.SDK_INT)
protected static void nGetTextPath(
long paintPtr, int bidiFlags, String text, int start, int end, float x, float y, long path) {
PaintNatives.nGetTextPath(paintPtr, bidiFlags, text, start, end, x, y, path);
@@ -303,7 +302,7 @@ public class ShadowNativePaint {
PaintNatives.nGetTextPath(paintPtr, typefacePtr, bidiFlags, text, start, end, x, y, path);
}
- @Implementation(minSdk = P)
+ @Implementation(minSdk = P, maxSdk = U.SDK_INT)
protected static void nGetStringBounds(
long nativePaint, String text, int start, int end, int bidiFlags, Rect bounds) {
PaintNatives.nGetStringBounds(nativePaint, text, start, end, bidiFlags, bounds);
@@ -331,7 +330,7 @@ public class ShadowNativePaint {
return PaintNatives.nGetAlpha(paintPtr);
}
- @Implementation(minSdk = P)
+ @Implementation(minSdk = P, maxSdk = U.SDK_INT)
protected static void nGetCharArrayBounds(
long nativePaint, char[] text, int index, int count, int bidiFlags, Rect bounds) {
PaintNatives.nGetCharArrayBounds(nativePaint, text, index, count, bidiFlags, bounds);
@@ -350,7 +349,7 @@ public class ShadowNativePaint {
nativePaint, typefacePtr, text, index, count, bidiFlags, bounds);
}
- @Implementation(minSdk = P)
+ @Implementation(minSdk = P, maxSdk = U.SDK_INT)
protected static boolean nHasGlyph(long paintPtr, int bidiFlags, String string) {
return PaintNatives.nHasGlyph(paintPtr, bidiFlags, string);
}
@@ -361,7 +360,7 @@ public class ShadowNativePaint {
return PaintNatives.nHasGlyph(paintPtr, typefacePtr, bidiFlags, string);
}
- @Implementation(minSdk = P)
+ @Implementation(minSdk = P, maxSdk = U.SDK_INT)
protected static float nGetRunAdvance(
long paintPtr,
char[] text,
@@ -405,7 +404,7 @@ public class ShadowNativePaint {
paintPtr, typefacePtr, text, start, end, contextStart, contextEnd, isRtl, advance);
}
- @Implementation(minSdk = P)
+ @Implementation(minSdk = P, maxSdk = U.SDK_INT)
protected static int nGetOffsetForAdvance(
long paintPtr,
char[] text,
@@ -419,22 +418,16 @@ public class ShadowNativePaint {
paintPtr, text, start, end, contextStart, contextEnd, isRtl, advance);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static int nSetTextLocales(long paintPtr, String locales) {
return PaintNatives.nSetTextLocales(paintPtr, locales);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetFontFeatureSettings(long paintPtr, String settings) {
PaintNatives.nSetFontFeatureSettings(paintPtr, settings);
}
- @Implementation(minSdk = V.SDK_INT)
- protected static float nGetFontMetrics(
- long paintPtr, FontMetrics metrics, /* Ignored */ boolean useLocale) {
- return PaintNatives.nGetFontMetrics(paintPtr, metrics);
- }
-
@Implementation(minSdk = P, maxSdk = U.SDK_INT)
protected static float nGetFontMetrics(long paintPtr, FontMetrics metrics) {
return PaintNatives.nGetFontMetrics(paintPtr, metrics);
@@ -445,12 +438,6 @@ public class ShadowNativePaint {
return PaintNatives.nGetFontMetrics(paintPtr, typefacePtr, metrics);
}
- @Implementation(minSdk = V.SDK_INT)
- protected static int nGetFontMetricsInt(
- long paintPtr, FontMetricsInt fmi, /* Ignored */ boolean useLocale) {
- return PaintNatives.nGetFontMetricsInt(paintPtr, fmi);
- }
-
@Implementation(minSdk = P, maxSdk = U.SDK_INT)
protected static int nGetFontMetricsInt(long paintPtr, FontMetricsInt fmi) {
return PaintNatives.nGetFontMetricsInt(paintPtr, fmi);
@@ -461,77 +448,77 @@ public class ShadowNativePaint {
return PaintNatives.nGetFontMetricsInt(paintPtr, typefacePtr, fmi);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nReset(long paintPtr) {
PaintNatives.nReset(paintPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSet(long paintPtrDest, long paintPtrSrc) {
PaintNatives.nSet(paintPtrDest, paintPtrSrc);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static int nGetStyle(long paintPtr) {
return PaintNatives.nGetStyle(paintPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetStyle(long paintPtr, int style) {
PaintNatives.nSetStyle(paintPtr, style);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static int nGetStrokeCap(long paintPtr) {
return PaintNatives.nGetStrokeCap(paintPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetStrokeCap(long paintPtr, int cap) {
PaintNatives.nSetStrokeCap(paintPtr, cap);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static int nGetStrokeJoin(long paintPtr) {
return PaintNatives.nGetStrokeJoin(paintPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetStrokeJoin(long paintPtr, int join) {
PaintNatives.nSetStrokeJoin(paintPtr, join);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nGetFillPath(long paintPtr, long src, long dst) {
return PaintNatives.nGetFillPath(paintPtr, src, dst);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nSetShader(long paintPtr, long shader) {
return PaintNatives.nSetShader(paintPtr, shader);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nSetColorFilter(long paintPtr, long filter) {
return PaintNatives.nSetColorFilter(paintPtr, filter);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetXfermode(long paintPtr, int xfermode) {
PaintNatives.nSetXfermode(paintPtr, xfermode);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nSetPathEffect(long paintPtr, long effect) {
return PaintNatives.nSetPathEffect(paintPtr, effect);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nSetMaskFilter(long paintPtr, long maskfilter) {
return PaintNatives.nSetMaskFilter(paintPtr, maskfilter);
}
- @Implementation(minSdk = P)
+ @Implementation(minSdk = P, maxSdk = U.SDK_INT)
protected static void nSetTypeface(long paintPtr, long typeface) {
PaintNatives.nSetTypeface(paintPtr, typeface);
}
@@ -542,23 +529,23 @@ public class ShadowNativePaint {
return paintPtr;
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static int nGetTextAlign(long paintPtr) {
return PaintNatives.nGetTextAlign(paintPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetTextAlign(long paintPtr, int align) {
PaintNatives.nSetTextAlign(paintPtr, align);
}
- @Implementation(minSdk = P)
+ @Implementation(minSdk = P, maxSdk = U.SDK_INT)
protected static void nSetTextLocalesByMinikinLocaleListId(
long paintPtr, int mMinikinLocaleListId) {
PaintNatives.nSetTextLocalesByMinikinLocaleListId(paintPtr, mMinikinLocaleListId);
}
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static void nSetShadowLayer(
long paintPtr,
float radius,
@@ -575,142 +562,142 @@ public class ShadowNativePaint {
PaintNatives.nSetShadowLayer(paintPtr, radius, dx, dy, color);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nHasShadowLayer(long paintPtr) {
return PaintNatives.nHasShadowLayer(paintPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static float nGetLetterSpacing(long paintPtr) {
return PaintNatives.nGetLetterSpacing(paintPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetLetterSpacing(long paintPtr, float letterSpacing) {
PaintNatives.nSetLetterSpacing(paintPtr, letterSpacing);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static float nGetWordSpacing(long paintPtr) {
return PaintNatives.nGetWordSpacing(paintPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetWordSpacing(long paintPtr, float wordSpacing) {
PaintNatives.nSetWordSpacing(paintPtr, wordSpacing);
}
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static int nGetStartHyphenEdit(long paintPtr) {
return PaintNatives.nGetStartHyphenEdit(paintPtr);
}
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static int nGetEndHyphenEdit(long paintPtr) {
return PaintNatives.nGetEndHyphenEdit(paintPtr);
}
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static void nSetStartHyphenEdit(long paintPtr, int hyphen) {
PaintNatives.nSetStartHyphenEdit(paintPtr, hyphen);
}
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static void nSetEndHyphenEdit(long paintPtr, int hyphen) {
PaintNatives.nSetEndHyphenEdit(paintPtr, hyphen);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetStrokeMiter(long paintPtr, float miter) {
PaintNatives.nSetStrokeMiter(paintPtr, miter);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static float nGetStrokeMiter(long paintPtr) {
return PaintNatives.nGetStrokeMiter(paintPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetStrokeWidth(long paintPtr, float width) {
PaintNatives.nSetStrokeWidth(paintPtr, width);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static float nGetStrokeWidth(long paintPtr) {
return PaintNatives.nGetStrokeWidth(paintPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetAlpha(long paintPtr, int a) {
PaintNatives.nSetAlpha(paintPtr, a);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetDither(long paintPtr, boolean dither) {
PaintNatives.nSetDither(paintPtr, dither);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static int nGetFlags(long paintPtr) {
return PaintNatives.nGetFlags(paintPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetFlags(long paintPtr, int flags) {
PaintNatives.nSetFlags(paintPtr, flags);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static int nGetHinting(long paintPtr) {
return PaintNatives.nGetHinting(paintPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetHinting(long paintPtr, int mode) {
PaintNatives.nSetHinting(paintPtr, mode);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetAntiAlias(long paintPtr, boolean aa) {
PaintNatives.nSetAntiAlias(paintPtr, aa);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetLinearText(long paintPtr, boolean linearText) {
PaintNatives.nSetLinearText(paintPtr, linearText);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetSubpixelText(long paintPtr, boolean subpixelText) {
PaintNatives.nSetSubpixelText(paintPtr, subpixelText);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetUnderlineText(long paintPtr, boolean underlineText) {
PaintNatives.nSetUnderlineText(paintPtr, underlineText);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetFakeBoldText(long paintPtr, boolean fakeBoldText) {
PaintNatives.nSetFakeBoldText(paintPtr, fakeBoldText);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetFilterBitmap(long paintPtr, boolean filter) {
PaintNatives.nSetFilterBitmap(paintPtr, filter);
}
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static void nSetColor(long paintPtr, long colorSpaceHandle, @ColorLong long color) {
PaintNatives.nSetColor(paintPtr, colorSpaceHandle, color);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetColor(long paintPtr, @ColorInt int color) {
PaintNatives.nSetColor(paintPtr, color);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetStrikeThruText(long paintPtr, boolean strikeThruText) {
PaintNatives.nSetStrikeThruText(paintPtr, strikeThruText);
}
@@ -720,52 +707,42 @@ public class ShadowNativePaint {
return PaintNatives.nIsElegantTextHeight(paintPtr);
}
- @Implementation(minSdk = V.SDK_INT)
- protected static int nGetElegantTextHeight(long paintPtr) {
- return PaintNatives.nGetElegantTextHeight(paintPtr);
- }
-
// Note: the following three values must be equal to the ones in the JNI file: Paint.cpp
private static final int ELEGANT_TEXT_HEIGHT_ENABLED = 0;
private static final int ELEGANT_TEXT_HEIGHT_DISABLED = 1;
@Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetElegantTextHeight(long paintPtr, boolean elegant) {
- nSetElegantTextHeight(
+ PaintNatives.nSetElegantTextHeight(
paintPtr, elegant ? ELEGANT_TEXT_HEIGHT_ENABLED : ELEGANT_TEXT_HEIGHT_DISABLED);
}
- @Implementation(minSdk = V.SDK_INT)
- protected static void nSetElegantTextHeight(long paintPtr, int value) {
- PaintNatives.nSetElegantTextHeight(paintPtr, value);
- }
-
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static float nGetTextSize(long paintPtr) {
return PaintNatives.nGetTextSize(paintPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static float nGetTextScaleX(long paintPtr) {
return PaintNatives.nGetTextScaleX(paintPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetTextScaleX(long paintPtr, float scaleX) {
PaintNatives.nSetTextScaleX(paintPtr, scaleX);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static float nGetTextSkewX(long paintPtr) {
return PaintNatives.nGetTextSkewX(paintPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetTextSkewX(long paintPtr, float skewX) {
PaintNatives.nSetTextSkewX(paintPtr, skewX);
}
- @Implementation(minSdk = P)
+ @Implementation(minSdk = P, maxSdk = U.SDK_INT)
protected static float nAscent(long paintPtr) {
return PaintNatives.nAscent(paintPtr);
}
@@ -775,7 +752,7 @@ public class ShadowNativePaint {
return PaintNatives.nAscent(paintPtr, typefacePtr);
}
- @Implementation(minSdk = P)
+ @Implementation(minSdk = P, maxSdk = U.SDK_INT)
protected static float nDescent(long paintPtr) {
return PaintNatives.nDescent(paintPtr);
}
@@ -785,37 +762,57 @@ public class ShadowNativePaint {
return PaintNatives.nDescent(paintPtr, typefacePtr);
}
- @Implementation(minSdk = P)
+ @Implementation(minSdk = P, maxSdk = U.SDK_INT)
protected static float nGetUnderlinePosition(long paintPtr) {
return PaintNatives.nGetUnderlinePosition(paintPtr);
}
- @Implementation(minSdk = P)
+ @Implementation(minSdk = O_MR1, maxSdk = O_MR1)
+ protected static float nGetUnderlinePosition(long paintPtr, long typefacePtr) {
+ return nGetUnderlinePosition(paintPtr);
+ }
+
+ @Implementation(minSdk = P, maxSdk = U.SDK_INT)
protected static float nGetUnderlineThickness(long paintPtr) {
return PaintNatives.nGetUnderlineThickness(paintPtr);
}
- @Implementation(minSdk = P)
+ @Implementation(minSdk = O_MR1, maxSdk = O_MR1)
+ protected static float nGetUnderlineThickness(long paintPtr, long typefacePtr) {
+ return nGetUnderlineThickness(paintPtr);
+ }
+
+ @Implementation(minSdk = P, maxSdk = U.SDK_INT)
protected static float nGetStrikeThruPosition(long paintPtr) {
return PaintNatives.nGetStrikeThruPosition(paintPtr);
}
- @Implementation(minSdk = P)
+ @Implementation(minSdk = O_MR1, maxSdk = O_MR1)
+ protected static float nGetStrikeThruPosition(long paintPtr, long typefacePtr) {
+ return nGetStrikeThruPosition(paintPtr);
+ }
+
+ @Implementation(minSdk = P, maxSdk = U.SDK_INT)
protected static float nGetStrikeThruThickness(long paintPtr) {
return PaintNatives.nGetStrikeThruThickness(paintPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O_MR1, maxSdk = O_MR1)
+ protected static float nGetStrikeThruThickness(long paintPtr, long typefacePtr) {
+ return nGetStrikeThruThickness(paintPtr);
+ }
+
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetTextSize(long paintPtr, float textSize) {
PaintNatives.nSetTextSize(paintPtr, textSize);
}
- @Implementation(minSdk = P)
+ @Implementation(minSdk = P, maxSdk = U.SDK_INT)
protected static boolean nEqualsForTextMeasurement(long leftPaintPtr, long rightPaintPtr) {
return PaintNatives.nEqualsForTextMeasurement(leftPaintPtr, rightPaintPtr);
}
- @Implementation(minSdk = TIRAMISU)
+ @Implementation(minSdk = TIRAMISU, maxSdk = U.SDK_INT)
protected static void nGetFontMetricsIntForText(
long paintPtr,
char[] text,
@@ -829,7 +826,7 @@ public class ShadowNativePaint {
paintPtr, text, start, count, ctxStart, ctxCount, isRtl, outMetrics);
}
- @Implementation(minSdk = TIRAMISU)
+ @Implementation(minSdk = TIRAMISU, maxSdk = U.SDK_INT)
protected static void nGetFontMetricsIntForText(
long paintPtr,
String text,
@@ -868,32 +865,9 @@ public class ShadowNativePaint {
advancesIndex);
}
- /** Requires loose signatures because of RunInfo parameter */
- @Implementation(minSdk = V.SDK_INT)
- protected static float nGetRunCharacterAdvance(
- Object /* long */ paintPtr,
- Object /* char[] */ text,
- Object /* int */ start,
- Object /* int */ end,
- Object /* int */ contextStart,
- Object /* int */ contextEnd,
- Object /* boolean */ isRtl,
- Object /* int */ offset,
- Object /* float[] */ advances,
- Object /* int */ advancesIndex,
- Object /* RectF */ drawingBounds,
- Object /* RunInfo */ runInfo) {
- return nGetRunCharacterAdvance(
- (long) paintPtr,
- (char[]) text,
- (int) start,
- (int) end,
- (int) contextStart,
- (int) contextEnd,
- (boolean) isRtl,
- (int) offset,
- (float[]) advances,
- (int) advancesIndex);
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static void nSetTextLocalesByMinikinLangListId(long paintPtr, int mMinikinLangListId) {
+ // no-op
}
/** Shadow picker for {@link Paint}. */
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePath.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePath.java
index c162df5a5..a5c9d2f8e 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePath.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePath.java
@@ -10,89 +10,103 @@ import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.nativeruntime.PathNatives;
+import org.robolectric.versioning.AndroidVersions.U;
+import org.robolectric.versioning.AndroidVersions.V;
/** Shadow for {@link Path} that is backed by native code */
-@Implements(value = Path.class, minSdk = O, isInAndroidSdk = false)
+@Implements(
+ value = Path.class,
+ minSdk = O,
+ isInAndroidSdk = false,
+ callNativeMethodsByDefault = true)
public class ShadowNativePath extends ShadowPath {
+ /**
+ * The {@link Path} static initializer invokes its own native methods. This has to be deferred
+ * starting in Android V.
+ */
+ @Implementation(minSdk = V.SDK_INT)
+ protected static void __staticInitializer__() {
+ // deferred
+ }
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nInit() {
DefaultNativeRuntimeLoader.injectAndLoad();
return PathNatives.nInit();
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nInit(long nPath) {
// Required for pre-P.
DefaultNativeRuntimeLoader.injectAndLoad();
return PathNatives.nInit(nPath);
}
- @Implementation(minSdk = P)
+ @Implementation(minSdk = P, maxSdk = U.SDK_INT)
protected static long nGetFinalizer() {
// Required for pre-P.
DefaultNativeRuntimeLoader.injectAndLoad();
return PathNatives.nGetFinalizer();
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSet(long nativeDst, long nSrc) {
PathNatives.nSet(nativeDst, nSrc);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nComputeBounds(long nPath, RectF bounds) {
PathNatives.nComputeBounds(nPath, bounds);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nIncReserve(long nPath, int extraPtCount) {
PathNatives.nIncReserve(nPath, extraPtCount);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nMoveTo(long nPath, float x, float y) {
PathNatives.nMoveTo(nPath, x, y);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nRMoveTo(long nPath, float dx, float dy) {
PathNatives.nRMoveTo(nPath, dx, dy);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nLineTo(long nPath, float x, float y) {
PathNatives.nLineTo(nPath, x, y);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nRLineTo(long nPath, float dx, float dy) {
PathNatives.nRLineTo(nPath, dx, dy);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nQuadTo(long nPath, float x1, float y1, float x2, float y2) {
PathNatives.nQuadTo(nPath, x1, y1, x2, y2);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nRQuadTo(long nPath, float dx1, float dy1, float dx2, float dy2) {
PathNatives.nRQuadTo(nPath, dx1, dy1, dx2, dy2);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nCubicTo(
long nPath, float x1, float y1, float x2, float y2, float x3, float y3) {
PathNatives.nCubicTo(nPath, x1, y1, x2, y2, x3, y3);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nRCubicTo(
long nPath, float x1, float y1, float x2, float y2, float x3, float y3) {
PathNatives.nRCubicTo(nPath, x1, y1, x2, y2, x3, y3);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nArcTo(
long nPath,
float left,
@@ -105,29 +119,29 @@ public class ShadowNativePath extends ShadowPath {
PathNatives.nArcTo(nPath, left, top, right, bottom, startAngle, sweepAngle, forceMoveTo);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nClose(long nPath) {
PathNatives.nClose(nPath);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nAddRect(
long nPath, float left, float top, float right, float bottom, int dir) {
PathNatives.nAddRect(nPath, left, top, right, bottom, dir);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nAddOval(
long nPath, float left, float top, float right, float bottom, int dir) {
PathNatives.nAddOval(nPath, left, top, right, bottom, dir);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nAddCircle(long nPath, float x, float y, float radius, int dir) {
PathNatives.nAddCircle(nPath, x, y, radius, dir);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nAddArc(
long nPath,
float left,
@@ -139,98 +153,103 @@ public class ShadowNativePath extends ShadowPath {
PathNatives.nAddArc(nPath, left, top, right, bottom, startAngle, sweepAngle);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nAddRoundRect(
long nPath, float left, float top, float right, float bottom, float rx, float ry, int dir) {
PathNatives.nAddRoundRect(nPath, left, top, right, bottom, rx, ry, dir);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nAddRoundRect(
long nPath, float left, float top, float right, float bottom, float[] radii, int dir) {
PathNatives.nAddRoundRect(nPath, left, top, right, bottom, radii, dir);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nAddPath(long nPath, long src, float dx, float dy) {
PathNatives.nAddPath(nPath, src, dx, dy);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nAddPath(long nPath, long src) {
PathNatives.nAddPath(nPath, src);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nAddPath(long nPath, long src, long matrix) {
PathNatives.nAddPath(nPath, src, matrix);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nOffset(long nPath, float dx, float dy) {
PathNatives.nOffset(nPath, dx, dy);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetLastPoint(long nPath, float dx, float dy) {
PathNatives.nSetLastPoint(nPath, dx, dy);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nTransform(long nPath, long matrix, long dstPath) {
PathNatives.nTransform(nPath, matrix, dstPath);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nTransform(long nPath, long matrix) {
PathNatives.nTransform(nPath, matrix);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nOp(long path1, long path2, int op, long result) {
return PathNatives.nOp(path1, path2, op, result);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nIsRect(long nPath, RectF rect) {
return PathNatives.nIsRect(nPath, rect);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nReset(long nPath) {
PathNatives.nReset(nPath);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nRewind(long nPath) {
PathNatives.nRewind(nPath);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nIsEmpty(long nPath) {
return PathNatives.nIsEmpty(nPath);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nIsConvex(long nPath) {
return PathNatives.nIsConvex(nPath);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static int nGetFillType(long nPath) {
return PathNatives.nGetFillType(nPath);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetFillType(long nPath, int ft) {
PathNatives.nSetFillType(nPath, ft);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static float[] nApproximate(long nPath, float error) {
return PathNatives.nApproximate(nPath, error);
}
+ @Implementation(minSdk = U.SDK_INT, maxSdk = U.SDK_INT)
+ protected static int nGetGenerationID(long nativePath) {
+ return 0;
+ }
+
@Override
public List<Point> getPoints() {
throw new UnsupportedOperationException("Legacy ShadowPath description APIs are not supported");
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathDashPathEffect.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathDashPathEffect.java
index b72b0b231..f04dacfb9 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathDashPathEffect.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathDashPathEffect.java
@@ -8,12 +8,17 @@ import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.nativeruntime.PathDashPathEffectNatives;
import org.robolectric.shadows.ShadowNativePathDashPathEffect.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link PathDashPathEffect} that is backed by native code */
-@Implements(value = PathDashPathEffect.class, minSdk = O, shadowPicker = Picker.class)
+@Implements(
+ value = PathDashPathEffect.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ callNativeMethodsByDefault = true)
public class ShadowNativePathDashPathEffect {
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nativeCreate(long nativePath, float advance, float phase, int nativeStyle) {
DefaultNativeRuntimeLoader.injectAndLoad();
return PathDashPathEffectNatives.nativeCreate(nativePath, advance, phase, nativeStyle);
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathEffect.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathEffect.java
index 440eb2642..bcdccae99 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathEffect.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathEffect.java
@@ -7,12 +7,17 @@ import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.PathEffectNatives;
import org.robolectric.shadows.ShadowNativePathEffect.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link PathEffect} that is backed by native code */
-@Implements(value = PathEffect.class, minSdk = O, shadowPicker = Picker.class)
+@Implements(
+ value = PathEffect.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ callNativeMethodsByDefault = true)
public class ShadowNativePathEffect {
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nativeDestructor(long nativePatheffect) {
PathEffectNatives.nativeDestructor(nativePatheffect);
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathMeasure.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathMeasure.java
index fd450e912..34540cc97 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathMeasure.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathMeasure.java
@@ -8,61 +8,63 @@ import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.nativeruntime.PathMeasureNatives;
import org.robolectric.shadows.ShadowNativePathMeasure.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link PathMeasure} that is backed by native code */
@Implements(
value = PathMeasure.class,
minSdk = O,
shadowPicker = Picker.class,
- isInAndroidSdk = false)
+ isInAndroidSdk = false,
+ callNativeMethodsByDefault = true)
public class ShadowNativePathMeasure {
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long native_create(long nativePath, boolean forceClosed) {
DefaultNativeRuntimeLoader.injectAndLoad();
return PathMeasureNatives.native_create(nativePath, forceClosed);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void native_setPath(long nativeInstance, long nativePath, boolean forceClosed) {
PathMeasureNatives.native_setPath(nativeInstance, nativePath, forceClosed);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static float native_getLength(long nativeInstance) {
return PathMeasureNatives.native_getLength(nativeInstance);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean native_getPosTan(
long nativeInstance, float distance, float[] pos, float[] tan) {
return PathMeasureNatives.native_getPosTan(nativeInstance, distance, pos, tan);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean native_getMatrix(
long nativeInstance, float distance, long nativeMatrix, int flags) {
return PathMeasureNatives.native_getMatrix(nativeInstance, distance, nativeMatrix, flags);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean native_getSegment(
long nativeInstance, float startD, float stopD, long nativePath, boolean startWithMoveTo) {
return PathMeasureNatives.native_getSegment(
nativeInstance, startD, stopD, nativePath, startWithMoveTo);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean native_isClosed(long nativeInstance) {
return PathMeasureNatives.native_isClosed(nativeInstance);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean native_nextContour(long nativeInstance) {
return PathMeasureNatives.native_nextContour(nativeInstance);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void native_destroy(long nativeInstance) {
PathMeasureNatives.native_destroy(nativeInstance);
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathParser.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathParser.java
index cf83491ad..1b6ccd549 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathParser.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathParser.java
@@ -8,61 +8,63 @@ import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.nativeruntime.PathParserNatives;
import org.robolectric.shadows.ShadowNativePathParser.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link PathParser} that is backed by native code */
@Implements(
value = PathParser.class,
minSdk = O,
shadowPicker = Picker.class,
- isInAndroidSdk = false)
+ isInAndroidSdk = false,
+ callNativeMethodsByDefault = true)
public class ShadowNativePathParser {
static {
DefaultNativeRuntimeLoader.injectAndLoad();
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nParseStringForPath(long pathPtr, String pathString, int stringLength) {
PathParserNatives.nParseStringForPath(pathPtr, pathString, stringLength);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nCreatePathDataFromString(String pathString, int stringLength) {
return PathParserNatives.nCreatePathDataFromString(pathString, stringLength);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nCreatePathFromPathData(long outPathPtr, long pathData) {
PathParserNatives.nCreatePathFromPathData(outPathPtr, pathData);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nCreateEmptyPathData() {
return PathParserNatives.nCreateEmptyPathData();
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nCreatePathData(long nativePtr) {
return PathParserNatives.nCreatePathData(nativePtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nInterpolatePathData(
long outDataPtr, long fromDataPtr, long toDataPtr, float fraction) {
return PathParserNatives.nInterpolatePathData(outDataPtr, fromDataPtr, toDataPtr, fraction);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nFinalize(long nativePtr) {
PathParserNatives.nFinalize(nativePtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nCanMorph(long fromDataPtr, long toDataPtr) {
return PathParserNatives.nCanMorph(fromDataPtr, toDataPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetPathData(long outDataPtr, long fromDataPtr) {
PathParserNatives.nSetPathData(outDataPtr, fromDataPtr);
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePicture.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePicture.java
index 493076c28..ce98fc8a1 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePicture.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePicture.java
@@ -10,55 +10,61 @@ import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.nativeruntime.PictureNatives;
import org.robolectric.shadows.ShadowNativePicture.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link Picture} that is backed by native code */
-@Implements(value = Picture.class, minSdk = O, shadowPicker = Picker.class, isInAndroidSdk = false)
+@Implements(
+ value = Picture.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ isInAndroidSdk = false,
+ callNativeMethodsByDefault = true)
public class ShadowNativePicture {
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long nativeConstructor(long nativeSrcOr0) {
DefaultNativeRuntimeLoader.injectAndLoad();
return PictureNatives.nativeConstructor(nativeSrcOr0);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long nativeCreateFromStream(InputStream stream, byte[] storage) {
DefaultNativeRuntimeLoader.injectAndLoad();
return PictureNatives.nativeCreateFromStream(stream, storage);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static int nativeGetWidth(long nativePicture) {
return PictureNatives.nativeGetWidth(nativePicture);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static int nativeGetHeight(long nativePicture) {
return PictureNatives.nativeGetHeight(nativePicture);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long nativeBeginRecording(long nativeCanvas, int w, int h) {
return PictureNatives.nativeBeginRecording(nativeCanvas, w, h);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nativeEndRecording(long nativeCanvas) {
PictureNatives.nativeEndRecording(nativeCanvas);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nativeDraw(long nativeCanvas, long nativePicture) {
PictureNatives.nativeDraw(nativeCanvas, nativePicture);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nativeWriteToStream(
long nativePicture, OutputStream stream, byte[] storage) {
return PictureNatives.nativeWriteToStream(nativePicture, stream, storage);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nativeDestructor(long nativePicture) {
PictureNatives.nativeDestructor(nativePicture);
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePluralRules.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePluralRules.java
index e99af08c8..454843ee5 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePluralRules.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePluralRules.java
@@ -1,7 +1,6 @@
package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.M;
import org.robolectric.annotation.Implementation;
@@ -10,7 +9,7 @@ import org.robolectric.annotation.Implements;
@Implements(className = "libcore.icu.NativePluralRules", isInAndroidSdk = false, maxSdk = M)
public class ShadowNativePluralRules {
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected static int quantityForIntImpl(long address, int quantity) {
// just return the mapping for english locale for now
if (quantity == 1) return 1;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePorterDuffColorFilter.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePorterDuffColorFilter.java
index 837ab51d3..71c1b1919 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePorterDuffColorFilter.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePorterDuffColorFilter.java
@@ -10,16 +10,18 @@ import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.nativeruntime.PorterDuffColorFilterNatives;
import org.robolectric.shadows.ShadowNativePorterDuffColorFilter.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link PorterDuffColorFilter} that is backed by native code */
@Implements(
value = PorterDuffColorFilter.class,
minSdk = O,
shadowPicker = Picker.class,
- isInAndroidSdk = false)
+ isInAndroidSdk = false,
+ callNativeMethodsByDefault = true)
public class ShadowNativePorterDuffColorFilter extends ShadowPorterDuffColorFilter {
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static long native_CreateBlendModeFilter(int srcColor, int blendmode) {
DefaultNativeRuntimeLoader.injectAndLoad();
return PorterDuffColorFilterNatives.native_CreateBlendModeFilter(srcColor, blendmode);
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePositionedGlyphs.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePositionedGlyphs.java
index 90a6f977a..5b65023af 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePositionedGlyphs.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePositionedGlyphs.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import android.graphics.text.MeasuredText;
import android.graphics.text.PositionedGlyphs;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
@@ -8,57 +7,72 @@ import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.nativeruntime.PositionedGlyphsNatives;
import org.robolectric.shadows.ShadowNativePositionedGlyphs.Picker;
import org.robolectric.versioning.AndroidVersions.S;
+import org.robolectric.versioning.AndroidVersions.U;
+import org.robolectric.versioning.AndroidVersions.V;
/** Shadow for {@link PositionedGlyphs} that is backed by native code */
-@Implements(value = PositionedGlyphs.class, minSdk = S.SDK_INT, shadowPicker = Picker.class)
+@Implements(
+ value = PositionedGlyphs.class,
+ minSdk = S.SDK_INT,
+ shadowPicker = Picker.class,
+ callNativeMethodsByDefault = true)
public class ShadowNativePositionedGlyphs {
- @Implementation
+ /**
+ * The {@link PositionedGlyphs} static initializer invokes its own native methods. This has to be
+ * deferred starting in Android V.
+ */
+ @Implementation(minSdk = V.SDK_INT)
+ protected static void __staticInitializer__() {
+ // deferred
+ }
+
+ @Implementation(maxSdk = U.SDK_INT)
protected static int nGetGlyphCount(long minikinLayout) {
return PositionedGlyphsNatives.nGetGlyphCount(minikinLayout);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static float nGetTotalAdvance(long minikinLayout) {
return PositionedGlyphsNatives.nGetTotalAdvance(minikinLayout);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static float nGetAscent(long minikinLayout) {
return PositionedGlyphsNatives.nGetAscent(minikinLayout);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static float nGetDescent(long minikinLayout) {
return PositionedGlyphsNatives.nGetDescent(minikinLayout);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static int nGetGlyphId(long minikinLayout, int i) {
return PositionedGlyphsNatives.nGetGlyphId(minikinLayout, i);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static float nGetX(long minikinLayout, int i) {
return PositionedGlyphsNatives.nGetX(minikinLayout, i);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static float nGetY(long minikinLayout, int i) {
return PositionedGlyphsNatives.nGetY(minikinLayout, i);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long nGetFont(long minikinLayout, int i) {
return PositionedGlyphsNatives.nGetFont(minikinLayout, i);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long nReleaseFunc() {
DefaultNativeRuntimeLoader.injectAndLoad();
return PositionedGlyphsNatives.nReleaseFunc();
}
- /** Shadow picker for {@link MeasuredText}. */
+ /** Shadow picker for {@link PositionedGlyphs}. */
public static final class Picker extends GraphicsShadowPicker<Object> {
public Picker() {
super(null, ShadowNativePositionedGlyphs.class);
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePropertyValuesHolder.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePropertyValuesHolder.java
index f5e9b8e9f..4e2c6f76c 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePropertyValuesHolder.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePropertyValuesHolder.java
@@ -8,75 +8,80 @@ import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.nativeruntime.PropertyValuesHolderNatives;
import org.robolectric.shadows.ShadowNativePropertyValuesHolder.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link PropertyValuesHolder} that is backed by native code */
-@Implements(value = PropertyValuesHolder.class, minSdk = O, shadowPicker = Picker.class)
+@Implements(
+ value = PropertyValuesHolder.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ callNativeMethodsByDefault = true)
public class ShadowNativePropertyValuesHolder {
static {
DefaultNativeRuntimeLoader.injectAndLoad();
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long nGetIntMethod(Class<?> targetClass, String methodName) {
return PropertyValuesHolderNatives.nGetIntMethod(targetClass, methodName);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long nGetFloatMethod(Class<?> targetClass, String methodName) {
return PropertyValuesHolderNatives.nGetFloatMethod(targetClass, methodName);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long nGetMultipleIntMethod(
Class<?> targetClass, String methodName, int numParams) {
return PropertyValuesHolderNatives.nGetMultipleIntMethod(targetClass, methodName, numParams);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long nGetMultipleFloatMethod(
Class<?> targetClass, String methodName, int numParams) {
return PropertyValuesHolderNatives.nGetMultipleFloatMethod(targetClass, methodName, numParams);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nCallIntMethod(Object target, long methodID, int arg) {
PropertyValuesHolderNatives.nCallIntMethod(target, methodID, arg);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nCallFloatMethod(Object target, long methodID, float arg) {
PropertyValuesHolderNatives.nCallFloatMethod(target, methodID, arg);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nCallTwoIntMethod(Object target, long methodID, int arg1, int arg2) {
PropertyValuesHolderNatives.nCallTwoIntMethod(target, methodID, arg1, arg2);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nCallFourIntMethod(
Object target, long methodID, int arg1, int arg2, int arg3, int arg4) {
PropertyValuesHolderNatives.nCallFourIntMethod(target, methodID, arg1, arg2, arg3, arg4);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nCallMultipleIntMethod(Object target, long methodID, int[] args) {
PropertyValuesHolderNatives.nCallMultipleIntMethod(target, methodID, args);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nCallTwoFloatMethod(Object target, long methodID, float arg1, float arg2) {
PropertyValuesHolderNatives.nCallTwoFloatMethod(target, methodID, arg1, arg2);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nCallFourFloatMethod(
Object target, long methodID, float arg1, float arg2, float arg3, float arg4) {
PropertyValuesHolderNatives.nCallFourFloatMethod(target, methodID, arg1, arg2, arg3, arg4);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nCallMultipleFloatMethod(Object target, long methodID, float[] args) {
PropertyValuesHolderNatives.nCallMultipleFloatMethod(target, methodID, args);
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRadialGradient.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRadialGradient.java
index 07d2a724c..4411a095e 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRadialGradient.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRadialGradient.java
@@ -6,19 +6,24 @@ import static android.os.Build.VERSION_CODES.Q;
import static android.os.Build.VERSION_CODES.R;
import static android.os.Build.VERSION_CODES.S;
+import android.annotation.ColorLong;
import android.graphics.RadialGradient;
-import androidx.annotation.ColorLong;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.nativeruntime.RadialGradientNatives;
import org.robolectric.shadows.ShadowNativeRadialGradient.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link RadialGradient} that is backed by native code */
-@Implements(value = RadialGradient.class, minSdk = O, shadowPicker = Picker.class)
+@Implements(
+ value = RadialGradient.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ callNativeMethodsByDefault = true)
public class ShadowNativeRadialGradient {
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static long nativeCreate(
long matrix,
float startX,
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRecordingCanvas.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRecordingCanvas.java
index 537419c86..24df09f04 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRecordingCanvas.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRecordingCanvas.java
@@ -1,67 +1,92 @@
package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.Q;
+import static android.os.Build.VERSION_CODES.R;
import static android.os.Build.VERSION_CODES.S;
import android.graphics.RecordingCanvas;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.nativeruntime.RecordingCanvasNatives;
import org.robolectric.shadows.ShadowNativeRecordingCanvas.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link RecordingCanvas} that is backed by native code */
-@Implements(value = RecordingCanvas.class, minSdk = Q, shadowPicker = Picker.class)
+@Implements(
+ value = RecordingCanvas.class,
+ minSdk = Q,
+ shadowPicker = Picker.class,
+ callNativeMethodsByDefault = true)
public class ShadowNativeRecordingCanvas extends ShadowNativeBaseRecordingCanvas {
- @Implementation
+ private static final Map<Long, Long> recordingCanvasToRenderNode =
+ Collections.synchronizedMap(new HashMap<>());
+
+ @Implementation(maxSdk = U.SDK_INT)
protected static long nCreateDisplayListCanvas(long node, int width, int height) {
DefaultNativeRuntimeLoader.injectAndLoad();
- return RecordingCanvasNatives.nCreateDisplayListCanvas(node, width, height);
+ long result = RecordingCanvasNatives.nCreateDisplayListCanvas(node, width, height);
+ recordingCanvasToRenderNode.put(result, node);
+ return result;
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nResetDisplayListCanvas(long canvas, long node, int width, int height) {
RecordingCanvasNatives.nResetDisplayListCanvas(canvas, node, width, height);
+ recordingCanvasToRenderNode.put(canvas, node);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static int nGetMaximumTextureWidth() {
return RecordingCanvasNatives.nGetMaximumTextureWidth();
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static int nGetMaximumTextureHeight() {
return RecordingCanvasNatives.nGetMaximumTextureHeight();
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static void nEnableZ(long renderer, boolean enableZ) {
RecordingCanvasNatives.nEnableZ(renderer, enableZ);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static void nFinishRecording(long renderer, long renderNode) {
RecordingCanvasNatives.nFinishRecording(renderer, renderNode);
}
- @Implementation
+ @Implementation(minSdk = Q, maxSdk = R)
+ protected static long nFinishRecording(long renderer) {
+ Long renderNode = recordingCanvasToRenderNode.get(renderer);
+ if (renderNode != null && renderNode != 0) {
+ RecordingCanvasNatives.nFinishRecording(renderer, renderNode);
+ }
+ return 0;
+ }
+
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDrawRenderNode(long renderer, long renderNode) {
RecordingCanvasNatives.nDrawRenderNode(renderer, renderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDrawTextureLayer(long renderer, long layer) {
RecordingCanvasNatives.nDrawTextureLayer(renderer, layer);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDrawCircle(
long renderer, long propCx, long propCy, long propRadius, long propPaint) {
RecordingCanvasNatives.nDrawCircle(renderer, propCx, propCy, propRadius, propPaint);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static void nDrawRipple(
long renderer,
long propCx,
@@ -84,7 +109,7 @@ public class ShadowNativeRecordingCanvas extends ShadowNativeBaseRecordingCanvas
runtimeEffect);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDrawRoundRect(
long renderer,
long propLeft,
@@ -98,11 +123,21 @@ public class ShadowNativeRecordingCanvas extends ShadowNativeBaseRecordingCanvas
renderer, propLeft, propTop, propRight, propBottom, propRx, propRy, propPaint);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nDrawWebViewFunctor(long canvas, int functor) {
RecordingCanvasNatives.nDrawWebViewFunctor(canvas, functor);
}
+ @Implementation(maxSdk = R)
+ protected static void nInsertReorderBarrier(long renderer, boolean enableReorder) {
+ // no-op
+ }
+
+ @Resetter
+ public static void reset() {
+ recordingCanvasToRenderNode.clear();
+ }
+
/** Shadow picker for {@link RecordingCanvas}. */
public static final class Picker extends GraphicsShadowPicker<Object> {
public Picker() {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRegion.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRegion.java
index 6c855c48b..898ec4fc0 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRegion.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRegion.java
@@ -17,21 +17,27 @@ import org.robolectric.shadows.ShadowNativeRegion.Picker;
import org.robolectric.util.ReflectionHelpers.ClassParameter;
import org.robolectric.util.reflector.Accessor;
import org.robolectric.util.reflector.ForType;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link Region} that is backed by native code */
-@Implements(value = Region.class, minSdk = O, shadowPicker = Picker.class, isInAndroidSdk = false)
+@Implements(
+ value = Region.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ isInAndroidSdk = false,
+ callNativeMethodsByDefault = true)
public class ShadowNativeRegion {
RegionNatives regionNatives = new RegionNatives();
@RealObject Region realRegion;
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected void __constructor__(long ni) {
invokeConstructor(Region.class, realRegion, ClassParameter.from(long.class, ni));
regionNatives.mNativeRegion = ni;
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected void __constructor__(int left, int top, int right, int bottom) {
invokeConstructor(
Region.class,
@@ -43,128 +49,128 @@ public class ShadowNativeRegion {
regionNatives.mNativeRegion = reflector(RegionReflector.class, realRegion).getNativeRegion();
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected void __constructor__(Rect rect) {
invokeConstructor(Region.class, realRegion, ClassParameter.from(Rect.class, rect));
regionNatives.mNativeRegion = reflector(RegionReflector.class, realRegion).getNativeRegion();
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nativeEquals(long nativeR1, long nativeR2) {
return RegionNatives.nativeEquals(nativeR1, nativeR2);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nativeConstructor() {
DefaultNativeRuntimeLoader.injectAndLoad();
return RegionNatives.nativeConstructor();
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nativeDestructor(long nativeRegion) {
RegionNatives.nativeDestructor(nativeRegion);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nativeSetRegion(long nativeDst, long nativeSrc) {
RegionNatives.nativeSetRegion(nativeDst, nativeSrc);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nativeSetRect(long nativeDst, int left, int top, int right, int bottom) {
return RegionNatives.nativeSetRect(nativeDst, left, top, right, bottom);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nativeSetPath(long nativeDst, long nativePath, long nativeClip) {
return RegionNatives.nativeSetPath(nativeDst, nativePath, nativeClip);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nativeGetBounds(long nativeRegion, Rect rect) {
return RegionNatives.nativeGetBounds(nativeRegion, rect);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nativeGetBoundaryPath(long nativeRegion, long nativePath) {
return RegionNatives.nativeGetBoundaryPath(nativeRegion, nativePath);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nativeOp(
long nativeDst, int left, int top, int right, int bottom, int op) {
return RegionNatives.nativeOp(nativeDst, left, top, right, bottom, op);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nativeOp(long nativeDst, Rect rect, long nativeRegion, int op) {
return RegionNatives.nativeOp(nativeDst, rect, nativeRegion, op);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nativeOp(
long nativeDst, long nativeRegion1, long nativeRegion2, int op) {
return RegionNatives.nativeOp(nativeDst, nativeRegion1, nativeRegion2, op);
}
@DoNotCall("Always throws java.lang.UnsupportedOperationException")
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nativeCreateFromParcel(Parcel p) {
throw new UnsupportedOperationException();
}
@DoNotCall("Always throws java.lang.UnsupportedOperationException")
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nativeWriteToParcel(long nativeRegion, Parcel p) {
throw new UnsupportedOperationException();
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static String nativeToString(long nativeRegion) {
return RegionNatives.nativeToString(nativeRegion);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected boolean isEmpty() {
return regionNatives.isEmpty();
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected boolean isRect() {
return regionNatives.isRect();
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected boolean isComplex() {
return regionNatives.isComplex();
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected boolean contains(int x, int y) {
return regionNatives.contains(x, y);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected boolean quickContains(int left, int top, int right, int bottom) {
return regionNatives.quickContains(left, top, right, bottom);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected boolean quickReject(int left, int top, int right, int bottom) {
return regionNatives.quickReject(left, top, right, bottom);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected boolean quickReject(Region rgn) {
return regionNatives.quickReject(rgn);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected void translate(int dx, int dy, Region dst) {
regionNatives.translate(dx, dy, dst);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected void scale(float scale, Region dst) {
regionNatives.scale(scale, dst);
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRegionIterator.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRegionIterator.java
index b47bd420c..cb0055aed 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRegionIterator.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRegionIterator.java
@@ -9,23 +9,28 @@ import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.nativeruntime.RegionIteratorNatives;
import org.robolectric.shadows.ShadowNativeRegionIterator.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link RegionIterator} that is backed by native code */
-@Implements(value = RegionIterator.class, minSdk = O, shadowPicker = Picker.class)
+@Implements(
+ value = RegionIterator.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ callNativeMethodsByDefault = true)
public class ShadowNativeRegionIterator {
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nativeConstructor(long nativeRegion) {
DefaultNativeRuntimeLoader.injectAndLoad();
return RegionIteratorNatives.nativeConstructor(nativeRegion);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nativeDestructor(long nativeIter) {
RegionIteratorNatives.nativeDestructor(nativeIter);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nativeNext(long nativeIter, Rect r) {
return RegionIteratorNatives.nativeNext(nativeIter, r);
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderEffect.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderEffect.java
index 0535803db..d56feb579 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderEffect.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderEffect.java
@@ -9,26 +9,31 @@ import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.nativeruntime.RenderEffectNatives;
import org.robolectric.shadows.ShadowNativeRenderEffect.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link RenderEffect} that is backed by native code */
-@Implements(value = RenderEffect.class, minSdk = O, shadowPicker = Picker.class)
+@Implements(
+ value = RenderEffect.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ callNativeMethodsByDefault = true)
public class ShadowNativeRenderEffect {
static {
DefaultNativeRuntimeLoader.injectAndLoad();
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static long nativeCreateOffsetEffect(float offsetX, float offsetY, long nativeInput) {
return RenderEffectNatives.nativeCreateOffsetEffect(offsetX, offsetY, nativeInput);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static long nativeCreateBlurEffect(
float radiusX, float radiusY, long nativeInput, int edgeTreatment) {
return RenderEffectNatives.nativeCreateBlurEffect(radiusX, radiusY, nativeInput, edgeTreatment);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static long nativeCreateBitmapEffect(
long bitmapHandle,
float srcLeft,
@@ -43,27 +48,27 @@ public class ShadowNativeRenderEffect {
bitmapHandle, srcLeft, srcTop, srcRight, srcBottom, dstLeft, dstTop, dstRight, dstBottom);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static long nativeCreateColorFilterEffect(long colorFilter, long nativeInput) {
return RenderEffectNatives.nativeCreateColorFilterEffect(colorFilter, nativeInput);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static long nativeCreateBlendModeEffect(long dst, long src, int blendmode) {
return RenderEffectNatives.nativeCreateBlendModeEffect(dst, src, blendmode);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static long nativeCreateChainEffect(long outer, long inner) {
return RenderEffectNatives.nativeCreateChainEffect(outer, inner);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static long nativeCreateShaderEffect(long shader) {
return RenderEffectNatives.nativeCreateShaderEffect(shader);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static long nativeGetFinalizer() {
return RenderEffectNatives.nativeGetFinalizer();
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderNode.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderNode.java
index 4b11355f7..489602636 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderNode.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderNode.java
@@ -4,40 +4,47 @@ import static android.os.Build.VERSION_CODES.Q;
import static android.os.Build.VERSION_CODES.R;
import static android.os.Build.VERSION_CODES.S;
import static android.os.Build.VERSION_CODES.S_V2;
+import static android.os.Build.VERSION_CODES.TIRAMISU;
import android.graphics.RenderNode;
import android.graphics.RenderNode.PositionUpdateListener;
+import java.lang.ref.WeakReference;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.nativeruntime.RenderNodeNatives;
import org.robolectric.shadows.ShadowNativeRenderNode.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link RenderNode} that is backed by native code */
-@Implements(value = RenderNode.class, minSdk = Q, shadowPicker = Picker.class)
+@Implements(
+ value = RenderNode.class,
+ minSdk = Q,
+ shadowPicker = Picker.class,
+ callNativeMethodsByDefault = true)
public class ShadowNativeRenderNode {
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long nCreate(String name) {
DefaultNativeRuntimeLoader.injectAndLoad();
return RenderNodeNatives.nCreate(name);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long nGetNativeFinalizer() {
return RenderNodeNatives.nGetNativeFinalizer();
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nOutput(long renderNode) {
RenderNodeNatives.nOutput(renderNode);
}
- @Implementation(minSdk = R)
+ @Implementation(minSdk = R, maxSdk = U.SDK_INT)
protected static int nGetUsageSize(long renderNode) {
return RenderNodeNatives.nGetUsageSize(renderNode);
}
- @Implementation(minSdk = R)
+ @Implementation(minSdk = R, maxSdk = U.SDK_INT)
protected static int nGetAllocatedSize(long renderNode) {
return RenderNodeNatives.nGetAllocatedSize(renderNode);
}
@@ -47,418 +54,448 @@ public class ShadowNativeRenderNode {
RenderNodeNatives.nRequestPositionUpdates(renderNode, callback);
}
- @Implementation
+ @Implementation(minSdk = TIRAMISU, maxSdk = U.SDK_INT)
+ protected static void nRequestPositionUpdates(
+ long renderNode, WeakReference<PositionUpdateListener> callback) {
+ nRequestPositionUpdates(renderNode, callback.get());
+ }
+
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nAddAnimator(long renderNode, long animatorPtr) {
RenderNodeNatives.nAddAnimator(renderNode, animatorPtr);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nEndAllAnimators(long renderNode) {
RenderNodeNatives.nEndAllAnimators(renderNode);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = TIRAMISU, maxSdk = U.SDK_INT)
+ protected static void nForceEndAnimators(long renderNode) {
+ RenderNodeNatives.nForceEndAnimators(renderNode);
+ }
+
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static void nDiscardDisplayList(long renderNode) {
RenderNodeNatives.nDiscardDisplayList(renderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nIsValid(long renderNode) {
return RenderNodeNatives.nIsValid(renderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nGetTransformMatrix(long renderNode, long nativeMatrix) {
RenderNodeNatives.nGetTransformMatrix(renderNode, nativeMatrix);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nGetInverseTransformMatrix(long renderNode, long nativeMatrix) {
RenderNodeNatives.nGetInverseTransformMatrix(renderNode, nativeMatrix);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nHasIdentityMatrix(long renderNode) {
return RenderNodeNatives.nHasIdentityMatrix(renderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nOffsetTopAndBottom(long renderNode, int offset) {
return RenderNodeNatives.nOffsetTopAndBottom(renderNode, offset);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nOffsetLeftAndRight(long renderNode, int offset) {
return RenderNodeNatives.nOffsetLeftAndRight(renderNode, offset);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nSetLeftTopRightBottom(
long renderNode, int left, int top, int right, int bottom) {
return RenderNodeNatives.nSetLeftTopRightBottom(renderNode, left, top, right, bottom);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nSetLeft(long renderNode, int left) {
return RenderNodeNatives.nSetLeft(renderNode, left);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nSetTop(long renderNode, int top) {
return RenderNodeNatives.nSetTop(renderNode, top);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nSetRight(long renderNode, int right) {
return RenderNodeNatives.nSetRight(renderNode, right);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nSetBottom(long renderNode, int bottom) {
return RenderNodeNatives.nSetBottom(renderNode, bottom);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static int nGetLeft(long renderNode) {
return RenderNodeNatives.nGetLeft(renderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static int nGetTop(long renderNode) {
return RenderNodeNatives.nGetTop(renderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static int nGetRight(long renderNode) {
return RenderNodeNatives.nGetRight(renderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static int nGetBottom(long renderNode) {
return RenderNodeNatives.nGetBottom(renderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nSetCameraDistance(long renderNode, float distance) {
return RenderNodeNatives.nSetCameraDistance(renderNode, distance);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nSetPivotY(long renderNode, float pivotY) {
return RenderNodeNatives.nSetPivotY(renderNode, pivotY);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nSetPivotX(long renderNode, float pivotX) {
return RenderNodeNatives.nSetPivotX(renderNode, pivotX);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nResetPivot(long renderNode) {
return RenderNodeNatives.nResetPivot(renderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nSetLayerType(long renderNode, int layerType) {
return RenderNodeNatives.nSetLayerType(renderNode, layerType);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static int nGetLayerType(long renderNode) {
return RenderNodeNatives.nGetLayerType(renderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nSetLayerPaint(long renderNode, long paint) {
return RenderNodeNatives.nSetLayerPaint(renderNode, paint);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nSetClipToBounds(long renderNode, boolean clipToBounds) {
return RenderNodeNatives.nSetClipToBounds(renderNode, clipToBounds);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nGetClipToBounds(long renderNode) {
return RenderNodeNatives.nGetClipToBounds(renderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nSetClipBounds(
long renderNode, int left, int top, int right, int bottom) {
return RenderNodeNatives.nSetClipBounds(renderNode, left, top, right, bottom);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nSetClipBoundsEmpty(long renderNode) {
return RenderNodeNatives.nSetClipBoundsEmpty(renderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nSetProjectBackwards(long renderNode, boolean shouldProject) {
return RenderNodeNatives.nSetProjectBackwards(renderNode, shouldProject);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nSetProjectionReceiver(long renderNode, boolean shouldReceive) {
return RenderNodeNatives.nSetProjectionReceiver(renderNode, shouldReceive);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nSetOutlineRoundRect(
long renderNode, int left, int top, int right, int bottom, float radius, float alpha) {
return RenderNodeNatives.nSetOutlineRoundRect(
renderNode, left, top, right, bottom, radius, alpha);
}
- @Implementation(minSdk = R)
+ @Implementation(minSdk = R, maxSdk = U.SDK_INT)
protected static boolean nSetOutlinePath(long renderNode, long nativePath, float alpha) {
return RenderNodeNatives.nSetOutlinePath(renderNode, nativePath, alpha);
}
- @Implementation
+ @Implementation(maxSdk = Q)
+ protected static boolean nSetOutlineConvexPath(long renderNode, long nativePath, float alpha) {
+ return nSetOutlinePath(renderNode, nativePath, alpha);
+ }
+
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nSetOutlineEmpty(long renderNode) {
return RenderNodeNatives.nSetOutlineEmpty(renderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nSetOutlineNone(long renderNode) {
return RenderNodeNatives.nSetOutlineNone(renderNode);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static boolean nClearStretch(long renderNode) {
return RenderNodeNatives.nClearStretch(renderNode);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static boolean nStretch(
long renderNode, float vecX, float vecY, float maxStretchX, float maxStretchY) {
return RenderNodeNatives.nStretch(renderNode, vecX, vecY, maxStretchX, maxStretchY);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nHasShadow(long renderNode) {
return RenderNodeNatives.nHasShadow(renderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nSetSpotShadowColor(long renderNode, int color) {
return RenderNodeNatives.nSetSpotShadowColor(renderNode, color);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nSetAmbientShadowColor(long renderNode, int color) {
return RenderNodeNatives.nSetAmbientShadowColor(renderNode, color);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static int nGetSpotShadowColor(long renderNode) {
return RenderNodeNatives.nGetSpotShadowColor(renderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static int nGetAmbientShadowColor(long renderNode) {
return RenderNodeNatives.nGetAmbientShadowColor(renderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nSetClipToOutline(long renderNode, boolean clipToOutline) {
return RenderNodeNatives.nSetClipToOutline(renderNode, clipToOutline);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nSetRevealClip(
long renderNode, boolean shouldClip, float x, float y, float radius) {
return RenderNodeNatives.nSetRevealClip(renderNode, shouldClip, x, y, radius);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nSetAlpha(long renderNode, float alpha) {
return RenderNodeNatives.nSetAlpha(renderNode, alpha);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static boolean nSetRenderEffect(long renderNode, long renderEffect) {
return RenderNodeNatives.nSetRenderEffect(renderNode, renderEffect);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nSetHasOverlappingRendering(
long renderNode, boolean hasOverlappingRendering) {
return RenderNodeNatives.nSetHasOverlappingRendering(renderNode, hasOverlappingRendering);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nSetUsageHint(long renderNode, int usageHint) {
RenderNodeNatives.nSetUsageHint(renderNode, usageHint);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nSetElevation(long renderNode, float lift) {
return RenderNodeNatives.nSetElevation(renderNode, lift);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nSetTranslationX(long renderNode, float translationX) {
return RenderNodeNatives.nSetTranslationX(renderNode, translationX);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nSetTranslationY(long renderNode, float translationY) {
return RenderNodeNatives.nSetTranslationY(renderNode, translationY);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nSetTranslationZ(long renderNode, float translationZ) {
return RenderNodeNatives.nSetTranslationZ(renderNode, translationZ);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nSetRotation(long renderNode, float rotation) {
return RenderNodeNatives.nSetRotation(renderNode, rotation);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nSetRotationX(long renderNode, float rotationX) {
return RenderNodeNatives.nSetRotationX(renderNode, rotationX);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nSetRotationY(long renderNode, float rotationY) {
return RenderNodeNatives.nSetRotationY(renderNode, rotationY);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nSetScaleX(long renderNode, float scaleX) {
return RenderNodeNatives.nSetScaleX(renderNode, scaleX);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nSetScaleY(long renderNode, float scaleY) {
return RenderNodeNatives.nSetScaleY(renderNode, scaleY);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nSetStaticMatrix(long renderNode, long nativeMatrix) {
return RenderNodeNatives.nSetStaticMatrix(renderNode, nativeMatrix);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nSetAnimationMatrix(long renderNode, long animationMatrix) {
return RenderNodeNatives.nSetAnimationMatrix(renderNode, animationMatrix);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nHasOverlappingRendering(long renderNode) {
return RenderNodeNatives.nHasOverlappingRendering(renderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nGetAnimationMatrix(long renderNode, long animationMatrix) {
return RenderNodeNatives.nGetAnimationMatrix(renderNode, animationMatrix);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nGetClipToOutline(long renderNode) {
return RenderNodeNatives.nGetClipToOutline(renderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static float nGetAlpha(long renderNode) {
return RenderNodeNatives.nGetAlpha(renderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static float nGetCameraDistance(long renderNode) {
return RenderNodeNatives.nGetCameraDistance(renderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static float nGetScaleX(long renderNode) {
return RenderNodeNatives.nGetScaleX(renderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static float nGetScaleY(long renderNode) {
return RenderNodeNatives.nGetScaleY(renderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static float nGetElevation(long renderNode) {
return RenderNodeNatives.nGetElevation(renderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static float nGetTranslationX(long renderNode) {
return RenderNodeNatives.nGetTranslationX(renderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static float nGetTranslationY(long renderNode) {
return RenderNodeNatives.nGetTranslationY(renderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static float nGetTranslationZ(long renderNode) {
return RenderNodeNatives.nGetTranslationZ(renderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static float nGetRotation(long renderNode) {
return RenderNodeNatives.nGetRotation(renderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static float nGetRotationX(long renderNode) {
return RenderNodeNatives.nGetRotationX(renderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static float nGetRotationY(long renderNode) {
return RenderNodeNatives.nGetRotationY(renderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nIsPivotExplicitlySet(long renderNode) {
return RenderNodeNatives.nIsPivotExplicitlySet(renderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static float nGetPivotX(long renderNode) {
return RenderNodeNatives.nGetPivotX(renderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static float nGetPivotY(long renderNode) {
return RenderNodeNatives.nGetPivotY(renderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static int nGetWidth(long renderNode) {
return RenderNodeNatives.nGetWidth(renderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static int nGetHeight(long renderNode) {
return RenderNodeNatives.nGetHeight(renderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nSetAllowForceDark(long renderNode, boolean allowForceDark) {
return RenderNodeNatives.nSetAllowForceDark(renderNode, allowForceDark);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nGetAllowForceDark(long renderNode) {
return RenderNodeNatives.nGetAllowForceDark(renderNode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long nGetUniqueId(long renderNode) {
return RenderNodeNatives.nGetUniqueId(renderNode);
}
+ @Implementation(minSdk = Q, maxSdk = R)
+ protected static void nSetDisplayList(long renderNode, long newData) {
+ // No-op
+ // In Q and R, ending recording was a two-part operation, one part is calling
+ // RecordingCanvas.finishRecording (which returned a long displayList),
+ // and then calling RenderNode.nSetDisplayList with that result. However, in S, these
+ // were combined into one, and all that is needed is to call RecordingCanvas.finishRecording.
+ }
+
+ @Implementation(minSdk = U.SDK_INT, maxSdk = U.SDK_INT)
+ protected static void nSetIsTextureView(long renderNode) {
+ // no-op
+ }
+
/** Shadow picker for {@link RenderNode}. */
public static final class Picker extends GraphicsShadowPicker<Object> {
public Picker() {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderNodeAnimator.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderNodeAnimator.java
index 5b3c32a1c..a8cf0f60a 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderNodeAnimator.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderNodeAnimator.java
@@ -8,27 +8,29 @@ import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.nativeruntime.RenderNodeAnimatorNatives;
import org.robolectric.shadows.ShadowNativeRenderNodeAnimator.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link RenderNodeAnimator} that is backed by native code */
@Implements(
value = RenderNodeAnimator.class,
minSdk = R,
shadowPicker = Picker.class,
- isInAndroidSdk = false)
+ isInAndroidSdk = false,
+ callNativeMethodsByDefault = true)
public class ShadowNativeRenderNodeAnimator {
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long nCreateAnimator(int property, float finalValue) {
DefaultNativeRuntimeLoader.injectAndLoad();
return RenderNodeAnimatorNatives.nCreateAnimator(property, finalValue);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long nCreateCanvasPropertyFloatAnimator(long canvasProperty, float finalValue) {
DefaultNativeRuntimeLoader.injectAndLoad();
return RenderNodeAnimatorNatives.nCreateCanvasPropertyFloatAnimator(canvasProperty, finalValue);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long nCreateCanvasPropertyPaintAnimator(
long canvasProperty, int paintField, float finalValue) {
DefaultNativeRuntimeLoader.injectAndLoad();
@@ -36,53 +38,53 @@ public class ShadowNativeRenderNodeAnimator {
canvasProperty, paintField, finalValue);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long nCreateRevealAnimator(int x, int y, float startRadius, float endRadius) {
DefaultNativeRuntimeLoader.injectAndLoad();
return RenderNodeAnimatorNatives.nCreateRevealAnimator(x, y, startRadius, endRadius);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nSetStartValue(long nativePtr, float startValue) {
RenderNodeAnimatorNatives.nSetStartValue(nativePtr, startValue);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nSetDuration(long nativePtr, long duration) {
RenderNodeAnimatorNatives.nSetDuration(nativePtr, duration);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long nGetDuration(long nativePtr) {
return RenderNodeAnimatorNatives.nGetDuration(nativePtr);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nSetStartDelay(long nativePtr, long startDelay) {
RenderNodeAnimatorNatives.nSetStartDelay(nativePtr, startDelay);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nSetInterpolator(long animPtr, long interpolatorPtr) {
RenderNodeAnimatorNatives.nSetInterpolator(animPtr, interpolatorPtr);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nSetAllowRunningAsync(long animPtr, boolean mayRunAsync) {
RenderNodeAnimatorNatives.nSetAllowRunningAsync(animPtr, mayRunAsync);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nSetListener(long animPtr, RenderNodeAnimator listener) {
RenderNodeAnimatorNatives.nSetListener(animPtr, listener);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nStart(long animPtr) {
RenderNodeAnimatorNatives.nStart(animPtr);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nEnd(long animPtr) {
RenderNodeAnimatorNatives.nEnd(animPtr);
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRuntimeShader.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRuntimeShader.java
index 9793d1d68..c048f62c4 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRuntimeShader.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRuntimeShader.java
@@ -15,9 +15,14 @@ import org.robolectric.nativeruntime.RuntimeShaderNatives;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowNativeRuntimeShader.Picker;
import org.robolectric.util.ReflectionHelpers.ClassParameter;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link RuntimeShader} that is backed by native code */
-@Implements(value = RuntimeShader.class, minSdk = O, shadowPicker = Picker.class)
+@Implements(
+ value = RuntimeShader.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ callNativeMethodsByDefault = true)
public class ShadowNativeRuntimeShader {
@RealObject RuntimeShader runtimeShader;
@@ -127,7 +132,7 @@ public class ShadowNativeRuntimeShader {
private static final String RIPPLE_SHADER_31 =
RIPPLE_SHADER_UNIFORMS_31 + RIPPLE_SHADER_LIB_31 + RIPPLE_SHADER_MAIN_31;
- @Implementation(minSdk = TIRAMISU)
+ @Implementation(minSdk = TIRAMISU, maxSdk = U.SDK_INT)
protected void __constructor__(String sksl) {
// This is a workaround for supporting RippleShader from T+ with the native code from S.
// There were some new capabilities added to SKSL in T which are not available in S. Use the
@@ -144,12 +149,12 @@ public class ShadowNativeRuntimeShader {
RuntimeShader.class, runtimeShader, ClassParameter.from(String.class, sksl));
}
- @Implementation(minSdk = R)
+ @Implementation(minSdk = R, maxSdk = U.SDK_INT)
protected static long nativeGetFinalizer() {
return RuntimeShaderNatives.nativeGetFinalizer();
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static long nativeCreateBuilder(String sksl) {
DefaultNativeRuntimeLoader.injectAndLoad();
return RuntimeShaderNatives.nativeCreateBuilder(sksl);
@@ -160,13 +165,54 @@ public class ShadowNativeRuntimeShader {
return RuntimeShaderNatives.nativeCreateShader(shaderBuilder, matrix, isOpaque);
}
+ @Implementation(minSdk = TIRAMISU, maxSdk = U.SDK_INT)
+ protected static long nativeCreateShader(long shaderBuilder, long matrix) {
+ return nativeCreateShader(shaderBuilder, matrix, false);
+ }
+
@Implementation(minSdk = S, maxSdk = S_V2)
protected static void nativeUpdateUniforms(
long shaderBuilder, String uniformName, float[] uniforms) {
RuntimeShaderNatives.nativeUpdateUniforms(shaderBuilder, uniformName, uniforms);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = TIRAMISU, maxSdk = U.SDK_INT)
+ protected static void nativeUpdateUniforms(
+ long shaderBuilder, String uniformName, float[] uniforms, boolean isColor) {
+ nativeUpdateUniforms(shaderBuilder, uniformName, uniforms);
+ }
+
+ @Implementation(minSdk = TIRAMISU, maxSdk = U.SDK_INT)
+ protected static void nativeUpdateUniforms(
+ long shaderBuilder,
+ String uniformName,
+ float value1,
+ float value2,
+ float value3,
+ float value4,
+ int count) {
+ // no-op
+ }
+
+ @Implementation(minSdk = TIRAMISU, maxSdk = U.SDK_INT)
+ protected static void nativeUpdateUniforms(
+ long shaderBuilder, String uniformName, int[] uniforms) {
+ // no-op
+ }
+
+ @Implementation(minSdk = TIRAMISU, maxSdk = U.SDK_INT)
+ protected static void nativeUpdateUniforms(
+ long shaderBuilder,
+ String uniformName,
+ int value1,
+ int value2,
+ int value3,
+ int value4,
+ int count) {
+ // no-op
+ }
+
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static void nativeUpdateShader(long shaderBuilder, String shaderName, long shader) {
RuntimeShaderNatives.nativeUpdateShader(shaderBuilder, shaderName, shader);
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSQLiteConnection.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSQLiteConnection.java
index 8c3b6de40..5f26774c3 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSQLiteConnection.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSQLiteConnection.java
@@ -16,9 +16,14 @@ import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.nativeruntime.SQLiteConnectionNatives;
import org.robolectric.util.PerfStatsCollector;
+import org.robolectric.versioning.AndroidVersions.T;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link SQLiteConnection} that is backed by native code */
-@Implements(className = "android.database.sqlite.SQLiteConnection", isInAndroidSdk = false)
+@Implements(
+ className = "android.database.sqlite.SQLiteConnection",
+ isInAndroidSdk = false,
+ callNativeMethodsByDefault = true)
public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection {
@Implementation(maxSdk = O)
protected static Number nativeOpen(
@@ -30,7 +35,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection {
return result;
}
- @Implementation(minSdk = O_MR1)
+ @Implementation(minSdk = O_MR1, maxSdk = U.SDK_INT)
protected static long nativeOpen(
String path,
int openFlags,
@@ -59,7 +64,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection {
nativeClose(PreLPointers.get(connectionPtr));
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static void nativeClose(long connectionPtr) {
PerfStatsCollector.getInstance()
.measure("androidsqlite", () -> SQLiteConnectionNatives.nativeClose(connectionPtr));
@@ -71,7 +76,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection {
return PreLPointers.register(statementPtr);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static long nativePrepareStatement(long connectionPtr, String sql) {
return PerfStatsCollector.getInstance()
.measure(
@@ -84,7 +89,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection {
nativeFinalizeStatement(PreLPointers.get(connectionPtr), PreLPointers.get(statementPtr));
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static void nativeFinalizeStatement(long connectionPtr, long statementPtr) {
PerfStatsCollector.getInstance()
.measure(
@@ -97,7 +102,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection {
return nativeGetParameterCount(PreLPointers.get(connectionPtr), PreLPointers.get(statementPtr));
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static int nativeGetParameterCount(final long connectionPtr, final long statementPtr) {
return PerfStatsCollector.getInstance()
.measure(
@@ -110,7 +115,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection {
return nativeIsReadOnly(PreLPointers.get(connectionPtr), PreLPointers.get(statementPtr));
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static boolean nativeIsReadOnly(final long connectionPtr, final long statementPtr) {
return PerfStatsCollector.getInstance()
.measure(
@@ -123,7 +128,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection {
return nativeExecuteForString(PreLPointers.get(connectionPtr), PreLPointers.get(statementPtr));
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static String nativeExecuteForString(
final long connectionPtr, final long statementPtr) {
return PerfStatsCollector.getInstance()
@@ -137,7 +142,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection {
nativeRegisterLocalizedCollators(PreLPointers.get(connectionPtr), locale);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static void nativeRegisterLocalizedCollators(long connectionPtr, String locale) {
PerfStatsCollector.getInstance()
.measure(
@@ -150,7 +155,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection {
return nativeExecuteForLong(PreLPointers.get(connectionPtr), PreLPointers.get(statementPtr));
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static long nativeExecuteForLong(final long connectionPtr, final long statementPtr) {
return PerfStatsCollector.getInstance()
.measure(
@@ -171,7 +176,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection {
() -> SQLiteConnectionNatives.nativeExecute(connectionPtr, statementPtr, false));
}
- @Implementation(minSdk = 33)
+ @Implementation(minSdk = T.SDK_INT, maxSdk = U.SDK_INT)
protected static void nativeExecute(
final long connectionPtr, final long statementPtr, boolean isPragmaStmt) {
PerfStatsCollector.getInstance()
@@ -186,7 +191,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection {
PreLPointers.get(connectionPtr), PreLPointers.get(statementPtr));
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static int nativeExecuteForChangedRowCount(
final long connectionPtr, final long statementPtr) {
return PerfStatsCollector.getInstance()
@@ -202,7 +207,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection {
return nativeGetColumnCount(PreLPointers.get(connectionPtr), PreLPointers.get(statementPtr));
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static int nativeGetColumnCount(final long connectionPtr, final long statementPtr) {
return PerfStatsCollector.getInstance()
.measure(
@@ -216,7 +221,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection {
PreLPointers.get(connectionPtr), PreLPointers.get(statementPtr), index);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static String nativeGetColumnName(
final long connectionPtr, final long statementPtr, final int index) {
return PerfStatsCollector.getInstance()
@@ -230,7 +235,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection {
nativeBindNull(PreLPointers.get(connectionPtr), PreLPointers.get(statementPtr), index);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static void nativeBindNull(
final long connectionPtr, final long statementPtr, final int index) {
PerfStatsCollector.getInstance()
@@ -244,7 +249,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection {
nativeBindLong(PreLPointers.get(connectionPtr), PreLPointers.get(statementPtr), index, value);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static void nativeBindLong(
final long connectionPtr, final long statementPtr, final int index, final long value) {
PerfStatsCollector.getInstance()
@@ -260,7 +265,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection {
nativeBindDouble(PreLPointers.get(connectionPtr), PreLPointers.get(statementPtr), index, value);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static void nativeBindDouble(
final long connectionPtr, final long statementPtr, final int index, final double value) {
PerfStatsCollector.getInstance()
@@ -277,7 +282,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection {
nativeBindString(PreLPointers.get(connectionPtr), PreLPointers.get(statementPtr), index, value);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static void nativeBindString(
final long connectionPtr, final long statementPtr, final int index, final String value) {
PerfStatsCollector.getInstance()
@@ -294,7 +299,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection {
nativeBindBlob(PreLPointers.get(connectionPtr), PreLPointers.get(statementPtr), index, value);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static void nativeBindBlob(
final long connectionPtr, final long statementPtr, final int index, final byte[] value) {
PerfStatsCollector.getInstance()
@@ -310,7 +315,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection {
PreLPointers.get(connectionPtr), PreLPointers.get(statementPtr));
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static void nativeResetStatementAndClearBindings(
final long connectionPtr, final long statementPtr) {
PerfStatsCollector.getInstance()
@@ -327,7 +332,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection {
PreLPointers.get(connectionPtr), PreLPointers.get(statementPtr));
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static long nativeExecuteForLastInsertedRowId(
final long connectionPtr, final long statementPtr) {
return PerfStatsCollector.getInstance()
@@ -355,7 +360,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection {
countAllRows);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static long nativeExecuteForCursorWindow(
final long connectionPtr,
final long statementPtr,
@@ -377,7 +382,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection {
PreLPointers.get(connectionPtr), PreLPointers.get(statementPtr));
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static int nativeExecuteForBlobFileDescriptor(
final long connectionPtr, final long statementPtr) {
return PerfStatsCollector.getInstance()
@@ -393,7 +398,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection {
nativeCancel(PreLPointers.get(connectionPtr));
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static void nativeCancel(long connectionPtr) {
PerfStatsCollector.getInstance()
.measure("androidsqlite", () -> SQLiteConnectionNatives.nativeCancel(connectionPtr));
@@ -404,7 +409,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection {
nativeResetCancel(PreLPointers.get(connectionPtr), cancelable);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static void nativeResetCancel(long connectionPtr, boolean cancelable) {
PerfStatsCollector.getInstance()
.measure(
@@ -412,7 +417,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection {
() -> SQLiteConnectionNatives.nativeResetCancel(connectionPtr, cancelable));
}
- @Implementation(minSdk = R)
+ @Implementation(minSdk = R, maxSdk = U.SDK_INT)
@SuppressWarnings("AndroidJdkLibsChecker")
protected static void nativeRegisterCustomScalarFunction(
long connectionPtr, String name, UnaryOperator<String> function) {
@@ -424,7 +429,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection {
connectionPtr, name, function));
}
- @Implementation(minSdk = R)
+ @Implementation(minSdk = R, maxSdk = U.SDK_INT)
@SuppressWarnings("AndroidJdkLibsChecker")
protected static void nativeRegisterCustomAggregateFunction(
long connectionPtr, String name, BinaryOperator<String> function) {
@@ -441,7 +446,7 @@ public class ShadowNativeSQLiteConnection extends ShadowSQLiteConnection {
return nativeGetDbLookaside(PreLPointers.get(connectionPtr));
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static int nativeGetDbLookaside(long connectionPtr) {
return PerfStatsCollector.getInstance()
.measure(
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeShader.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeShader.java
index 1a216f8ab..4b71f0d19 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeShader.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeShader.java
@@ -8,12 +8,17 @@ import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.nativeruntime.ShaderNatives;
import org.robolectric.shadows.ShadowNativeShader.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link Shader} that is backed by native code */
-@Implements(value = Shader.class, minSdk = O, shadowPicker = Picker.class)
+@Implements(
+ value = Shader.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ callNativeMethodsByDefault = true)
public class ShadowNativeShader {
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nativeGetFinalizer() {
DefaultNativeRuntimeLoader.injectAndLoad();
return ShaderNatives.nativeGetFinalizer();
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSumPathEffect.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSumPathEffect.java
index 2d436ae23..1c21f918e 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSumPathEffect.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSumPathEffect.java
@@ -8,12 +8,17 @@ import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.nativeruntime.SumPathEffectNatives;
import org.robolectric.shadows.ShadowNativeSumPathEffect.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link SumPathEffect} that is backed by native code */
-@Implements(value = SumPathEffect.class, minSdk = O, shadowPicker = Picker.class)
+@Implements(
+ value = SumPathEffect.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ callNativeMethodsByDefault = true)
public class ShadowNativeSumPathEffect {
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nativeCreate(long first, long second) {
DefaultNativeRuntimeLoader.injectAndLoad();
return SumPathEffectNatives.nativeCreate(first, second);
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSurface.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSurface.java
index 97556fcff..3ccae4a45 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSurface.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSurface.java
@@ -2,6 +2,7 @@ package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.O_MR1;
+import static android.os.Build.VERSION_CODES.P;
import static android.os.Build.VERSION_CODES.Q;
import static android.os.Build.VERSION_CODES.S;
@@ -17,31 +18,42 @@ import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.nativeruntime.SurfaceNatives;
import org.robolectric.shadows.ShadowNativeSurface.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link Surface} that is backed by native code */
-@Implements(value = Surface.class, minSdk = O, shadowPicker = Picker.class, isInAndroidSdk = false)
+@Implements(
+ value = Surface.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ isInAndroidSdk = false,
+ callNativeMethodsByDefault = true)
public class ShadowNativeSurface {
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long nativeCreateFromSurfaceTexture(SurfaceTexture surfaceTexture)
throws OutOfResourcesException {
DefaultNativeRuntimeLoader.injectAndLoad();
return SurfaceNatives.nativeCreateFromSurfaceTexture(surfaceTexture);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long nativeCreateFromSurfaceControl(long surfaceControlNativeObject) {
DefaultNativeRuntimeLoader.injectAndLoad();
return SurfaceNatives.nativeCreateFromSurfaceControl(surfaceControlNativeObject);
}
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static long nativeGetFromSurfaceControl(
long surfaceObject, long surfaceControlNativeObject) {
DefaultNativeRuntimeLoader.injectAndLoad();
return SurfaceNatives.nativeGetFromSurfaceControl(surfaceObject, surfaceControlNativeObject);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = P, maxSdk = P)
+ protected static long nativeGetFromSurfaceControl(long surfaceControlNativeObject) {
+ return nativeGetFromSurfaceControl(0, surfaceControlNativeObject);
+ }
+
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static long nativeGetFromBlastBufferQueue(
long surfaceObject, long blastBufferQueueNativeObject) {
return SurfaceNatives.nativeGetFromBlastBufferQueue(
@@ -56,84 +68,84 @@ public class ShadowNativeSurface {
throw new UnsupportedOperationException("Not implemented yet");
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nativeUnlockCanvasAndPost(long nativeObject, Canvas canvas) {
SurfaceNatives.nativeUnlockCanvasAndPost(nativeObject, canvas);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nativeRelease(long nativeObject) {
SurfaceNatives.nativeRelease(nativeObject);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nativeIsValid(long nativeObject) {
return SurfaceNatives.nativeIsValid(nativeObject);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static boolean nativeIsConsumerRunningBehind(long nativeObject) {
return SurfaceNatives.nativeIsConsumerRunningBehind(nativeObject);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long nativeReadFromParcel(long nativeObject, Parcel source) {
return SurfaceNatives.nativeReadFromParcel(nativeObject, source);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nativeWriteToParcel(long nativeObject, Parcel dest) {
SurfaceNatives.nativeWriteToParcel(nativeObject, dest);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static void nativeAllocateBuffers(long nativeObject) {
SurfaceNatives.nativeAllocateBuffers(nativeObject);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static int nativeGetWidth(long nativeObject) {
return SurfaceNatives.nativeGetWidth(nativeObject);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static int nativeGetHeight(long nativeObject) {
return SurfaceNatives.nativeGetHeight(nativeObject);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long nativeGetNextFrameNumber(long nativeObject) {
return SurfaceNatives.nativeGetNextFrameNumber(nativeObject);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static int nativeSetScalingMode(long nativeObject, int scalingMode) {
return SurfaceNatives.nativeSetScalingMode(nativeObject, scalingMode);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static int nativeForceScopedDisconnect(long nativeObject) {
return SurfaceNatives.nativeForceScopedDisconnect(nativeObject);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static int nativeAttachAndQueueBufferWithColorSpace(
long nativeObject, HardwareBuffer buffer, int colorSpaceId) {
return SurfaceNatives.nativeAttachAndQueueBufferWithColorSpace(
nativeObject, buffer, colorSpaceId);
}
- @Implementation(minSdk = O_MR1)
+ @Implementation(minSdk = O_MR1, maxSdk = U.SDK_INT)
protected static int nativeSetSharedBufferModeEnabled(long nativeObject, boolean enabled) {
return SurfaceNatives.nativeSetSharedBufferModeEnabled(nativeObject, enabled);
}
- @Implementation(minSdk = O_MR1)
+ @Implementation(minSdk = O_MR1, maxSdk = U.SDK_INT)
protected static int nativeSetAutoRefreshEnabled(long nativeObject, boolean enabled) {
return SurfaceNatives.nativeSetAutoRefreshEnabled(nativeObject, enabled);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static int nativeSetFrameRate(
long nativeObject, float frameRate, int compatibility, int changeFrameRateStrategy) {
return SurfaceNatives.nativeSetFrameRate(
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSweepGradient.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSweepGradient.java
index d51300f1c..158a1a198 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSweepGradient.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSweepGradient.java
@@ -10,12 +10,17 @@ import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.nativeruntime.SweepGradientNatives;
import org.robolectric.shadows.ShadowNativeSweepGradient.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link SweepGradient} that is backed by native code */
-@Implements(value = SweepGradient.class, minSdk = O, shadowPicker = Picker.class)
+@Implements(
+ value = SweepGradient.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ callNativeMethodsByDefault = true)
public class ShadowNativeSweepGradient {
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static long nativeCreate(
long matrix, float x, float y, long[] colors, float[] positions, long colorSpaceHandle) {
DefaultNativeRuntimeLoader.injectAndLoad();
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSystemFonts.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSystemFonts.java
index f154df3f0..4d53bf268 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSystemFonts.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSystemFonts.java
@@ -25,6 +25,7 @@ import java.util.ArrayList;
import java.util.Map;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.shadows.ShadowNativeSystemFonts.Picker;
import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;
@@ -52,7 +53,8 @@ public class ShadowNativeSystemFonts {
String fontDir = System.getProperty("robolectric.nativeruntime.fontdir");
Preconditions.checkNotNull(fontDir);
Preconditions.checkState(new File(fontDir).isDirectory(), "Missing fonts directory");
- Preconditions.checkState(fontDir.endsWith("/"), "Fonts directory must end with a slash");
+ Preconditions.checkState(
+ fontDir.endsWith(File.separator), "Fonts directory must end with a slash");
return reflector(SystemFontsReflector.class)
.getSystemFontConfigInternal(
fontDir + "fonts.xml",
@@ -71,10 +73,14 @@ public class ShadowNativeSystemFonts {
FontCustomizationParser.Result oemCustomization,
ArrayMap<String, FontFamily[]> fallbackMap,
ArrayList<Font> availableFonts) {
+ // In Q and R, calling SystemFonts.getAvailableFonts does not automatically result in RNG being
+ // loaded, so we must ensure it is loaded for `robolectric.nativeruntime.fontdir` to be defined.
+ DefaultNativeRuntimeLoader.injectAndLoad();
String fontDir = System.getProperty("robolectric.nativeruntime.fontdir");
Preconditions.checkNotNull(fontDir);
Preconditions.checkState(new File(fontDir).isDirectory(), "Missing fonts directory");
- Preconditions.checkState(fontDir.endsWith("/"), "Fonts directory must end with a slash");
+ Preconditions.checkState(
+ fontDir.endsWith(File.separator), "Fonts directory must end with a slash");
return reflector(SystemFontsReflector.class)
.buildSystemFallback(
fontDir + "fonts.xml", fontDir, oemCustomization, fallbackMap, availableFonts);
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeTableMaskFilter.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeTableMaskFilter.java
index 7d7c0a34c..78b297557 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeTableMaskFilter.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeTableMaskFilter.java
@@ -8,28 +8,30 @@ import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.nativeruntime.TableMaskFilterNatives;
import org.robolectric.shadows.ShadowNativeTableMaskFilter.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link TableMaskFilter} that is backed by native code */
@Implements(
value = TableMaskFilter.class,
minSdk = O,
shadowPicker = Picker.class,
- isInAndroidSdk = false)
+ isInAndroidSdk = false,
+ callNativeMethodsByDefault = true)
public class ShadowNativeTableMaskFilter {
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nativeNewTable(byte[] table) {
DefaultNativeRuntimeLoader.injectAndLoad();
return TableMaskFilterNatives.nativeNewTable(table);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nativeNewClip(int min, int max) {
DefaultNativeRuntimeLoader.injectAndLoad();
return TableMaskFilterNatives.nativeNewClip(min, max);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nativeNewGamma(float gamma) {
DefaultNativeRuntimeLoader.injectAndLoad();
return TableMaskFilterNatives.nativeNewGamma(gamma);
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeTextRunShaper.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeTextRunShaper.java
index aaa8cb059..f6aed6be2 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeTextRunShaper.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeTextRunShaper.java
@@ -7,12 +7,17 @@ import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.TextRunShaperNatives;
import org.robolectric.shadows.ShadowNativeTextRunShaper.Picker;
import org.robolectric.versioning.AndroidVersions.S;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link TextRunShaper} that is backed by native code */
-@Implements(value = TextRunShaper.class, minSdk = S.SDK_INT, shadowPicker = Picker.class)
+@Implements(
+ value = TextRunShaper.class,
+ minSdk = S.SDK_INT,
+ shadowPicker = Picker.class,
+ callNativeMethodsByDefault = true)
public class ShadowNativeTextRunShaper {
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long nativeShapeTextRun(
char[] text,
int start,
@@ -25,7 +30,7 @@ public class ShadowNativeTextRunShaper {
text, start, count, contextStart, contextCount, isRtl, nativePaint);
}
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT)
protected static long nativeShapeTextRun(
String text,
int start,
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeTypeface.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeTypeface.java
index a0b5d2760..897ea85d8 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeTypeface.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeTypeface.java
@@ -25,6 +25,7 @@ import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.List;
import java.util.Map;
+import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
@@ -36,7 +37,12 @@ import org.robolectric.util.reflector.Static;
import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link Typeface} that is backed by native code */
-@Implements(value = Typeface.class, looseSignatures = true, minSdk = O, isInAndroidSdk = false)
+@Implements(
+ value = Typeface.class,
+ looseSignatures = true,
+ minSdk = O,
+ isInAndroidSdk = false,
+ callNativeMethodsByDefault = true)
public class ShadowNativeTypeface extends ShadowTypeface {
private static final String TAG = "ShadowNativeTypeface";
@@ -45,12 +51,17 @@ public class ShadowNativeTypeface extends ShadowTypeface {
private static final int STYLE_NORMAL = 0;
private static final int STYLE_ITALIC = 1;
+
@Implementation(minSdk = S)
protected static void __staticInitializer__() {
- Shadow.directInitialize(Typeface.class);
- // Initialize the system font map. In real Android this is done as part of Application startup
- // and uses a more complex SharedMemory system not supported in Robolectric.
- Typeface.loadPreinstalledSystemFontMap();
+ if (RuntimeEnvironment.getApiLevel() <= U.SDK_INT) {
+ Shadow.directInitialize(Typeface.class);
+ // Initialize the system font map. In real Android this is done as part of Application startup
+ // and uses a more complex SharedMemory system not supported in Robolectric.
+ Typeface.loadPreinstalledSystemFontMap();
+ }
+ // The Typeface static initializer invokes its own native methods. This has to be deferred
+ // starting in Android V.
}
@Implementation(minSdk = P, maxSdk = P)
@@ -62,7 +73,8 @@ public class ShadowNativeTypeface extends ShadowTypeface {
String fontDir = System.getProperty("robolectric.nativeruntime.fontdir");
Preconditions.checkNotNull(fontDir);
Preconditions.checkState(new File(fontDir).isDirectory(), "Missing fonts directory");
- Preconditions.checkState(fontDir.endsWith("/"), "Fonts directory must end with a slash");
+ Preconditions.checkState(
+ fontDir.endsWith(File.separator), "Fonts directory must end with a slash");
reflector(TypefaceReflector.class)
.buildSystemFallback(fontDir + "fonts.xml", fontDir, fontMap, fallbackMap);
}
@@ -75,7 +87,8 @@ public class ShadowNativeTypeface extends ShadowTypeface {
String fontDir = System.getProperty("robolectric.nativeruntime.fontdir");
Preconditions.checkNotNull(fontDir);
Preconditions.checkState(new File(fontDir).isDirectory(), "Missing fonts directory");
- Preconditions.checkState(fontDir.endsWith("/"), "Fonts directory must end with a slash");
+ Preconditions.checkState(
+ fontDir.endsWith(File.separator), "Fonts directory must end with a slash");
return new File(fontDir);
}
@@ -124,24 +137,24 @@ public class ShadowNativeTypeface extends ShadowTypeface {
return fontFamily;
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static long nativeCreateFromTypeface(long nativeInstance, int style) {
return TypefaceNatives.nativeCreateFromTypeface(nativeInstance, style);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nativeCreateFromTypefaceWithExactStyle(
long nativeInstance, int weight, boolean italic) {
return TypefaceNatives.nativeCreateFromTypefaceWithExactStyle(nativeInstance, weight, italic);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nativeCreateFromTypefaceWithVariation(
long nativeInstance, List<FontVariationAxis> axes) {
return TypefaceNatives.nativeCreateFromTypefaceWithVariation(nativeInstance, axes);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static long nativeCreateWeightAlias(long nativeInstance, int weight) {
return TypefaceNatives.nativeCreateWeightAlias(nativeInstance, weight);
}
@@ -151,33 +164,33 @@ public class ShadowNativeTypeface extends ShadowTypeface {
return TypefaceNatives.nativeCreateFromArray(familyArray, 0, weight, italic);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static long nativeCreateFromArray(
long[] familyArray, long fallbackTypeface, int weight, int italic) {
return TypefaceNatives.nativeCreateFromArray(familyArray, fallbackTypeface, weight, italic);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static int[] nativeGetSupportedAxes(long nativeInstance) {
return TypefaceNatives.nativeGetSupportedAxes(nativeInstance);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static void nativeSetDefault(long nativePtr) {
TypefaceNatives.nativeSetDefault(nativePtr);
}
- @Implementation(minSdk = LOLLIPOP)
+ @Implementation(minSdk = LOLLIPOP, maxSdk = U.SDK_INT)
protected static int nativeGetStyle(long nativePtr) {
return TypefaceNatives.nativeGetStyle(nativePtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static int nativeGetWeight(long nativePtr) {
return TypefaceNatives.nativeGetWeight(nativePtr);
}
- @Implementation(minSdk = P)
+ @Implementation(minSdk = P, maxSdk = U.SDK_INT)
protected static long nativeGetReleaseFunc() {
DefaultNativeRuntimeLoader.injectAndLoad();
return TypefaceNatives.nativeGetReleaseFunc();
@@ -193,7 +206,7 @@ public class ShadowNativeTypeface extends ShadowTypeface {
return TypefaceNatives.nativeGetFamily(nativePtr, index);
}
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static void nativeRegisterGenericFamily(String str, long nativePtr) {
TypefaceNatives.nativeRegisterGenericFamily(str, nativePtr);
}
@@ -203,7 +216,7 @@ public class ShadowNativeTypeface extends ShadowTypeface {
return TypefaceNatives.nativeWriteTypefaces(buffer, nativePtrs);
}
- @Implementation(minSdk = U.SDK_INT)
+ @Implementation(minSdk = U.SDK_INT, maxSdk = U.SDK_INT)
protected static int nativeWriteTypefaces(ByteBuffer buffer, int position, long[] nativePtrs) {
return nativeWriteTypefaces(buffer, nativePtrs);
}
@@ -213,21 +226,26 @@ public class ShadowNativeTypeface extends ShadowTypeface {
return TypefaceNatives.nativeReadTypefaces(buffer);
}
- @Implementation(minSdk = U.SDK_INT)
+ @Implementation(minSdk = U.SDK_INT, maxSdk = U.SDK_INT)
protected static long[] nativeReadTypefaces(ByteBuffer buffer, int position) {
return nativeReadTypefaces(buffer);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static void nativeForceSetStaticFinalField(String fieldName, Typeface typeface) {
TypefaceNatives.nativeForceSetStaticFinalField(fieldName, typeface);
}
- @Implementation(minSdk = S)
+ @Implementation(minSdk = S, maxSdk = U.SDK_INT)
protected static void nativeAddFontCollections(long nativePtr) {
TypefaceNatives.nativeAddFontCollections(nativePtr);
}
+ @Implementation(minSdk = U.SDK_INT, maxSdk = U.SDK_INT)
+ protected static void nativeRegisterLocaleList(String locales) {
+ // no-op
+ }
+
static void ensureInitialized() {
try {
// Forces static initialization. This should be called before any native code that calls
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeVectorDrawable.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeVectorDrawable.java
index 88a4a76d5..877d7589d 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeVectorDrawable.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeVectorDrawable.java
@@ -10,16 +10,18 @@ import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
import org.robolectric.nativeruntime.VectorDrawableNatives;
import org.robolectric.shadows.ShadowNativeVectorDrawable.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link VectorDrawable} that is backed by native code */
@Implements(
value = VectorDrawable.class,
minSdk = O,
shadowPicker = Picker.class,
- isInAndroidSdk = false)
+ isInAndroidSdk = false,
+ callNativeMethodsByDefault = true)
public class ShadowNativeVectorDrawable extends ShadowDrawable {
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static int nDraw(
long rendererPtr,
long canvasWrapperPtr,
@@ -31,73 +33,73 @@ public class ShadowNativeVectorDrawable extends ShadowDrawable {
rendererPtr, canvasWrapperPtr, colorFilterPtr, bounds, needsMirroring, canReuseCache);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nGetFullPathProperties(long pathPtr, byte[] properties, int length) {
return VectorDrawableNatives.nGetFullPathProperties(pathPtr, properties, length);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetName(long nodePtr, String name) {
VectorDrawableNatives.nSetName(nodePtr, name);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nGetGroupProperties(long groupPtr, float[] properties, int length) {
return VectorDrawableNatives.nGetGroupProperties(groupPtr, properties, length);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetPathString(long pathPtr, String pathString, int length) {
VectorDrawableNatives.nSetPathString(pathPtr, pathString, length);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nCreateTree(long rootGroupPtr) {
return VectorDrawableNatives.nCreateTree(rootGroupPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nCreateTreeFromCopy(long treeToCopy, long rootGroupPtr) {
return VectorDrawableNatives.nCreateTreeFromCopy(treeToCopy, rootGroupPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetRendererViewportSize(
long rendererPtr, float viewportWidth, float viewportHeight) {
VectorDrawableNatives.nSetRendererViewportSize(rendererPtr, viewportWidth, viewportHeight);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static boolean nSetRootAlpha(long rendererPtr, float alpha) {
return VectorDrawableNatives.nSetRootAlpha(rendererPtr, alpha);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static float nGetRootAlpha(long rendererPtr) {
return VectorDrawableNatives.nGetRootAlpha(rendererPtr);
}
- @Implementation(minSdk = Q)
+ @Implementation(minSdk = Q, maxSdk = U.SDK_INT)
protected static void nSetAntiAlias(long rendererPtr, boolean aa) {
VectorDrawableNatives.nSetAntiAlias(rendererPtr, aa);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetAllowCaching(long rendererPtr, boolean allowCaching) {
VectorDrawableNatives.nSetAllowCaching(rendererPtr, allowCaching);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nCreateFullPath() {
return VectorDrawableNatives.nCreateFullPath();
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nCreateFullPath(long nativeFullPathPtr) {
return VectorDrawableNatives.nCreateFullPath(nativeFullPathPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nUpdateFullPathProperties(
long pathPtr,
float strokeWidth,
@@ -128,39 +130,39 @@ public class ShadowNativeVectorDrawable extends ShadowDrawable {
fillType);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nUpdateFullPathFillGradient(long pathPtr, long fillGradientPtr) {
VectorDrawableNatives.nUpdateFullPathFillGradient(pathPtr, fillGradientPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nUpdateFullPathStrokeGradient(long pathPtr, long strokeGradientPtr) {
VectorDrawableNatives.nUpdateFullPathStrokeGradient(pathPtr, strokeGradientPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nCreateClipPath() {
return VectorDrawableNatives.nCreateClipPath();
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nCreateClipPath(long clipPathPtr) {
return VectorDrawableNatives.nCreateClipPath(clipPathPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nCreateGroup() {
DefaultNativeRuntimeLoader.injectAndLoad();
return VectorDrawableNatives.nCreateGroup();
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static long nCreateGroup(long groupPtr) {
DefaultNativeRuntimeLoader.injectAndLoad();
return VectorDrawableNatives.nCreateGroup(groupPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nUpdateGroupProperties(
long groupPtr,
float rotate,
@@ -174,162 +176,162 @@ public class ShadowNativeVectorDrawable extends ShadowDrawable {
groupPtr, rotate, pivotX, pivotY, scaleX, scaleY, translateX, translateY);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nAddChild(long groupPtr, long nodePtr) {
VectorDrawableNatives.nAddChild(groupPtr, nodePtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static float nGetRotation(long groupPtr) {
return VectorDrawableNatives.nGetRotation(groupPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetRotation(long groupPtr, float rotation) {
VectorDrawableNatives.nSetRotation(groupPtr, rotation);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static float nGetPivotX(long groupPtr) {
return VectorDrawableNatives.nGetPivotX(groupPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetPivotX(long groupPtr, float pivotX) {
VectorDrawableNatives.nSetPivotX(groupPtr, pivotX);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static float nGetPivotY(long groupPtr) {
return VectorDrawableNatives.nGetPivotY(groupPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetPivotY(long groupPtr, float pivotY) {
VectorDrawableNatives.nSetPivotY(groupPtr, pivotY);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static float nGetScaleX(long groupPtr) {
return VectorDrawableNatives.nGetScaleX(groupPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetScaleX(long groupPtr, float scaleX) {
VectorDrawableNatives.nSetScaleX(groupPtr, scaleX);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static float nGetScaleY(long groupPtr) {
return VectorDrawableNatives.nGetScaleY(groupPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetScaleY(long groupPtr, float scaleY) {
VectorDrawableNatives.nSetScaleY(groupPtr, scaleY);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static float nGetTranslateX(long groupPtr) {
return VectorDrawableNatives.nGetTranslateX(groupPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetTranslateX(long groupPtr, float translateX) {
VectorDrawableNatives.nSetTranslateX(groupPtr, translateX);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static float nGetTranslateY(long groupPtr) {
return VectorDrawableNatives.nGetTranslateY(groupPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetTranslateY(long groupPtr, float translateY) {
VectorDrawableNatives.nSetTranslateY(groupPtr, translateY);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetPathData(long pathPtr, long pathDataPtr) {
VectorDrawableNatives.nSetPathData(pathPtr, pathDataPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static float nGetStrokeWidth(long pathPtr) {
return VectorDrawableNatives.nGetStrokeWidth(pathPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetStrokeWidth(long pathPtr, float width) {
VectorDrawableNatives.nSetStrokeWidth(pathPtr, width);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static int nGetStrokeColor(long pathPtr) {
return VectorDrawableNatives.nGetStrokeColor(pathPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetStrokeColor(long pathPtr, int strokeColor) {
VectorDrawableNatives.nSetStrokeColor(pathPtr, strokeColor);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static float nGetStrokeAlpha(long pathPtr) {
return VectorDrawableNatives.nGetStrokeAlpha(pathPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetStrokeAlpha(long pathPtr, float alpha) {
VectorDrawableNatives.nSetStrokeAlpha(pathPtr, alpha);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static int nGetFillColor(long pathPtr) {
return VectorDrawableNatives.nGetFillColor(pathPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetFillColor(long pathPtr, int fillColor) {
VectorDrawableNatives.nSetFillColor(pathPtr, fillColor);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static float nGetFillAlpha(long pathPtr) {
return VectorDrawableNatives.nGetFillAlpha(pathPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetFillAlpha(long pathPtr, float fillAlpha) {
VectorDrawableNatives.nSetFillAlpha(pathPtr, fillAlpha);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static float nGetTrimPathStart(long pathPtr) {
return VectorDrawableNatives.nGetTrimPathStart(pathPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetTrimPathStart(long pathPtr, float trimPathStart) {
VectorDrawableNatives.nSetTrimPathStart(pathPtr, trimPathStart);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static float nGetTrimPathEnd(long pathPtr) {
return VectorDrawableNatives.nGetTrimPathEnd(pathPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetTrimPathEnd(long pathPtr, float trimPathEnd) {
VectorDrawableNatives.nSetTrimPathEnd(pathPtr, trimPathEnd);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static float nGetTrimPathOffset(long pathPtr) {
return VectorDrawableNatives.nGetTrimPathOffset(pathPtr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nSetTrimPathOffset(long pathPtr, float trimPathOffset) {
VectorDrawableNatives.nSetTrimPathOffset(pathPtr, trimPathOffset);
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeVirtualRefBasePtr.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeVirtualRefBasePtr.java
index 8343912c5..e4c4640b5 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeVirtualRefBasePtr.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeVirtualRefBasePtr.java
@@ -7,21 +7,23 @@ import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.nativeruntime.VirtualRefBasePtrNatives;
import org.robolectric.shadows.ShadowNativeVirtualRefBasePtr.Picker;
+import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link VirtualRefBasePtr} that is backed by native code */
@Implements(
value = VirtualRefBasePtr.class,
minSdk = O,
shadowPicker = Picker.class,
- isInAndroidSdk = false)
+ isInAndroidSdk = false,
+ callNativeMethodsByDefault = true)
public class ShadowNativeVirtualRefBasePtr {
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nIncStrong(long ptr) {
VirtualRefBasePtrNatives.nIncStrong(ptr);
}
- @Implementation(minSdk = O)
+ @Implementation(minSdk = O, maxSdk = U.SDK_INT)
protected static void nDecStrong(long ptr) {
VirtualRefBasePtrNatives.nDecStrong(ptr);
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNfcAdapter.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNfcAdapter.java
index 38137474f..9713efd5d 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNfcAdapter.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNfcAdapter.java
@@ -14,6 +14,7 @@ import android.nfc.Tag;
import android.os.Build;
import android.os.Bundle;
import java.util.Map;
+import javax.annotation.concurrent.GuardedBy;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
@@ -30,23 +31,67 @@ import org.robolectric.util.reflector.Static;
@Implements(NfcAdapter.class)
public class ShadowNfcAdapter {
@RealObject NfcAdapter nfcAdapter;
+
+ @GuardedBy("ShadowNfcAdapter.class")
private static boolean hardwareExists = true;
+
+ @GuardedBy("this")
private boolean enabled;
+
+ @GuardedBy("this")
+ private boolean secureNfcSupported;
+
+ @GuardedBy("this")
+ private boolean secureNfcEnabled;
+
+ @GuardedBy("this")
private Activity enabledActivity;
+
+ @GuardedBy("this")
private PendingIntent intent;
+
+ @GuardedBy("this")
private IntentFilter[] filters;
+
+ @GuardedBy("this")
private String[][] techLists;
+
+ @GuardedBy("this")
private Activity disabledActivity;
+
+ @GuardedBy("this")
private NdefMessage ndefPushMessage;
+
+ @GuardedBy("this")
private boolean ndefPushMessageSet;
+
+ @GuardedBy("this")
private NfcAdapter.CreateNdefMessageCallback ndefPushMessageCallback;
+
+ @GuardedBy("this")
private NfcAdapter.OnNdefPushCompleteCallback onNdefPushCompleteCallback;
+
+ @GuardedBy("this")
private NfcAdapter.ReaderCallback readerCallback;
@Implementation
+ protected static NfcAdapter getDefaultAdapter(Context context) {
+ // The result of `getNfcAdapter` is cached, so need to check `hardwareExists` again here in case
+ // its value was set after the value returned by `getNfcAdapter` got cached.
+ synchronized (ShadowNfcAdapter.class) {
+ if (!hardwareExists) {
+ return null;
+ }
+ }
+ return reflector(NfcAdapterReflector.class).getDefaultAdapter(context);
+ }
+
+ @Implementation
protected static NfcAdapter getNfcAdapter(Context context) {
- if (!hardwareExists) {
- return null;
+ synchronized (ShadowNfcAdapter.class) {
+ if (!hardwareExists) {
+ return null;
+ }
}
return reflector(NfcAdapterReflector.class).getNfcAdapter(context);
}
@@ -75,18 +120,22 @@ public class ShadowNfcAdapter {
@Implementation
protected void enableForegroundDispatch(
Activity activity, PendingIntent intent, IntentFilter[] filters, String[][] techLists) {
- this.enabledActivity = activity;
- this.intent = intent;
- this.filters = filters;
- this.techLists = techLists;
+ synchronized (this) {
+ this.enabledActivity = activity;
+ this.intent = intent;
+ this.filters = filters;
+ this.techLists = techLists;
+ }
}
@Implementation
protected void disableForegroundDispatch(Activity activity) {
- disabledActivity = activity;
+ synchronized (this) {
+ disabledActivity = activity;
+ }
}
- @Implementation(minSdk = Build.VERSION_CODES.KITKAT)
+ @Implementation
protected void enableReaderMode(
Activity activity, NfcAdapter.ReaderCallback callback, int flags, Bundle extras) {
if (!RuntimeEnvironment.getApplication()
@@ -97,28 +146,37 @@ public class ShadowNfcAdapter {
if (callback == null) {
throw new NullPointerException("ReaderCallback is null");
}
- readerCallback = callback;
+
+ synchronized (this) {
+ readerCallback = callback;
+ }
}
- @Implementation(minSdk = Build.VERSION_CODES.KITKAT)
+ @Implementation
protected void disableReaderMode(Activity activity) {
if (!RuntimeEnvironment.getApplication()
.getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_NFC)) {
throw new UnsupportedOperationException();
}
- readerCallback = null;
+ synchronized (this) {
+ readerCallback = null;
+ }
}
/** Returns true if NFC is in reader mode. */
- public boolean isInReaderMode() {
+ public synchronized boolean isInReaderMode() {
return readerCallback != null;
}
/** Dispatches the tag onto any registered readers. */
public void dispatchTagDiscovered(Tag tag) {
- if (readerCallback != null) {
- readerCallback.onTagDiscovered(tag);
+ NfcAdapter.ReaderCallback callback;
+ synchronized (this) {
+ callback = readerCallback;
+ }
+ if (callback != null) {
+ callback.onTagDiscovered(tag);
}
}
@@ -137,14 +195,18 @@ public class ShadowNfcAdapter {
throw new NullPointerException("activities cannot contain null");
}
}
- this.ndefPushMessage = message;
- this.ndefPushMessageSet = true;
+ synchronized (this) {
+ this.ndefPushMessage = message;
+ this.ndefPushMessageSet = true;
+ }
}
@Implementation
protected void setNdefPushMessageCallback(
NfcAdapter.CreateNdefMessageCallback callback, Activity activity, Activity... activities) {
- this.ndefPushMessageCallback = callback;
+ synchronized (this) {
+ this.ndefPushMessageCallback = callback;
+ }
}
/**
@@ -164,23 +226,53 @@ public class ShadowNfcAdapter {
throw new NullPointerException("activities cannot contain null");
}
}
- this.onNdefPushCompleteCallback = callback;
+ synchronized (this) {
+ this.onNdefPushCompleteCallback = callback;
+ }
}
@Implementation
protected boolean isEnabled() {
- return enabled;
+ synchronized (this) {
+ return enabled;
+ }
}
@Implementation
protected boolean enable() {
- enabled = true;
+ synchronized (this) {
+ enabled = true;
+ }
return true;
}
@Implementation
protected boolean disable() {
- enabled = false;
+ synchronized (this) {
+ enabled = false;
+ }
+ return true;
+ }
+
+ @Implementation(minSdk = Build.VERSION_CODES.Q)
+ protected boolean isSecureNfcSupported() {
+ synchronized (this) {
+ return secureNfcSupported;
+ }
+ }
+
+ @Implementation(minSdk = Build.VERSION_CODES.Q)
+ protected boolean isSecureNfcEnabled() {
+ synchronized (this) {
+ return secureNfcEnabled;
+ }
+ }
+
+ @Implementation(minSdk = Build.VERSION_CODES.Q)
+ protected boolean enableSecureNfc(boolean enableSecureNfc) {
+ synchronized (this) {
+ this.secureNfcEnabled = enableSecureNfc;
+ }
return true;
}
@@ -188,45 +280,49 @@ public class ShadowNfcAdapter {
* Modifies the behavior of {@link #getNfcAdapter(Context)} to return {@code null}, to simulate
* absence of NFC hardware.
*/
- public static void setNfcHardwareExists(boolean hardwareExists) {
+ public static synchronized void setNfcHardwareExists(boolean hardwareExists) {
ShadowNfcAdapter.hardwareExists = hardwareExists;
}
- public void setEnabled(boolean enabled) {
+ public synchronized void setEnabled(boolean enabled) {
this.enabled = enabled;
}
- public Activity getEnabledActivity() {
+ public synchronized void setSecureNfcSupported(boolean secureNfcSupported) {
+ this.secureNfcSupported = secureNfcSupported;
+ }
+
+ public synchronized Activity getEnabledActivity() {
return enabledActivity;
}
- public PendingIntent getIntent() {
+ public synchronized PendingIntent getIntent() {
return intent;
}
- public IntentFilter[] getFilters() {
+ public synchronized IntentFilter[] getFilters() {
return filters;
}
- public String[][] getTechLists() {
+ public synchronized String[][] getTechLists() {
return techLists;
}
- public Activity getDisabledActivity() {
+ public synchronized Activity getDisabledActivity() {
return disabledActivity;
}
/** Returns last registered callback, or {@code null} if none was set. */
- public NfcAdapter.CreateNdefMessageCallback getNdefPushMessageCallback() {
+ public synchronized NfcAdapter.CreateNdefMessageCallback getNdefPushMessageCallback() {
return ndefPushMessageCallback;
}
- public NfcAdapter.OnNdefPushCompleteCallback getOnNdefPushCompleteCallback() {
+ public synchronized NfcAdapter.OnNdefPushCompleteCallback getOnNdefPushCompleteCallback() {
return onNdefPushCompleteCallback;
}
/** Returns last set NDEF message, or throws {@code IllegalStateException} if it was never set. */
- public NdefMessage getNdefPushMessage() {
+ public synchronized NdefMessage getNdefPushMessage() {
if (!ndefPushMessageSet) {
throw new IllegalStateException();
}
@@ -271,6 +367,10 @@ public class ShadowNfcAdapter {
@Direct
@Static
NfcAdapter getNfcAdapter(Context context);
+
+ @Direct
+ @Static
+ NfcAdapter getDefaultAdapter(Context context);
}
@ForType(Tag.class)
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNotification.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNotification.java
index 164f5bd3a..ea08d71f1 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNotification.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNotification.java
@@ -14,8 +14,10 @@ import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
+import com.google.common.collect.ImmutableList;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
+import java.util.List;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
@@ -76,6 +78,15 @@ public class ShadowNotification {
}
}
+ public List<CharSequence> getTextLines() {
+ CharSequence[] linesArray =
+ realNotification.extras.getCharSequenceArray(Notification.EXTRA_TEXT_LINES);
+ if (linesArray == null) {
+ return ImmutableList.of();
+ }
+ return ImmutableList.copyOf(linesArray);
+ }
+
public Bitmap getBigPicture() {
if (RuntimeEnvironment.getApiLevel() >= Build.VERSION_CODES.N) {
return realNotification.extras.getParcelable(Notification.EXTRA_PICTURE);
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNotificationManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNotificationManager.java
index ef0f0542a..8a566a680 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNotificationManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNotificationManager.java
@@ -8,6 +8,7 @@ import static android.os.Build.VERSION_CODES.P;
import static android.os.Build.VERSION_CODES.Q;
import static android.os.Build.VERSION_CODES.R;
+import android.annotation.NonNull;
import android.app.AutomaticZenRule;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -17,7 +18,6 @@ import android.content.ComponentName;
import android.os.Build;
import android.os.Parcel;
import android.service.notification.StatusBarNotification;
-import androidx.annotation.NonNull;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageManager.java
index 3f493e3c9..f753496c7 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageManager.java
@@ -27,8 +27,6 @@ import static android.content.pm.PackageManager.SIGNATURE_NEITHER_SIGNED;
import static android.content.pm.PackageManager.SIGNATURE_NO_MATCH;
import static android.content.pm.PackageManager.SIGNATURE_SECOND_NOT_SIGNED;
import static android.content.pm.PackageManager.VERIFICATION_ALLOW;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.N;
import static android.os.Build.VERSION_CODES.TIRAMISU;
import static java.util.Arrays.asList;
@@ -70,7 +68,6 @@ import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Binder;
-import android.os.Build;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.RemoteException;
@@ -642,11 +639,7 @@ public class ShadowPackageManager {
public void addResolveInfoForIntent(Intent intent, ResolveInfo info) {
info.isDefault = true;
ComponentInfo[] componentInfos =
- new ComponentInfo[] {
- info.activityInfo,
- info.serviceInfo,
- Build.VERSION.SDK_INT >= KITKAT ? info.providerInfo : null
- };
+ new ComponentInfo[] {info.activityInfo, info.serviceInfo, info.providerInfo};
for (ComponentInfo component : componentInfos) {
if (component != null && component.applicationInfo != null) {
component.applicationInfo.flags |= ApplicationInfo.FLAG_INSTALLED;
@@ -1102,7 +1095,7 @@ public class ShadowPackageManager {
return null;
}
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
protected List<ResolveInfo> queryBroadcastReceivers(
Intent intent, int flags, @UserIdInt int userId) {
return null;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageParser.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageParser.java
index a37fbf9f5..0f31e379c 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageParser.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPackageParser.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1;
import static org.robolectric.util.reflector.Reflector.reflector;
@@ -100,16 +99,6 @@ public class ShadowPackageParser {
@ForType(PackageParser.class)
interface _PackageParser_ {
- // <= JELLY_BEAN
- @Static
- PackageInfo generatePackageInfo(
- PackageParser.Package p,
- int[] gids,
- int flags,
- long firstInstallTime,
- long lastUpdateTime,
- HashSet<String> grantedPermissions);
-
// <= LOLLIPOP
@Static
PackageInfo generatePackageInfo(
@@ -152,10 +141,7 @@ public class ShadowPackageParser {
long lastUpdateTime) {
int apiLevel = RuntimeEnvironment.getApiLevel();
- if (apiLevel <= JELLY_BEAN) {
- return generatePackageInfo(p, gids, flags, firstInstallTime, lastUpdateTime,
- new HashSet<>());
- } else if (apiLevel <= LOLLIPOP) {
+ if (apiLevel <= LOLLIPOP) {
return generatePackageInfo(
p,
gids,
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPaint.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPaint.java
index e5bbc8287..8eececdd7 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPaint.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPaint.java
@@ -1,7 +1,6 @@
package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.KITKAT_WATCH;
import static android.os.Build.VERSION_CODES.L;
@@ -20,7 +19,6 @@ import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Paint.FontMetricsInt;
import android.graphics.PathEffect;
-import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.Typeface;
import org.robolectric.annotation.Implementation;
@@ -396,7 +394,7 @@ public class ShadowPaint {
return breakText(text, maxWidth, measuredWidth);
}
- @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = KITKAT_WATCH)
+ @Implementation(maxSdk = KITKAT_WATCH)
protected int native_breakText(
char[] text, int index, int count, float maxWidth, int bidiFlags, float[] measuredWidth) {
return breakText(text, maxWidth, measuredWidth);
@@ -453,7 +451,7 @@ public class ShadowPaint {
return breakText(text, maxWidth, measuredWidth);
}
- @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = KITKAT_WATCH)
+ @Implementation(maxSdk = KITKAT_WATCH)
protected int native_breakText(
String text, boolean measureForwards, float maxWidth, int bidiFlags, float[] measuredWidth) {
return breakText(text, maxWidth, measuredWidth);
@@ -661,7 +659,7 @@ public class ShadowPaint {
return nGetRunAdvance(0, text.toCharArray(), start, end, contextStart, contextEnd, isRtl, 0);
}
- @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = KITKAT)
+ @Implementation(maxSdk = KITKAT)
protected static float native_getTextRunAdvances(
int nativeObject,
char[] text,
@@ -676,7 +674,7 @@ public class ShadowPaint {
0, text, index, index + count, contextIndex, contextIndex + contextCount, false, index);
}
- @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = KITKAT)
+ @Implementation(maxSdk = KITKAT)
protected static float native_getTextRunAdvances(
int nativeObject,
String text,
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPaintDrawable.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPaintDrawable.java
new file mode 100644
index 000000000..de8e894ec
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPaintDrawable.java
@@ -0,0 +1,48 @@
+package org.robolectric.shadows;
+
+import static org.robolectric.util.reflector.Reflector.reflector;
+
+import android.graphics.drawable.PaintDrawable;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.RoundRectShape;
+import android.graphics.drawable.shapes.Shape;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.RealObject;
+import org.robolectric.util.reflector.Accessor;
+import org.robolectric.util.reflector.ForType;
+
+/** Shadow for {@link PaintDrawable}. */
+@Implements(PaintDrawable.class)
+public class ShadowPaintDrawable extends ShadowDrawable {
+
+ @RealObject PaintDrawable realPaintDrawable;
+
+ /** Gets the corder radii */
+ public float[] getCornerRadii() {
+ Object shapeState = reflector(ShapeDrawableReflector.class, realPaintDrawable).getShapeState();
+ Shape shape = reflector(ShapeStateReflector.class, shapeState).getShape();
+ if (!(shape instanceof RoundRectShape)) {
+ return null;
+ }
+ RoundRectShape roundRectShape = (RoundRectShape) shape;
+ return reflector(RoundRectShapeReflector.class, roundRectShape).getOuterRadii();
+ }
+
+ @ForType(ShapeDrawable.class)
+ interface ShapeDrawableReflector {
+ @Accessor("mShapeState")
+ Object getShapeState();
+ }
+
+ @ForType(className = "android.graphics.drawable.ShapeDrawable$ShapeState")
+ interface ShapeStateReflector {
+ @Accessor("mShape")
+ Shape getShape();
+ }
+
+ @ForType(RoundRectShape.class)
+ interface RoundRectShapeReflector {
+ @Accessor("mOuterRadii")
+ float[] getOuterRadii();
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowParcel.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowParcel.java
index 10fc4081d..a27e5038e 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowParcel.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowParcel.java
@@ -1,7 +1,6 @@
package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
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;
@@ -83,7 +82,7 @@ public class ShadowParcel {
}
@HiddenApi
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
public Parcelable.Creator<?> readParcelableCreator(ClassLoader loader) {
// note: calling `readString` will also consume the string, and increment the data-pointer.
// which is exactly what we need, since we do not call the real `readParcelableCreator`.
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowParcelFileDescriptor.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowParcelFileDescriptor.java
index 0982971fe..08cdae467 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowParcelFileDescriptor.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowParcelFileDescriptor.java
@@ -1,7 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE;
import static org.robolectric.shadow.api.Shadow.invokeConstructor;
import static org.robolectric.util.ReflectionHelpers.ClassParameter.from;
@@ -12,11 +10,16 @@ import android.os.Handler;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
+import android.system.Os;
+import com.google.common.io.ByteStreams;
import java.io.File;
import java.io.FileDescriptor;
+import java.io.FileInputStream;
import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
+import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.util.Collections;
import java.util.HashMap;
@@ -30,7 +33,6 @@ import org.robolectric.annotation.RealObject;
import org.robolectric.annotation.Resetter;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.ReflectionHelpers;
-import org.robolectric.util.ReflectionHelpers.ClassParameter;
import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;
@@ -111,14 +113,7 @@ public class ShadowParcelFileDescriptor {
}
private static ParcelFileDescriptor newParcelFileDescriptor() {
- if (RuntimeEnvironment.getApiLevel() > JELLY_BEAN) {
- return new ParcelFileDescriptor(new FileDescriptor());
- } else {
- // In Jelly Bean, the ParcelFileDescriptor(FileDescriptor) constructor was non-public.
- return ReflectionHelpers.callConstructor(
- ParcelFileDescriptor.class,
- ClassParameter.from(FileDescriptor.class, new FileDescriptor()));
- }
+ return new ParcelFileDescriptor(new FileDescriptor());
}
@Implementation
@@ -147,7 +142,7 @@ public class ShadowParcelFileDescriptor {
return pfd;
}
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected static ParcelFileDescriptor open(
File file, int mode, Handler handler, ParcelFileDescriptor.OnCloseListener listener)
throws IOException {
@@ -193,7 +188,7 @@ public class ShadowParcelFileDescriptor {
return new ParcelFileDescriptor[] {readSide, writeSide};
}
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected static ParcelFileDescriptor[] createReliablePipe() throws IOException {
return createPipe();
}
@@ -212,7 +207,11 @@ public class ShadowParcelFileDescriptor {
@Implementation
protected FileDescriptor getFileDescriptor() {
try {
- return getFile().getFD();
+ RandomAccessFile file = getFile();
+ if (file != null) {
+ return file.getFD();
+ }
+ return reflector(ParcelFileDescriptorReflector.class, realParcelFd).getFileDescriptor();
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -275,6 +274,34 @@ public class ShadowParcelFileDescriptor {
return new ParcelFileDescriptor(realParcelFd);
}
+ /**
+ * Support shadowing of the static method {@link ParcelFileDescriptor#dup}.
+ *
+ * <p>The real implementation calls {@link Os#fcntlInt} in order to duplicate the FileDescriptor
+ * in native code. This cannot be simulated on the JVM without the use of native code.
+ */
+ @Implementation
+ protected static ParcelFileDescriptor dup(FileDescriptor fileDescriptor) throws IOException {
+ File dupFile =
+ new File(
+ RuntimeEnvironment.getTempDirectory().createIfNotExists(PIPE_TMP_DIR).toFile(),
+ "dupfd-" + UUID.randomUUID());
+
+ // Duplicate the file represented by the file descriptor. Note that neither file streams should
+ // be closed because doing so will invalidate the corresponding file descriptor.
+ FileInputStream fileInputStream = new FileInputStream(fileDescriptor);
+ FileOutputStream fileOutputStream = new FileOutputStream(dupFile);
+ FileChannel sourceChannel = fileInputStream.getChannel();
+
+ long originalPosition = sourceChannel.position();
+
+ sourceChannel.position(0);
+ ByteStreams.copy(fileInputStream, fileOutputStream);
+ sourceChannel.position(originalPosition);
+ RandomAccessFile randomAccessFile = new RandomAccessFile(dupFile, "rw");
+ return new ParcelFileDescriptor(randomAccessFile.getFD());
+ }
+
static class FileDescriptorFromParcelUnavailableException extends RuntimeException {
FileDescriptorFromParcelUnavailableException() {
super(
@@ -291,5 +318,8 @@ public class ShadowParcelFileDescriptor {
@Direct
void close();
+
+ @Direct
+ FileDescriptor getFileDescriptor();
}
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedChoreographer.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedChoreographer.java
index 8d1f04212..efeed94bd 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedChoreographer.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedChoreographer.java
@@ -2,13 +2,22 @@ package org.robolectric.shadows;
import static org.robolectric.util.reflector.Reflector.reflector;
+import android.os.Looper;
import android.view.Choreographer;
import android.view.DisplayEventReceiver;
-import androidx.annotation.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.LooperMode;
-import org.robolectric.annotation.Resetter;
+import org.robolectric.annotation.RealObject;
+import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowDisplayEventReceiver.DisplayEventReceiverReflector;
+import org.robolectric.versioning.AndroidVersions.NMR1;
+import org.robolectric.versioning.AndroidVersions.O;
+import org.robolectric.versioning.AndroidVersions.T;
+import org.robolectric.versioning.AndroidVersions.U;
/**
* A {@link Choreographer} shadow for {@link LooperMode.Mode.PAUSED}.
@@ -24,9 +33,51 @@ import org.robolectric.shadows.ShadowDisplayEventReceiver.DisplayEventReceiverRe
isInAndroidSdk = false)
public class ShadowPausedChoreographer extends ShadowChoreographer {
- @Resetter
- public static void reset() {
- reflector(ChoreographerReflector.class).getThreadInstance().remove();
+ // keep track of all Loopers with active Choreographer so they can be selectively reset
+ private static final Set<Looper> choreographedLoopers = new CopyOnWriteArraySet<>();
+
+ @RealObject private Choreographer realChoreographer;
+
+ @Implementation(maxSdk = NMR1.SDK_INT)
+ protected void __constructor__(Looper looper) {
+ reflector(ChoreographerReflector.class, realChoreographer).__constructor__(looper);
+ choreographedLoopers.add(looper);
+ }
+
+ @Implementation(minSdk = O.SDK_INT, maxSdk = T.SDK_INT)
+ protected void __constructor__(Looper looper, int vsyncSource) {
+ reflector(ChoreographerReflector.class, realChoreographer).__constructor__(looper, vsyncSource);
+ choreographedLoopers.add(looper);
+ }
+
+ @Implementation(minSdk = U.SDK_INT)
+ protected void __constructor__(Looper looper, int vsyncSource, long layerHandle) {
+ reflector(ChoreographerReflector.class, realChoreographer)
+ .__constructor__(looper, vsyncSource, layerHandle);
+ choreographedLoopers.add(looper);
+ }
+
+ /**
+ * Resets the choreographer ThreadLocal instance for the given Looper
+ *
+ * @param looper an active looper whose queue has already been reset
+ */
+ static void reset(Looper looper) {
+ if (choreographedLoopers.remove(looper)) {
+ if (looper.getThread() == Thread.currentThread()) {
+ reflector(ChoreographerReflector.class).getThreadInstance().remove();
+ } else if (looper.getThread().isAlive()) {
+ ShadowPausedLooper shadowLooper = Shadow.extract(looper);
+ shadowLooper.postSyncQuiet(
+ () -> reflector(ChoreographerReflector.class).getThreadInstance().remove());
+ }
+ }
+ }
+
+ // safeguard that clears the list of choreographed Loopers. Intended to clean up references
+ // to Loopers that are no longer running
+ static void clearLoopers() {
+ choreographedLoopers.clear();
}
/**
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedLooper.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedLooper.java
index 3114b11c5..aa63a6872 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedLooper.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedLooper.java
@@ -6,7 +6,6 @@ import static org.robolectric.util.ReflectionHelpers.ClassParameter.from;
import static org.robolectric.util.reflector.Reflector.reflector;
import android.app.Instrumentation;
-import android.os.Build;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.Looper;
@@ -14,6 +13,7 @@ import android.os.Message;
import android.os.MessageQueue.IdleHandler;
import android.os.SystemClock;
import android.util.Log;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import java.time.Duration;
import java.util.ArrayList;
@@ -79,7 +79,7 @@ public final class ShadowPausedLooper extends ShadowLooper {
invokeConstructor(Looper.class, realLooper, from(boolean.class, quitAllowed));
loopingLoopers.add(realLooper);
- looperExecutor = new HandlerExecutor(new Handler(realLooper));
+ looperExecutor = new HandlerExecutor(realLooper);
}
protected static Collection<Looper> getLoopers() {
@@ -218,6 +218,22 @@ public final class ShadowPausedLooper extends ShadowLooper {
executeOnLooper(new PostAndIdleToRunnable(runnable));
}
+ /**
+ * Posts the runnable as an asynchronous task and wait until it has been run. Ignores all
+ * exceptions.
+ *
+ * <p>This method is similar to postSync, but used in internal cases where you want to make a best
+ * effort quick attempt to execute the Runnable, and do not need to idle all the non-async tasks
+ * that might be posted to the Looper's queue.
+ */
+ void postSyncQuiet(Runnable runnable) {
+ try {
+ executeOnLooper(new PostAsyncAndIdleToRunnable(runnable));
+ } catch (RuntimeException e) {
+ Log.w("ShadowPausedLooper", "ignoring exception on postSyncQuiet", e);
+ }
+ }
+
// this API doesn't make sense in LooperMode.PAUSED, but just retain it for backwards
// compatibility for now
@Override
@@ -291,6 +307,7 @@ public final class ShadowPausedLooper extends ShadowLooper {
ShadowPausedLooper shadowPausedLooper = Shadow.extract(looper);
shadowPausedLooper.resetLooperToInitialState();
}
+ ShadowPausedChoreographer.clearLoopers();
}
private static final Object instrumentationTestMainThreadLock = new Object();
@@ -359,20 +376,23 @@ public final class ShadowPausedLooper extends ShadowLooper {
}
}
- private synchronized void resetLooperToInitialState() {
+ @VisibleForTesting
+ synchronized void resetLooperToInitialState() {
// Do not use looperMode() here, because its cached value might already have been reset
LooperMode.Mode looperMode = ConfigurationRegistry.get(LooperMode.Mode.class);
ShadowPausedMessageQueue shadowQueue = Shadow.extract(realLooper.getQueue());
shadowQueue.reset();
- boolean canBeUnpaused =
- !(realLooper == Looper.getMainLooper()
- && looperMode != LooperMode.Mode.INSTRUMENTATION_TEST);
- if (canBeUnpaused && realLooper.getThread().isAlive()) {
- if (isPaused()) {
- unPause();
- }
+ if (realLooper.getThread().isAlive()
+ && !shadowQueue
+ .isQuitting()) { // Trying to unpause a quitted background Looper may deadlock.
+
+ if (isPaused()
+ && !(realLooper == Looper.getMainLooper() && looperMode != Mode.INSTRUMENTATION_TEST)) {
+ unPause();
+ }
+ ShadowPausedChoreographer.reset(realLooper);
}
}
@@ -388,10 +408,13 @@ public final class ShadowPausedLooper extends ShadowLooper {
if (isPaused()) {
executeOnLooper(new UnPauseRunnable());
}
- reflector(LooperReflector.class, realLooper).quit();
+ synchronized (realLooper.getQueue()) {
+ drainQueueSafely(shadowQueue());
+ reflector(LooperReflector.class, realLooper).quit();
+ }
}
- @Implementation(minSdk = Build.VERSION_CODES.JELLY_BEAN_MR2)
+ @Implementation
protected void quitSafely() {
if (isPaused()) {
executeOnLooper(new UnPauseRunnable());
@@ -457,15 +480,7 @@ public final class ShadowPausedLooper extends ShadowLooper {
} else {
synchronized (realLooper.getQueue()) {
shadowQueue.setUncaughtException(e);
- // release any ControlRunnables currently in queue to prevent deadlocks
- shadowQueue.drainQueue(
- input -> {
- if (input instanceof ControlRunnable) {
- ((ControlRunnable) input).runLatch.countDown();
- return true;
- }
- return false;
- });
+ drainQueueSafely(shadowQueue);
}
}
if (e instanceof ControlException) {
@@ -476,6 +491,18 @@ public final class ShadowPausedLooper extends ShadowLooper {
}
}
+ private static void drainQueueSafely(ShadowPausedMessageQueue shadowQueue) {
+ // release any ControlRunnables currently in queue to prevent deadlocks
+ shadowQueue.drainQueue(
+ input -> {
+ if (input instanceof ControlRunnable) {
+ ((ControlRunnable) input).runLatch.countDown();
+ return true;
+ }
+ return false;
+ });
+ }
+
/**
* If the given {@code lastMessageRead} is not null and the queue is now idle, get the idle
* handlers and run them. This synchronization mirrors what happens in the real message queue
@@ -618,8 +645,37 @@ public final class ShadowPausedLooper extends ShadowLooper {
}
}
- /** Executes the given runnable on the loopers thread, and waits for it to complete. */
- private void executeOnLooper(ControlRunnable runnable) {
+ private class PostAsyncAndIdleToRunnable extends ControlRunnable {
+ private final Runnable runnable;
+ private final Handler handler;
+
+ PostAsyncAndIdleToRunnable(Runnable runnable) {
+ this.runnable = runnable;
+ this.handler = createAsyncHandler(realLooper);
+ }
+
+ @Override
+ public void doRun() {
+ handler.postAtFrontOfQueue(runnable);
+ Message msg;
+ do {
+ msg = getNextExecutableMessage();
+ if (msg == null) {
+ throw new IllegalStateException("Runnable is not in the queue");
+ }
+ msg.getTarget().dispatchMessage(msg);
+
+ } while (msg.getCallback() != runnable);
+ }
+ }
+
+ /**
+ * Executes the given runnable on the loopers thread, and waits for it to complete.
+ *
+ * @throws IllegalStateException if Looper is quitting or has stopped due to uncaught exception
+ */
+ private void executeOnLooper(ControlRunnable runnable) throws IllegalStateException {
+ checkState(!shadowQueue().isQuitting(), "Looper is quitting");
if (Thread.currentThread() == realLooper.getThread()) {
if (runnable instanceof UnPauseRunnable) {
// Need to trigger the unpause action in PausedLooperExecutor
@@ -682,16 +738,27 @@ public final class ShadowPausedLooper extends ShadowLooper {
private class UnPauseRunnable extends ControlRunnable {
@Override
public void doRun() {
- setLooperExecutor(new HandlerExecutor(new Handler(realLooper)));
+ setLooperExecutor(new HandlerExecutor(realLooper));
isPaused = false;
}
}
+ static Handler createAsyncHandler(Looper looper) {
+ if (RuntimeEnvironment.getApiLevel() >= 28) {
+ // createAsync is only available in API 28+
+ return Handler.createAsync(looper);
+ } else {
+ return new Handler(looper, null, true);
+ }
+ }
+
private static class HandlerExecutor implements Executor {
private final Handler handler;
- private HandlerExecutor(Handler handler) {
- this.handler = handler;
+ private HandlerExecutor(Looper looper) {
+ // always post async messages so ControlRunnables get processed even if Looper is blocked on a
+ // sync barrier
+ this.handler = createAsyncHandler(looper);
}
@Override
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedMessageQueue.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedMessageQueue.java
index c40abfce9..8af76ac18 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedMessageQueue.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedMessageQueue.java
@@ -1,7 +1,6 @@
package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.KITKAT_WATCH;
import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1;
@@ -17,11 +16,10 @@ import android.os.MessageQueue;
import android.os.MessageQueue.IdleHandler;
import android.os.SystemClock;
import android.util.Log;
-import androidx.annotation.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting;
import com.google.common.base.Predicate;
import java.time.Duration;
import java.util.ArrayList;
-import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.LooperMode;
@@ -77,7 +75,7 @@ public class ShadowPausedMessageQueue extends ShadowMessageQueue {
nativeDestroy(reflector(MessageQueueReflector.class, realQueue).getPtr());
}
- @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = KITKAT)
+ @Implementation(maxSdk = KITKAT)
protected static void nativeDestroy(int ptr) {
nativeDestroy((long) ptr);
}
@@ -95,7 +93,7 @@ public class ShadowPausedMessageQueue extends ShadowMessageQueue {
// use the generic Object parameter types here, to avoid conflicts with the non-static
// nativePollOnce
- @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = LOLLIPOP_MR1)
+ @Implementation(maxSdk = LOLLIPOP_MR1)
protected static void nativePollOnce(Object ptr, Object timeoutMillis) {
long ptrLong = getLong(ptr);
nativeQueueRegistry.getNativeObject(ptrLong).nativePollOnce(ptrLong, (int) timeoutMillis);
@@ -164,7 +162,7 @@ public class ShadowPausedMessageQueue extends ShadowMessageQueue {
// use the generic Object parameter types here, to avoid conflicts with the non-static
// nativeWake
- @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = KITKAT)
+ @Implementation(maxSdk = KITKAT)
protected static void nativeWake(Object ptr) {
// JELLY_BEAN_MR2 has a bug where nativeWake can get called when pointer has already been
// destroyed. See here where nativeWake is called outside the synchronized block
@@ -260,25 +258,17 @@ public class ShadowPausedMessageQueue extends ShadowMessageQueue {
@Implementation(maxSdk = JELLY_BEAN_MR1)
protected void quit() {
- if (RuntimeEnvironment.getApiLevel() >= JELLY_BEAN_MR2) {
- reflector(MessageQueueReflector.class, realQueue).quit(false);
- } else {
- reflector(MessageQueueReflector.class, realQueue).quit();
- }
+ reflector(MessageQueueReflector.class, realQueue).quit(false);
}
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected void quit(boolean allowed) {
reflector(MessageQueueReflector.class, realQueue).quit(allowed);
ShadowPausedSystemClock.removeListener(clockListener);
}
- private boolean isQuitting() {
- if (RuntimeEnvironment.getApiLevel() >= KITKAT) {
- return reflector(MessageQueueReflector.class, realQueue).getQuitting();
- } else {
- return reflector(MessageQueueReflector.class, realQueue).getQuiting();
- }
+ boolean isQuitting() {
+ return reflector(MessageQueueReflector.class, realQueue).getQuitting();
}
private static long getLong(Object intOrLongObj) {
@@ -509,17 +499,9 @@ public class ShadowPausedMessageQueue extends ShadowMessageQueue {
@Accessor("mPtr")
int getPtr();
- // for APIs < JELLYBEAN_MR2
- @Direct
- void quit();
-
@Direct
void quit(boolean b);
- // for APIs < KITKAT
- @Accessor("mQuiting")
- boolean getQuiting();
-
@Accessor("mQuitting")
boolean getQuitting();
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedSystemClock.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedSystemClock.java
index 193c08615..5f6edfc8b 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedSystemClock.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedSystemClock.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static android.os.Build.VERSION_CODES.P;
import android.os.SystemClock;
@@ -11,15 +10,15 @@ import javax.annotation.concurrent.GuardedBy;
import org.robolectric.annotation.HiddenApi;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
-import org.robolectric.annotation.LooperMode;
import org.robolectric.annotation.Resetter;
/**
* A shadow SystemClock used when {@link LooperMode.Mode#PAUSED} is active.
*
- * <p>In this variant, there is just one global system time controlled by this class. The current
- * time is fixed in place, and manually advanced by calling {@link
- * SystemClock#setCurrentTimeMillis(long)}
+ * <p>In this variant, System times (both elapsed realtime and uptime) are controlled by this class.
+ * The current times are fixed in place. You can manually advance both by calling {@link
+ * SystemClock#setCurrentTimeMillis(long)} or just advance elapsed realtime only by calling {@link
+ * deepSleep(long)}.
*
* <p>{@link SystemClock#uptimeMillis()} and {@link SystemClock#currentThreadTimeMillis()} are
* identical.
@@ -34,8 +33,13 @@ public class ShadowPausedSystemClock extends ShadowSystemClock {
private static final long INITIAL_TIME = 100;
private static final int MILLIS_PER_NANO = 1000000;
+ @SuppressWarnings("NonFinalStaticField")
@GuardedBy("ShadowPausedSystemClock.class")
- private static long currentTimeMillis = INITIAL_TIME;
+ private static long currentUptimeMillis = INITIAL_TIME;
+
+ @SuppressWarnings("NonFinalStaticField")
+ @GuardedBy("ShadowPausedSystemClock.class")
+ private static long currentRealtimeMillis = INITIAL_TIME;
private static final List<Listener> listeners = new CopyOnWriteArrayList<>();
// hopefully temporary list of clock listeners that are NOT cleared between tests
@@ -62,11 +66,29 @@ public class ShadowPausedSystemClock extends ShadowSystemClock {
staticListeners.add(listener);
}
- /** Advances the current time by given millis, without sleeping the current thread/ */
+ /**
+ * Advances the current time (both elapsed realtime and uptime) by given millis, without sleeping
+ * the current thread.
+ */
@Implementation
protected static void sleep(long millis) {
synchronized (ShadowPausedSystemClock.class) {
- currentTimeMillis += millis;
+ currentUptimeMillis += millis;
+ currentRealtimeMillis += millis;
+ }
+ informListeners();
+ }
+
+ /**
+ * Advances the current time (elapsed realtime only) by given millis, without sleeping the current
+ * thread.
+ *
+ * <p>This is to simulate scenarios like suspend-to-RAM, where only elapsed realtime is
+ * incremented when the device is in deep sleep.
+ */
+ protected static void deepSleep(long millis) {
+ synchronized (ShadowPausedSystemClock.class) {
+ currentRealtimeMillis += millis;
}
informListeners();
}
@@ -81,21 +103,24 @@ public class ShadowPausedSystemClock extends ShadowSystemClock {
}
/**
- * Sets the current wall time.
+ * Sets the current wall time (both elapsed realtime and uptime).
+ *
+ * <p>This API sets both of the elapsed realtime and uptime to the specified value.
*
* <p>Currently does not perform any permission checks.
*
- * @return false if specified time is less than current time.
+ * @return false if specified time is less than current uptime.
*/
@Implementation
protected static boolean setCurrentTimeMillis(long millis) {
synchronized (ShadowPausedSystemClock.class) {
- if (currentTimeMillis > millis) {
+ if (currentUptimeMillis > millis) {
return false;
- } else if (currentTimeMillis == millis) {
+ } else if (currentUptimeMillis == millis) {
return true;
} else {
- currentTimeMillis = millis;
+ currentUptimeMillis = millis;
+ currentRealtimeMillis = millis;
}
}
informListeners();
@@ -104,15 +129,15 @@ public class ShadowPausedSystemClock extends ShadowSystemClock {
@Implementation
protected static synchronized long uptimeMillis() {
- return currentTimeMillis;
+ return currentUptimeMillis;
}
@Implementation
- protected static long elapsedRealtime() {
- return uptimeMillis();
+ protected static synchronized long elapsedRealtime() {
+ return currentRealtimeMillis;
}
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
protected static long elapsedRealtimeNanos() {
return elapsedRealtime() * MILLIS_PER_NANO;
}
@@ -138,7 +163,7 @@ public class ShadowPausedSystemClock extends ShadowSystemClock {
@HiddenApi
protected static synchronized long currentNetworkTimeMillis() {
if (networkTimeAvailable) {
- return currentTimeMillis;
+ return currentUptimeMillis;
} else {
throw new DateTimeException("Network time not available");
}
@@ -146,7 +171,8 @@ public class ShadowPausedSystemClock extends ShadowSystemClock {
@Resetter
public static synchronized void reset() {
- currentTimeMillis = INITIAL_TIME;
+ currentUptimeMillis = INITIAL_TIME;
+ currentRealtimeMillis = INITIAL_TIME;
ShadowSystemClock.reset();
listeners.clear();
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPendingIntent.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPendingIntent.java
index c7a86f7e9..c005d51f7 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPendingIntent.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPendingIntent.java
@@ -5,7 +5,6 @@ import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.app.PendingIntent.FLAG_NO_CREATE;
import static android.app.PendingIntent.FLAG_ONE_SHOT;
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.N;
import static android.os.Build.VERSION_CODES.O;
@@ -477,7 +476,7 @@ public class ShadowPendingIntent {
return getCreatorPackage();
}
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
protected String getCreatorPackage() {
return (creatorPackage == null)
? RuntimeEnvironment.getApplication().getPackageName()
@@ -488,7 +487,7 @@ public class ShadowPendingIntent {
this.creatorPackage = creatorPackage;
}
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
protected int getCreatorUid() {
return creatorUid;
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPhoneWindow.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPhoneWindow.java
index 9411ff1c8..3cd7ff8ff 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPhoneWindow.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPhoneWindow.java
@@ -4,10 +4,10 @@ import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.R;
import static org.robolectric.util.reflector.Reflector.reflector;
+import android.annotation.RequiresApi;
import android.graphics.drawable.Drawable;
import android.view.Gravity;
import android.view.Window;
-import androidx.annotation.RequiresApi;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
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 ae1bfd86d..a070ce1cb 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPixelCopy.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPixelCopy.java
@@ -4,6 +4,8 @@ import static android.os.Build.VERSION_CODES.O;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.robolectric.util.reflector.Reflector.reflector;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
@@ -18,8 +20,6 @@ import android.view.View;
import android.view.ViewRootImpl;
import android.view.Window;
import android.view.WindowManagerGlobal;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import java.util.function.Consumer;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPorterDuffColorFilter.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPorterDuffColorFilter.java
index f0da84485..68f65553b 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPorterDuffColorFilter.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPorterDuffColorFilter.java
@@ -24,7 +24,7 @@ public class ShadowPorterDuffColorFilter {
protected void __constructor__(int color, PorterDuff.Mode mode) {
// We need these copies because before Lollipop, PorterDuffColorFilter had no fields, it would
// just delegate to a native instance. If we remove them, the shadow cannot access the fields
- // on KitKat and earlier.
+ // on KitKat
this.color = color;
this.mode = mode;
}
@@ -34,7 +34,7 @@ public class ShadowPorterDuffColorFilter {
*/
@Implementation(minSdk = LOLLIPOP)
public int getColor() {
- if (RuntimeEnvironment.getApiLevel() <= KITKAT) {
+ if (RuntimeEnvironment.getApiLevel() == KITKAT) {
return color;
} else {
return reflector(PorterDuffColorFilterReflector.class, realPorterDuffColorFilter).getColor();
@@ -47,7 +47,7 @@ public class ShadowPorterDuffColorFilter {
*/
@Implementation(minSdk = LOLLIPOP)
public PorterDuff.Mode getMode() {
- if (RuntimeEnvironment.getApiLevel() <= KITKAT) {
+ if (RuntimeEnvironment.getApiLevel() == KITKAT) {
return mode;
} else {
return reflector(PorterDuffColorFilterReflector.class, realPorterDuffColorFilter).getMode();
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPowerManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPowerManager.java
index 9bce4cca6..cc6e427a7 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPowerManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPowerManager.java
@@ -26,7 +26,6 @@ import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
-import android.os.Build.VERSION_CODES;
import android.os.PowerManager;
import android.os.PowerManager.LowPowerStandbyPortDescription;
import android.os.PowerManager.LowPowerStandbyPortsLock;
@@ -559,11 +558,7 @@ public class ShadowPowerManager {
}
private Context getContext() {
- if (RuntimeEnvironment.getApiLevel() < VERSION_CODES.JELLY_BEAN_MR1) {
- return RuntimeEnvironment.getApplication();
- } else {
- return reflector(ReflectorPowerManager.class, realPowerManager).getContext();
- }
+ return reflector(ReflectorPowerManager.class, realPowerManager).getContext();
}
@Implementation(minSdk = TIRAMISU)
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowProcess.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowProcess.java
index 001391da0..965994d5c 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowProcess.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowProcess.java
@@ -3,7 +3,7 @@ package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.TIRAMISU;
import static com.google.common.base.Preconditions.checkArgument;
-import androidx.annotation.NonNull;
+import android.annotation.NonNull;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResources.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResources.java
index e6120d78e..93e56ccfd 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResources.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResources.java
@@ -81,10 +81,8 @@ public class ShadowResources {
protected static Resources getSystem() {
if (system == null) {
AssetManager assetManager = AssetManager.getSystem();
- DisplayMetrics metrics = new DisplayMetrics();
- Configuration config = new Configuration();
- system = new Resources(assetManager, metrics, config);
- Bootstrap.updateConfiguration(system);
+ system =
+ new Resources(assetManager, Bootstrap.getDisplayMetrics(), Bootstrap.getConfiguration());
}
return system;
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResourcesManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResourcesManager.java
index 64e72870a..d9651f6c9 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResourcesManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowResourcesManager.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static org.robolectric.util.reflector.Reflector.reflector;
import android.app.ResourcesManager;
@@ -13,7 +12,7 @@ import org.robolectric.util.reflector.Accessor;
import org.robolectric.util.reflector.ForType;
import org.robolectric.util.reflector.Static;
-@Implements(value = ResourcesManager.class, isInAndroidSdk = false, minSdk = KITKAT)
+@Implements(value = ResourcesManager.class, isInAndroidSdk = false)
public class ShadowResourcesManager {
@RealObject ResourcesManager realResourcesManager;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRoleManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRoleManager.java
index ae36b83ae..8f8cf4fe8 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRoleManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRoleManager.java
@@ -1,14 +1,25 @@
package org.robolectric.shadows;
+import static org.robolectric.shadow.api.Shadow.invokeConstructor;
+import static org.robolectric.util.ReflectionHelpers.ClassParameter.from;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.role.IRoleManager;
import android.app.role.RoleManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
import android.os.Build;
-import android.util.ArraySet;
-import androidx.annotation.NonNull;
import com.android.internal.util.Preconditions;
-import java.util.Set;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
+import org.robolectric.annotation.Resetter;
/** A shadow implementation of {@link android.app.role.RoleManager}. */
@Implements(value = RoleManager.class, minSdk = Build.VERSION_CODES.Q)
@@ -16,8 +27,37 @@ public class ShadowRoleManager {
@RealObject protected RoleManager roleManager;
- private final Set<String> heldRoles = new ArraySet<>();
- private final Set<String> availableRoles = new ArraySet<>();
+ // See RoleService implementation
+ private static final String[] DEFAULT_APPLICATION_ROLES = {
+ RoleManager.ROLE_ASSISTANT,
+ RoleManager.ROLE_BROWSER,
+ RoleManager.ROLE_CALL_REDIRECTION,
+ RoleManager.ROLE_CALL_SCREENING,
+ RoleManager.ROLE_DIALER,
+ RoleManager.ROLE_HOME,
+ RoleManager.ROLE_SMS,
+ };
+
+ private Context context;
+
+ // Roles that exist but are currently unavailable have their value set to {@code null}.
+ private static final Map<String, String> roleToHolder = new HashMap<>();
+
+ @Implementation(maxSdk = Build.VERSION_CODES.R)
+ protected void __constructor__(Context context) {
+ this.context = context;
+ invokeConstructor(RoleManager.class, roleManager, from(Context.class, context));
+ }
+
+ @Implementation(minSdk = Build.VERSION_CODES.S)
+ protected void __constructor__(Context context, IRoleManager service) {
+ this.context = context;
+ invokeConstructor(
+ RoleManager.class,
+ roleManager,
+ from(Context.class, context),
+ from(IRoleManager.class, service));
+ }
/**
* Check whether the calling application is holding a particular role.
@@ -30,28 +70,32 @@ public class ShadowRoleManager {
@Implementation
protected boolean isRoleHeld(@NonNull String roleName) {
Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
- return heldRoles.contains(roleName);
+ return context.getPackageName().equals(roleToHolder.get(roleName));
}
/**
* Add a role that would be held by the calling app when invoking {@link
* RoleManager#isRoleHeld(String)}.
+ *
+ * <p>This method makes the role available as well.
*/
public void addHeldRole(@NonNull String roleName) {
- heldRoles.add(roleName);
+ addAvailableRole(roleName);
+ roleToHolder.put(roleName, context.getPackageName());
}
/* Remove a role previously added via {@link #addHeldRole(String)}. */
public void removeHeldRole(@NonNull String roleName) {
- Preconditions.checkArgument(
- heldRoles.contains(roleName), "the supplied roleName was never added.");
- heldRoles.remove(roleName);
+ Preconditions.checkArgument(isRoleHeld(roleName), "the supplied roleName was never added.");
+ roleToHolder.put(roleName, null);
}
/**
* Check whether a particular role is available on the device.
*
- * <p>Callers can add available roles via {@link #addAvailableRole(String)}
+ * <p>Ideally available roles would be autodetected based on the state of other services or
+ * features present, but for now callers can add available roles via {@link
+ * #addAvailableRole(String)}.
*
* @param roleName the name of the role to check for
* @return whether the role is available
@@ -59,7 +103,7 @@ public class ShadowRoleManager {
@Implementation
protected boolean isRoleAvailable(@NonNull String roleName) {
Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
- return availableRoles.contains(roleName);
+ return roleToHolder.containsKey(roleName);
}
/**
@@ -68,13 +112,49 @@ public class ShadowRoleManager {
*/
public void addAvailableRole(@NonNull String roleName) {
Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
- availableRoles.add(roleName);
+ if (!isRoleAvailable(roleName)) {
+ roleToHolder.put(roleName, null);
+ }
}
/* Remove a role previously added via {@link #addAvailableRole(String)}. */
public void removeAvailableRole(@NonNull String roleName) {
Preconditions.checkArgument(
- availableRoles.contains(roleName), "the supplied roleName was never added.");
- availableRoles.remove(roleName);
+ roleToHolder.containsKey(roleName), "the supplied roleName was never added.");
+ roleToHolder.remove(roleName);
+ }
+
+ @Nullable
+ @Implementation(minSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ protected String getDefaultApplication(@NonNull String roleName) {
+ return roleToHolder.get(roleName);
+ }
+
+ @Implementation(minSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ protected void setDefaultApplication(
+ @NonNull String roleName,
+ @Nullable String packageName,
+ int flags,
+ @NonNull Executor executor,
+ @NonNull Consumer<Boolean> callback) {
+ Preconditions.checkArgument(
+ Arrays.asList(DEFAULT_APPLICATION_ROLES).contains(roleName),
+ "the supplied roleName in not a default app.");
+ try {
+ context.getPackageManager().getPackageInfo(packageName, 0);
+ if (isRoleAvailable(roleName)) {
+ roleToHolder.put(roleName, packageName);
+ executor.execute(() -> callback.accept(true));
+ return;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // fall through to failure
+ }
+ executor.execute(() -> callback.accept(false));
+ }
+
+ @Resetter
+ public static void reset() {
+ roleToHolder.clear();
}
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSafetyCenterManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSafetyCenterManager.java
index 43600c6c1..4840614cd 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSafetyCenterManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSafetyCenterManager.java
@@ -1,14 +1,17 @@
package org.robolectric.shadows;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Build.VERSION_CODES;
import android.safetycenter.SafetyCenterManager;
import android.safetycenter.SafetyEvent;
import android.safetycenter.SafetySourceData;
import android.safetycenter.SafetySourceErrorDetails;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
+import com.google.errorprone.annotations.concurrent.GuardedBy;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
+import java.util.Set;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
@@ -19,15 +22,28 @@ import org.robolectric.annotation.Implements;
isInAndroidSdk = false)
public class ShadowSafetyCenterManager {
+ private final Object lock = new Object();
+
+ @GuardedBy("lock")
private final Map<String, SafetySourceData> dataById = new HashMap<>();
+
+ @GuardedBy("lock")
private final Map<String, SafetyEvent> eventsById = new HashMap<>();
+
+ @GuardedBy("lock")
private final Map<String, SafetySourceErrorDetails> errorsById = new HashMap<>();
+ @GuardedBy("lock")
+ private final Set<String> throwForId = new HashSet<>();
+
+ @GuardedBy("lock")
private boolean enabled = false;
@Implementation
protected boolean isSafetyCenterEnabled() {
- return enabled;
+ synchronized (lock) {
+ return enabled;
+ }
}
@Implementation
@@ -35,7 +51,11 @@ public class ShadowSafetyCenterManager {
@NonNull String safetySourceId,
@Nullable SafetySourceData safetySourceData,
@NonNull SafetyEvent safetyEvent) {
- if (isSafetyCenterEnabled()) {
+ synchronized (lock) {
+ if (!isSafetyCenterEnabled()) {
+ return;
+ }
+ maybeThrowForId(safetySourceId);
dataById.put(safetySourceId, safetySourceData);
eventsById.put(safetySourceId, safetyEvent);
}
@@ -43,27 +63,51 @@ public class ShadowSafetyCenterManager {
@Implementation
protected SafetySourceData getSafetySourceData(@NonNull String safetySourceId) {
- if (isSafetyCenterEnabled()) {
+ synchronized (lock) {
+ if (!isSafetyCenterEnabled()) {
+ return null;
+ }
+ maybeThrowForId(safetySourceId);
return dataById.get(safetySourceId);
- } else {
- return null;
}
}
@Implementation
protected void reportSafetySourceError(
@NonNull String safetySourceId, @NonNull SafetySourceErrorDetails safetySourceErrorDetails) {
- if (isSafetyCenterEnabled()) {
+ synchronized (lock) {
+ if (!isSafetyCenterEnabled()) {
+ return;
+ }
+ maybeThrowForId(safetySourceId);
errorsById.put(safetySourceId, safetySourceErrorDetails);
}
}
+ @GuardedBy("lock")
+ private void maybeThrowForId(String safetySourceId) {
+ if (throwForId.contains(safetySourceId)) {
+ throw new IllegalArgumentException(String.format("%s is invalid", safetySourceId));
+ }
+ }
+
/**
* Sets the return value for {@link #isSafetyCenterEnabled} which also enables the {@link
* #setSafetySourceData} and {@link #getSafetySourceData} methods.
*/
public void setSafetyCenterEnabled(boolean enabled) {
- this.enabled = enabled;
+ synchronized (lock) {
+ this.enabled = enabled;
+ }
+ }
+
+ /**
+ * Makes the APIs throw an {@link IllegalArgumentException} for the given {@code safetySourceId}.
+ */
+ public void throwOnSafetySourceId(@NonNull String safetySourceId) {
+ synchronized (lock) {
+ throwForId.add(safetySourceId);
+ }
}
/**
@@ -71,7 +115,9 @@ public class ShadowSafetyCenterManager {
* {@link #setSafetySourceData} was called with this {@code safetySourceId}.
*/
public SafetyEvent getLastSafetyEvent(@NonNull String safetySourceId) {
- return eventsById.get(safetySourceId);
+ synchronized (lock) {
+ return eventsById.get(safetySourceId);
+ }
}
/**
@@ -79,6 +125,8 @@ public class ShadowSafetyCenterManager {
* last time {@link #reportSafetySourceError} was called with this {@code safetySourceId}.
*/
public SafetySourceErrorDetails getLastSafetySourceError(@NonNull String safetySourceId) {
- return errorsById.get(safetySourceId);
+ synchronized (lock) {
+ return errorsById.get(safetySourceId);
+ }
}
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSensorManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSensorManager.java
index 13d5d2ec6..4cea614d2 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSensorManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSensorManager.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.O;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
@@ -93,7 +92,7 @@ public class ShadowSensorManager {
/**
* @param maxLatency is ignored.
*/
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected boolean registerListener(
SensorEventListener listener, Sensor sensor, int rate, int maxLatency) {
return registerListener(listener, sensor, rate);
@@ -103,7 +102,7 @@ public class ShadowSensorManager {
* @param maxLatency is ignored.
* @param handler is ignored
*/
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected boolean registerListener(
SensorEventListener listener, Sensor sensor, int rate, int maxLatency, Handler handler) {
return registerListener(listener, sensor, rate);
@@ -167,7 +166,7 @@ public class ShadowSensorManager {
}
}
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected boolean flush(SensorEventListener listener) {
// ShadowSensorManager doesn't queue up any sensor events, so nothing actually needs to be
// flushed. Just call onFlushCompleted for each sensor that would have been flushed.
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowServiceManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowServiceManager.java
index 1dfbfeccf..7abe78b02 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowServiceManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowServiceManager.java
@@ -1,8 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.N;
@@ -105,6 +102,8 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
@@ -117,128 +116,18 @@ import android.companion.virtual.IVirtualDeviceManager;
@Implements(value = ServiceManager.class, isInAndroidSdk = false)
public class ShadowServiceManager {
- private static final Map<String, BinderService> binderServices = new HashMap<>();
- private static final Set<String> unavailableServices = new HashSet<>();
-
- static {
- addBinderService(Context.CLIPBOARD_SERVICE, IClipboard.class);
- addBinderService(Context.WIFI_P2P_SERVICE, IWifiP2pManager.class);
- addBinderService(Context.ACCOUNT_SERVICE, IAccountManager.class);
- addBinderService(Context.USB_SERVICE, IUsbManager.class);
- addBinderService(Context.LOCATION_SERVICE, ILocationManager.class);
- addBinderService(Context.INPUT_METHOD_SERVICE, IInputMethodManager.class);
- addBinderService(Context.ALARM_SERVICE, IAlarmManager.class);
- addBinderService(Context.POWER_SERVICE, IPowerManager.class);
- addBinderService(BatteryStats.SERVICE_NAME, IBatteryStats.class);
- addBinderService(Context.DROPBOX_SERVICE, IDropBoxManagerService.class);
- addBinderService(Context.DEVICE_POLICY_SERVICE, IDevicePolicyManager.class);
- addBinderService(Context.TELEPHONY_SERVICE, ITelephony.class);
- addBinderService(Context.CONNECTIVITY_SERVICE, IConnectivityManager.class);
- addBinderService(Context.WIFI_SERVICE, IWifiManager.class);
- addBinderService(Context.SEARCH_SERVICE, ISearchManager.class);
- addBinderService(Context.UI_MODE_SERVICE, IUiModeManager.class);
- addBinderService(Context.NETWORK_POLICY_SERVICE, INetworkPolicyManager.class);
- addBinderService(Context.INPUT_SERVICE, IInputManager.class);
- addBinderService(Context.COUNTRY_DETECTOR, ICountryDetector.class);
- addBinderService(Context.NSD_SERVICE, INsdManager.class);
- addBinderService(Context.AUDIO_SERVICE, IAudioService.class);
- addBinderService(Context.APPWIDGET_SERVICE, IAppWidgetService.class);
- addBinderService(Context.NOTIFICATION_SERVICE, INotificationManager.class);
- addBinderService(Context.WALLPAPER_SERVICE, IWallpaperManager.class);
- addBinderService(Context.BLUETOOTH_SERVICE, IBluetooth.class);
- addBinderService(Context.WINDOW_SERVICE, IWindowManager.class);
- addBinderService(Context.NFC_SERVICE, INfcAdapter.class, true);
- addBinderService(Context.VIRTUAL_DEVICE_SERVICE, IVirtualDeviceManager.class);
+ // A mutable map that contains a list of binder services. It is mutable so entries can be added by
+ // ShadowServiceManager subclasses. This is useful to support prerelease SDKs.
+ protected static final Map<String, BinderService> binderServices = buildBinderServicesMap();
- if (RuntimeEnvironment.getApiLevel() >= JELLY_BEAN_MR1) {
- addBinderService(Context.USER_SERVICE, IUserManager.class);
- addBinderService(BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE, IBluetoothManager.class);
- }
- if (RuntimeEnvironment.getApiLevel() >= JELLY_BEAN_MR2) {
- addBinderService(Context.APP_OPS_SERVICE, IAppOpsService.class);
- }
- if (RuntimeEnvironment.getApiLevel() >= KITKAT) {
- addBinderService("batteryproperties", IBatteryPropertiesRegistrar.class);
- }
- if (RuntimeEnvironment.getApiLevel() >= LOLLIPOP) {
- addBinderService(Context.RESTRICTIONS_SERVICE, IRestrictionsManager.class);
- addBinderService(Context.TRUST_SERVICE, ITrustManager.class);
- addBinderService(Context.JOB_SCHEDULER_SERVICE, IJobScheduler.class);
- addBinderService(Context.NETWORK_SCORE_SERVICE, INetworkScoreService.class);
- addBinderService(Context.USAGE_STATS_SERVICE, IUsageStatsManager.class);
- addBinderService(Context.MEDIA_ROUTER_SERVICE, IMediaRouterService.class);
- addBinderService(Context.MEDIA_SESSION_SERVICE, ISessionManager.class, true);
- addBinderService(
- Context.VOICE_INTERACTION_MANAGER_SERVICE, IVoiceInteractionManagerService.class, true);
- }
- if (RuntimeEnvironment.getApiLevel() >= M) {
- addBinderService(Context.FINGERPRINT_SERVICE, IFingerprintService.class);
- }
- if (RuntimeEnvironment.getApiLevel() >= N) {
- addBinderService(Context.CONTEXTHUB_SERVICE, IContextHubService.class);
- addBinderService(Context.SOUND_TRIGGER_SERVICE, ISoundTriggerService.class);
- }
- if (RuntimeEnvironment.getApiLevel() >= N_MR1) {
- addBinderService(Context.SHORTCUT_SERVICE, IShortcutService.class);
- }
- if (RuntimeEnvironment.getApiLevel() >= O) {
- addBinderService("mount", IStorageManager.class);
- addBinderService(Context.WIFI_AWARE_SERVICE, IWifiAwareManager.class);
- addBinderService(Context.STORAGE_STATS_SERVICE, IStorageStatsManager.class);
- addBinderService(Context.COMPANION_DEVICE_SERVICE, ICompanionDeviceManager.class);
- } else {
- addBinderService("mount", "android.os.storage.IMountService");
- }
- if (RuntimeEnvironment.getApiLevel() >= P) {
- addBinderService(Context.SLICE_SERVICE, ISliceManager.class);
- addBinderService(Context.CROSS_PROFILE_APPS_SERVICE, ICrossProfileApps.class);
- addBinderService(Context.WIFI_RTT_RANGING_SERVICE, IWifiRttManager.class);
- addBinderService(Context.IPSEC_SERVICE, IIpSecService.class);
- }
- if (RuntimeEnvironment.getApiLevel() >= Q) {
- addBinderService(Context.BIOMETRIC_SERVICE, IBiometricService.class);
- addBinderService(Context.CONTENT_CAPTURE_MANAGER_SERVICE, IContentCaptureManager.class);
- addBinderService(Context.ROLE_SERVICE, IRoleManager.class);
- addBinderService(Context.ROLLBACK_SERVICE, IRollbackManager.class);
- addBinderService(Context.THERMAL_SERVICE, IThermalService.class);
- addBinderService(Context.BUGREPORT_SERVICE, IDumpstate.class);
- }
- if (RuntimeEnvironment.getApiLevel() >= R) {
- addBinderService(Context.APP_INTEGRITY_SERVICE, IAppIntegrityManager.class);
- addBinderService(Context.AUTH_SERVICE, IAuthService.class);
- addBinderService(Context.TETHERING_SERVICE, ITetheringConnector.class);
- addBinderService("telephony.registry", ITelephonyRegistry.class);
- addBinderService(Context.PLATFORM_COMPAT_SERVICE, IPlatformCompat.class);
- }
- if (RuntimeEnvironment.getApiLevel() >= S) {
- addBinderService("permissionmgr", IPermissionManager.class);
- addBinderService(Context.TIME_ZONE_DETECTOR_SERVICE, ITimeZoneDetectorService.class);
- addBinderService(Context.TIME_DETECTOR_SERVICE, ITimeDetectorService.class);
- addBinderService(Context.SPEECH_RECOGNITION_SERVICE, IRecognitionServiceManager.class);
- addBinderService(Context.LEGACY_PERMISSION_SERVICE, ILegacyPermissionManager.class);
- addBinderService(Context.UWB_SERVICE, IUwbAdapter.class);
- addBinderService(Context.VCN_MANAGEMENT_SERVICE, IVcnManagementService.class);
- addBinderService(Context.TRANSLATION_MANAGER_SERVICE, ITranslationManager.class);
- addBinderService(Context.SENSOR_PRIVACY_SERVICE, ISensorPrivacyManager.class);
- addBinderService(Context.VPN_MANAGEMENT_SERVICE, IVpnManager.class);
- }
- if (RuntimeEnvironment.getApiLevel() >= TIRAMISU) {
- addBinderService(Context.AMBIENT_CONTEXT_SERVICE, IAmbientContextManager.class);
- addBinderService(Context.LOCALE_SERVICE, ILocaleManager.class);
- addBinderService(Context.SAFETY_CENTER_SERVICE, ISafetyCenterManager.class);
- addBinderService(Context.STATUS_BAR_SERVICE, IStatusBar.class);
- }
- if (RuntimeEnvironment.getApiLevel() >= UPSIDE_DOWN_CAKE) {
- addBinderService(Context.VIRTUAL_DEVICE_SERVICE, IVirtualDeviceManager.class);
- addBinderService(Context.WEARABLE_SENSING_SERVICE, IWearableSensingManager.class);
- }
- }
+ @GuardedBy("ShadowServiceManager.class")
+ private static final Set<String> unavailableServices = new HashSet<>();
/**
* A data class that holds descriptor information about binder services. It also holds the cached
* binder object if it is requested by {@link #getService(String)}.
*/
- private static class BinderService {
+ private static final class BinderService {
private final Class<? extends IInterface> clazz;
private final String className;
@@ -251,9 +140,8 @@ public class ShadowServiceManager {
this.useDeepBinder = useDeepBinder;
}
- // Needs to be synchronized in case multiple threads call ServiceManager.getService
- // concurrently.
- synchronized IBinder getBinder() {
+ @GuardedBy("ShadowServiceManager.class")
+ IBinder getBinder() {
if (cachedBinder == null) {
cachedBinder = new Binder();
cachedBinder.attachInterface(
@@ -266,52 +154,193 @@ public class ShadowServiceManager {
}
}
- protected static void addBinderService(String name, Class<? extends IInterface> clazz) {
- addBinderService(name, clazz, clazz.getCanonicalName(), false);
+ private static Map<String, BinderService> buildBinderServicesMap() {
+ Map<String, BinderService> binderServices = new HashMap<>();
+ addBinderService(binderServices, Context.CLIPBOARD_SERVICE, IClipboard.class);
+ addBinderService(binderServices, Context.WIFI_P2P_SERVICE, IWifiP2pManager.class);
+ addBinderService(binderServices, Context.ACCOUNT_SERVICE, IAccountManager.class);
+ addBinderService(binderServices, Context.USB_SERVICE, IUsbManager.class);
+ addBinderService(binderServices, Context.LOCATION_SERVICE, ILocationManager.class);
+ addBinderService(binderServices, Context.INPUT_METHOD_SERVICE, IInputMethodManager.class);
+ addBinderService(binderServices, Context.ALARM_SERVICE, IAlarmManager.class);
+ addBinderService(binderServices, Context.POWER_SERVICE, IPowerManager.class);
+ addBinderService(binderServices, BatteryStats.SERVICE_NAME, IBatteryStats.class);
+ addBinderService(binderServices, Context.DROPBOX_SERVICE, IDropBoxManagerService.class);
+ addBinderService(binderServices, Context.DEVICE_POLICY_SERVICE, IDevicePolicyManager.class);
+ addBinderService(binderServices, Context.TELEPHONY_SERVICE, ITelephony.class);
+ addBinderService(binderServices, Context.CONNECTIVITY_SERVICE, IConnectivityManager.class);
+ addBinderService(binderServices, Context.WIFI_SERVICE, IWifiManager.class);
+ addBinderService(binderServices, Context.SEARCH_SERVICE, ISearchManager.class);
+ addBinderService(binderServices, Context.UI_MODE_SERVICE, IUiModeManager.class);
+ addBinderService(binderServices, Context.NETWORK_POLICY_SERVICE, INetworkPolicyManager.class);
+ addBinderService(binderServices, Context.INPUT_SERVICE, IInputManager.class);
+ addBinderService(binderServices, Context.COUNTRY_DETECTOR, ICountryDetector.class);
+ addBinderService(binderServices, Context.NSD_SERVICE, INsdManager.class);
+ addBinderService(binderServices, Context.AUDIO_SERVICE, IAudioService.class);
+ addBinderService(binderServices, Context.APPWIDGET_SERVICE, IAppWidgetService.class);
+ addBinderService(binderServices, Context.NOTIFICATION_SERVICE, INotificationManager.class);
+ addBinderService(binderServices, Context.WALLPAPER_SERVICE, IWallpaperManager.class);
+ addBinderService(binderServices, Context.BLUETOOTH_SERVICE, IBluetooth.class);
+ addBinderService(binderServices, Context.WINDOW_SERVICE, IWindowManager.class);
+ addBinderService(binderServices, Context.NFC_SERVICE, INfcAdapter.class, true);
+ addBinderService(binderServices, Context.USER_SERVICE, IUserManager.class);
+ addBinderService(
+ binderServices, BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE, IBluetoothManager.class);
+ addBinderService(binderServices, Context.APP_OPS_SERVICE, IAppOpsService.class);
+ addBinderService(binderServices, "batteryproperties", IBatteryPropertiesRegistrar.class);
+
+ if (RuntimeEnvironment.getApiLevel() >= LOLLIPOP) {
+ addBinderService(binderServices, Context.RESTRICTIONS_SERVICE, IRestrictionsManager.class);
+ addBinderService(binderServices, Context.TRUST_SERVICE, ITrustManager.class);
+ addBinderService(binderServices, Context.JOB_SCHEDULER_SERVICE, IJobScheduler.class);
+ addBinderService(binderServices, Context.NETWORK_SCORE_SERVICE, INetworkScoreService.class);
+ addBinderService(binderServices, Context.USAGE_STATS_SERVICE, IUsageStatsManager.class);
+ addBinderService(binderServices, Context.MEDIA_ROUTER_SERVICE, IMediaRouterService.class);
+ addBinderService(binderServices, Context.MEDIA_SESSION_SERVICE, ISessionManager.class, true);
+ addBinderService(
+ binderServices,
+ Context.VOICE_INTERACTION_MANAGER_SERVICE,
+ IVoiceInteractionManagerService.class,
+ true);
+ }
+ if (RuntimeEnvironment.getApiLevel() >= M) {
+ addBinderService(binderServices, Context.FINGERPRINT_SERVICE, IFingerprintService.class);
+ }
+ if (RuntimeEnvironment.getApiLevel() >= N) {
+ addBinderService(binderServices, Context.CONTEXTHUB_SERVICE, IContextHubService.class);
+ addBinderService(binderServices, Context.SOUND_TRIGGER_SERVICE, ISoundTriggerService.class);
+ }
+ if (RuntimeEnvironment.getApiLevel() >= N_MR1) {
+ addBinderService(binderServices, Context.SHORTCUT_SERVICE, IShortcutService.class);
+ }
+ if (RuntimeEnvironment.getApiLevel() >= O) {
+ addBinderService(binderServices, "mount", IStorageManager.class);
+ addBinderService(binderServices, Context.WIFI_AWARE_SERVICE, IWifiAwareManager.class);
+ addBinderService(binderServices, Context.STORAGE_STATS_SERVICE, IStorageStatsManager.class);
+ addBinderService(
+ binderServices, Context.COMPANION_DEVICE_SERVICE, ICompanionDeviceManager.class);
+ } else {
+ addBinderService(binderServices, "mount", "android.os.storage.IMountService");
+ }
+ if (RuntimeEnvironment.getApiLevel() >= P) {
+ addBinderService(binderServices, Context.SLICE_SERVICE, ISliceManager.class);
+ addBinderService(binderServices, Context.CROSS_PROFILE_APPS_SERVICE, ICrossProfileApps.class);
+ addBinderService(binderServices, Context.WIFI_RTT_RANGING_SERVICE, IWifiRttManager.class);
+ addBinderService(binderServices, Context.IPSEC_SERVICE, IIpSecService.class);
+ }
+ if (RuntimeEnvironment.getApiLevel() >= Q) {
+ addBinderService(binderServices, Context.BIOMETRIC_SERVICE, IBiometricService.class);
+ addBinderService(
+ binderServices, Context.CONTENT_CAPTURE_MANAGER_SERVICE, IContentCaptureManager.class);
+ addBinderService(binderServices, Context.ROLE_SERVICE, IRoleManager.class);
+ addBinderService(binderServices, Context.ROLLBACK_SERVICE, IRollbackManager.class);
+ addBinderService(binderServices, Context.THERMAL_SERVICE, IThermalService.class);
+ addBinderService(binderServices, Context.BUGREPORT_SERVICE, IDumpstate.class);
+ }
+ if (RuntimeEnvironment.getApiLevel() >= R) {
+ addBinderService(binderServices, Context.APP_INTEGRITY_SERVICE, IAppIntegrityManager.class);
+ addBinderService(binderServices, Context.AUTH_SERVICE, IAuthService.class);
+ addBinderService(binderServices, Context.TETHERING_SERVICE, ITetheringConnector.class);
+ addBinderService(binderServices, "telephony.registry", ITelephonyRegistry.class);
+ addBinderService(binderServices, Context.PLATFORM_COMPAT_SERVICE, IPlatformCompat.class);
+ }
+ if (RuntimeEnvironment.getApiLevel() >= S) {
+ addBinderService(binderServices, "permissionmgr", IPermissionManager.class);
+ addBinderService(
+ binderServices, Context.TIME_ZONE_DETECTOR_SERVICE, ITimeZoneDetectorService.class);
+ addBinderService(binderServices, Context.TIME_DETECTOR_SERVICE, ITimeDetectorService.class);
+ addBinderService(
+ binderServices, Context.SPEECH_RECOGNITION_SERVICE, IRecognitionServiceManager.class);
+ addBinderService(
+ binderServices, Context.LEGACY_PERMISSION_SERVICE, ILegacyPermissionManager.class);
+ addBinderService(binderServices, Context.UWB_SERVICE, IUwbAdapter.class);
+ addBinderService(binderServices, Context.VCN_MANAGEMENT_SERVICE, IVcnManagementService.class);
+ addBinderService(
+ binderServices, Context.TRANSLATION_MANAGER_SERVICE, ITranslationManager.class);
+ addBinderService(binderServices, Context.SENSOR_PRIVACY_SERVICE, ISensorPrivacyManager.class);
+ addBinderService(binderServices, Context.VPN_MANAGEMENT_SERVICE, IVpnManager.class);
+ }
+ if (RuntimeEnvironment.getApiLevel() >= TIRAMISU) {
+ addBinderService(
+ binderServices, Context.AMBIENT_CONTEXT_SERVICE, IAmbientContextManager.class);
+ addBinderService(binderServices, Context.LOCALE_SERVICE, ILocaleManager.class);
+ addBinderService(binderServices, Context.SAFETY_CENTER_SERVICE, ISafetyCenterManager.class);
+ addBinderService(binderServices, Context.STATUS_BAR_SERVICE, IStatusBar.class);
+ }
+ if (RuntimeEnvironment.getApiLevel() >= UPSIDE_DOWN_CAKE) {
+ addBinderService(binderServices, Context.VIRTUAL_DEVICE_SERVICE, IVirtualDeviceManager.class);
+ addBinderService(
+ binderServices, Context.WEARABLE_SENSING_SERVICE, IWearableSensingManager.class);
+ }
+ return binderServices;
}
protected static void addBinderService(
- String name, Class<? extends IInterface> clazz, boolean useDeepBinder) {
- addBinderService(name, clazz, clazz.getCanonicalName(), useDeepBinder);
+ Map<String, BinderService> binderServices, String name, Class<? extends IInterface> clazz) {
+ addBinderService(binderServices, name, clazz, clazz.getCanonicalName(), false);
+ }
+
+ private static void addBinderService(
+ Map<String, BinderService> binderServices,
+ String name,
+ Class<? extends IInterface> clazz,
+ boolean useDeepBinder) {
+ addBinderService(binderServices, name, clazz, clazz.getCanonicalName(), useDeepBinder);
}
- protected static void addBinderService(String name, String className) {
+ private static void addBinderService(
+ Map<String, BinderService> binderServices, String name, String className) {
Class<? extends IInterface> clazz;
try {
clazz = Class.forName(className).asSubclass(IInterface.class);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
- addBinderService(name, clazz, className, false);
+ addBinderService(binderServices, name, clazz, className, false);
}
- protected static void addBinderService(
- String name, Class<? extends IInterface> clazz, String className, boolean useDeepBinder) {
+ private static void addBinderService(
+ Map<String, BinderService> binderServices,
+ String name,
+ Class<? extends IInterface> clazz,
+ String className,
+ boolean useDeepBinder) {
binderServices.put(name, new BinderService(clazz, className, useDeepBinder));
}
+
/**
- * Returns the binder associated with the given system service. If the given service is set to
- * unavailable in {@link #setServiceAvailability}, {@code null} will be returned.
+ * Returns the {@link IBinder} associated with the given system service. If the given service is
+ * set to unavailable in {@link #setServiceAvailability}, {@code null} will be returned.
*/
@Implementation
protected static IBinder getService(String name) {
- if (unavailableServices.contains(name)) {
- return null;
- }
- BinderService binderService = binderServices.get(name);
- if (binderService == null) {
- return null;
+ synchronized (ShadowServiceManager.class) {
+ if (unavailableServices.contains(name)) {
+ return null;
+ }
+ return getBinderForService(name);
}
-
- return binderService.getBinder();
}
@Implementation
protected static void addService(String name, IBinder service) {}
+ /**
+ * Same as {@link #getService}.
+ *
+ * <p>The real implementation of {@link #checkService} differs from {@link #getService} in that it
+ * is not a blocking call; so it is more likely to return {@code null} in cases where the service
+ * isn't available (whereas {@link #getService} will block until it becomes available, until a
+ * timeout or error happens).
+ */
@Implementation
protected static IBinder checkService(String name) {
- return null;
+ synchronized (ShadowServiceManager.class) {
+ if (unavailableServices.contains(name)) {
+ return null;
+ }
+ return getBinderForService(name);
+ }
}
@Implementation
@@ -325,8 +354,10 @@ public class ShadowServiceManager {
/**
* Sets the availability of the given system service. If the service is set as unavailable,
* subsequent calls to {@link Context#getSystemService} for that service will return {@code null}.
+ *
+ * <p>A service is considered available by default.
*/
- public static void setServiceAvailability(String service, boolean available) {
+ public static synchronized void setServiceAvailability(String service, boolean available) {
if (available) {
unavailableServices.remove(service);
} else {
@@ -334,8 +365,18 @@ public class ShadowServiceManager {
}
}
+ @GuardedBy("ShadowServiceManager.class")
+ @Nullable
+ private static IBinder getBinderForService(String name) {
+ BinderService binderService = binderServices.get(name);
+ if (binderService == null) {
+ return null;
+ }
+ return binderService.getBinder();
+ }
+
@Resetter
- public static void reset() {
+ public static synchronized void reset() {
unavailableServices.clear();
}
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSettings.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSettings.java
index 16cad3b6a..00149ac68 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSettings.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSettings.java
@@ -1,8 +1,6 @@
package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.JELLY_BEAN;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.P;
@@ -15,7 +13,6 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.location.LocationManager;
-import android.os.Build;
import android.provider.Settings;
import android.provider.Settings.Secure;
import android.provider.Settings.SettingNotFoundException;
@@ -74,7 +71,7 @@ public class ShadowSettings {
return get(String.class, name).orElse(null);
}
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
protected static String getStringForUser(ContentResolver cr, String name, int userHandle) {
return get(String.class, name).orElse(null);
}
@@ -161,7 +158,7 @@ public class ShadowSettings {
private static final Map<String, Optional<Object>> dataMap =
new ConcurrentHashMap<>(SECURE_DEFAULTS);
- @Implementation(minSdk = JELLY_BEAN_MR1, maxSdk = P)
+ @Implementation(maxSdk = P)
@SuppressWarnings("robolectric.ShadowReturnTypeMismatch")
protected static boolean setLocationProviderEnabledForUser(
ContentResolver cr, String provider, boolean enabled, int uid) {
@@ -250,13 +247,13 @@ public class ShadowSettings {
return true;
}
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
protected static int getIntForUser(ContentResolver cr, String name, int def, int userHandle) {
// ignore userhandle
return getInt(cr, name, def);
}
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
protected static int getIntForUser(ContentResolver cr, String name, int userHandle)
throws SettingNotFoundException {
// ignore userhandle
@@ -266,7 +263,6 @@ public class ShadowSettings {
@Implementation
protected static int getInt(ContentResolver cr, String name) throws SettingNotFoundException {
if (Settings.Secure.LOCATION_MODE.equals(name)
- && RuntimeEnvironment.getApiLevel() >= KITKAT
&& RuntimeEnvironment.getApiLevel() < P) {
// Map from to underlying location provider storage API to location mode
return reflector(SettingsSecureReflector.class).getLocationModeForUser(cr, 0);
@@ -278,7 +274,6 @@ public class ShadowSettings {
@Implementation
protected static int getInt(ContentResolver cr, String name, int def) {
if (Settings.Secure.LOCATION_MODE.equals(name)
- && RuntimeEnvironment.getApiLevel() >= KITKAT
&& RuntimeEnvironment.getApiLevel() < P) {
// Map from to underlying location provider storage API to location mode
return reflector(SettingsSecureReflector.class).getLocationModeForUser(cr, 0);
@@ -297,7 +292,7 @@ public class ShadowSettings {
return get(String.class, name).orElse(null);
}
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
protected static String getStringForUser(ContentResolver cr, String name, int userHandle) {
return getString(cr, name);
}
@@ -354,7 +349,7 @@ public class ShadowSettings {
}
}
- @Implements(value = Settings.Global.class, minSdk = JELLY_BEAN_MR1)
+ @Implements(value = Settings.Global.class)
public static class ShadowGlobal {
private static final ImmutableMap<String, Optional<Object>> DEFAULTS =
ImmutableMap.<String, Optional<Object>>builder()
@@ -387,7 +382,7 @@ public class ShadowSettings {
return get(String.class, name).orElse(null);
}
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
protected static String getStringForUser(ContentResolver cr, String name, int userHandle) {
return getString(cr, name);
}
@@ -514,13 +509,10 @@ public class ShadowSettings {
* @param adbEnabled new value for whether adb is enabled
*/
public static void setAdbEnabled(boolean adbEnabled) {
- // This setting moved from Secure to Global in JELLY_BEAN_MR1
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
- Settings.Global.putInt(
- RuntimeEnvironment.getApplication().getContentResolver(),
- Settings.Global.ADB_ENABLED,
- adbEnabled ? 1 : 0);
- }
+ Settings.Global.putInt(
+ RuntimeEnvironment.getApplication().getContentResolver(),
+ Settings.Global.ADB_ENABLED,
+ adbEnabled ? 1 : 0);
// Support all clients by always setting the Secure version of the setting
Settings.Secure.putInt(
RuntimeEnvironment.getApplication().getContentResolver(),
@@ -538,12 +530,10 @@ public class ShadowSettings {
// This setting moved from Secure to Global in JELLY_BEAN_MR1 and then moved it back to Global
// in LOLLIPOP. Support all clients by always setting this field on all versions >=
// JELLY_BEAN_MR1.
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
- Settings.Global.putInt(
- RuntimeEnvironment.getApplication().getContentResolver(),
- Settings.Global.INSTALL_NON_MARKET_APPS,
- installNonMarketApps ? 1 : 0);
- }
+ Settings.Global.putInt(
+ RuntimeEnvironment.getApplication().getContentResolver(),
+ Settings.Global.INSTALL_NON_MARKET_APPS,
+ installNonMarketApps ? 1 : 0);
// Always set the Secure version of the setting
Settings.Secure.putInt(
RuntimeEnvironment.getApplication().getContentResolver(),
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSliceManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSliceManager.java
index 74d64181b..8f8ef2517 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSliceManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSliceManager.java
@@ -2,6 +2,7 @@ package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.P;
+import android.annotation.NonNull;
import android.app.slice.SliceManager;
import android.app.slice.SliceSpec;
import android.content.Context;
@@ -9,7 +10,6 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.Uri;
import android.os.Handler;
-import androidx.annotation.NonNull;
import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
import java.util.Collection;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSmsManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSmsManager.java
index 95232dbd0..81a1521fe 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSmsManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSmsManager.java
@@ -1,18 +1,17 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1;
import static android.os.Build.VERSION_CODES.R;
import static android.os.Build.VERSION_CODES.S;
+import android.annotation.Nullable;
import android.app.PendingIntent;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.telephony.SmsManager;
import android.text.TextUtils;
-import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -22,7 +21,7 @@ import org.robolectric.annotation.Implements;
import org.robolectric.annotation.Resetter;
import org.robolectric.util.ReflectionHelpers;
-@Implements(value = SmsManager.class, minSdk = JELLY_BEAN_MR2)
+@Implements(value = SmsManager.class)
public class ShadowSmsManager {
private String smscAddress;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSpeechRecognizer.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSpeechRecognizer.java
index 4f60510a7..dc206ae80 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSpeechRecognizer.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSpeechRecognizer.java
@@ -2,6 +2,9 @@ package org.robolectric.shadows;
import static org.robolectric.util.reflector.Reflector.reflector;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresApi;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -13,9 +16,6 @@ import android.os.Message;
import android.speech.IRecognitionService;
import android.speech.RecognitionListener;
import android.speech.SpeechRecognizer;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
import com.google.common.base.Preconditions;
import java.util.Queue;
import java.util.concurrent.Executor;
@@ -28,6 +28,7 @@ import org.robolectric.util.reflector.Accessor;
import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;
import org.robolectric.util.reflector.Static;
+import org.robolectric.versioning.AndroidVersions.U;
/** Robolectric shadow for SpeechRecognizer. */
@Implements(value = SpeechRecognizer.class, looseSignatures = true)
@@ -112,7 +113,7 @@ public class ShadowSpeechRecognizer {
* Handles changing the listener and allows access to the internal listener to trigger events and
* sets the latest SpeechRecognizer.
*/
- @Implementation
+ @Implementation(maxSdk = U.SDK_INT) // TODO(hoisie): Update this to support Android V
protected void handleChangeListener(RecognitionListener listener) {
recognitionListener = listener;
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowStatFs.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowStatFs.java
index d685d5b3c..06f0de565 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowStatFs.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowStatFs.java
@@ -1,7 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
-
import android.os.StatFs;
import java.io.File;
import java.util.Map;
@@ -43,22 +41,22 @@ public class ShadowStatFs {
return stat.freeBlocks;
}
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected long getFreeBlocksLong() {
return stat.freeBlocks;
}
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected long getFreeBytes() {
return getBlockSizeLong() * getFreeBlocksLong();
}
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected long getAvailableBytes() {
return getBlockSizeLong() * getAvailableBlocksLong();
}
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected long getTotalBytes() {
return getBlockSizeLong() * getBlockCountLong();
}
@@ -88,17 +86,17 @@ public class ShadowStatFs {
}
/** Robolectric always uses a block size of 4096. */
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected long getBlockSizeLong() {
return BLOCK_SIZE;
}
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected long getBlockCountLong() {
return stat.blockCount;
}
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected long getAvailableBlocksLong() {
return stat.availableBlocks;
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowStatusBarManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowStatusBarManager.java
index 7282eee21..5fb1b89ba 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowStatusBarManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowStatusBarManager.java
@@ -6,7 +6,7 @@ import static android.os.Build.VERSION_CODES.TIRAMISU;
import static org.robolectric.util.reflector.Reflector.reflector;
import android.app.StatusBarManager;
-import androidx.annotation.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.util.reflector.Accessor;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowStorageManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowStorageManager.java
index 929c36aef..0a79ee7c3 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowStorageManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowStorageManager.java
@@ -16,6 +16,7 @@ import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.HiddenApi;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
import org.robolectric.shadow.api.Shadow;
/**
@@ -25,11 +26,11 @@ import org.robolectric.shadow.api.Shadow;
public class ShadowStorageManager {
private static boolean isFileEncryptionSupported = true;
- private final List<StorageVolume> storageVolumeList = new ArrayList<>();
+ private static final List<StorageVolume> storageVolumeList = new ArrayList<>();
@Implementation(minSdk = M)
protected static StorageVolume[] getVolumeList(int userId, int flags) {
- return new StorageVolume[0];
+ return storageVolumeList.toArray(new StorageVolume[0]);
}
/**
@@ -110,4 +111,9 @@ public class ShadowStorageManager {
Shadow.extract(RuntimeEnvironment.getApplication().getSystemService(UserManager.class));
return extract.isUserUnlocked();
}
+
+ @Resetter
+ public static void reset() {
+ storageVolumeList.clear();
+ }
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSubscriptionManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSubscriptionManager.java
index 27b787d96..41b349b26 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSubscriptionManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSubscriptionManager.java
@@ -131,11 +131,19 @@ public class ShadowSubscriptionManager {
* {@link #setActiveSubscriptionInfoList}.
*/
private List<SubscriptionInfo> subscriptionList = new ArrayList<>();
+
+ /**
+ * Cache of {@link SubscriptionInfo} used by {@link #getAccessibleSubscriptionInfoList}. Managed
+ * by {@link #setAccessibleSubscriptionInfos}.
+ */
+ private List<SubscriptionInfo> accessibleSubscriptionList = new ArrayList<>();
+
/**
* Cache of {@link SubscriptionInfo} used by {@link #getAvailableSubscriptionInfoList}. Managed by
* {@link #setAvailableSubscriptionInfos}.
*/
private List<SubscriptionInfo> availableSubscriptionList = new ArrayList<>();
+
/**
* List of listeners to be notified if the list of {@link SubscriptionInfo} changes. Managed by
* {@link #addOnSubscriptionsChangedListener} and {@link removeOnSubscriptionsChangedListener}.
@@ -158,6 +166,15 @@ public class ShadowSubscriptionManager {
}
/**
+ * Returns the accessible list of {@link SubscriptionInfo} that were set via {@link
+ * #setAccessibleSubscriptionInfoList}.
+ */
+ @Implementation(minSdk = O_MR1)
+ protected List<SubscriptionInfo> getAccessibleSubscriptionInfoList() {
+ return accessibleSubscriptionList;
+ }
+
+ /**
* Returns the available list of {@link SubscriptionInfo} that were set via {@link
* #setAvailableSubscriptionInfoList}.
*/
@@ -234,6 +251,12 @@ public class ShadowSubscriptionManager {
* Sets the active list of {@link SubscriptionInfo}. This call internally triggers {@link
* OnSubscriptionsChangedListener#onSubscriptionsChanged()} to all the listeners.
*
+ * <p>"Active" here means subscriptions which are currently mapped to a live modem stack in the
+ * device (i.e. the modem will attempt to use them to connect to nearby towers), and they are
+ * expected to have {@link SubscriptionInfo#getSimSlotIndex()} >= 0. A subscription being "active"
+ * in the device does NOT have any relation to a carrier's "activation" process for subscribers'
+ * SIMs.
+ *
* @param list - The subscription info list, can be null.
*/
public void setActiveSubscriptionInfoList(List<SubscriptionInfo> list) {
@@ -242,9 +265,34 @@ public class ShadowSubscriptionManager {
}
/**
- * Sets the active list of {@link SubscriptionInfo}. This call internally triggers {@link
+ * Sets the accessible list of {@link SubscriptionInfo}. This call internally triggers {@link
* OnSubscriptionsChangedListener#onSubscriptionsChanged()} to all the listeners.
*
+ * <p>"Accessible" here means subscriptions which are eSIM ({@link SubscriptionInfo#isEmbedded})
+ * and "owned" by the calling app, i.e. by {@link
+ * SubscriptionManager#canManageSubscription(SubscriptionInfo)}. They may be active, or
+ * installed-but-inactive. This is generally intended to be called by carrier apps that directly
+ * manage their own eSIM profiles on the device in concert with {@link
+ * android.telephony.EuiccManager}.
+ *
+ * @param list - The subscription info list, can be null.
+ */
+ public void setAccessibleSubscriptionInfoList(List<SubscriptionInfo> list) {
+ accessibleSubscriptionList = list;
+ dispatchOnSubscriptionsChanged();
+ }
+
+ /**
+ * Sets the available list of {@link SubscriptionInfo}. This call internally triggers {@link
+ * OnSubscriptionsChangedListener#onSubscriptionsChanged()} to all the listeners.
+ *
+ * <p>"Available" here means all active subscriptions (see {@link #setActiveSubscriptionInfoList})
+ * combined with all installed-but-inactive eSIM subscriptions (similar to {@link
+ * #setAccessibleSubscriptionInfoList}, but not filtered to one particular app's "ownership"
+ * rights for subscriptions). This is generally intended to be called by system components such as
+ * the eSIM LPA or Settings that allow the user to manage all subscriptions on the device through
+ * some system-provided user interface.
+ *
* @param list - The subscription info list, can be null.
*/
public void setAvailableSubscriptionInfoList(List<SubscriptionInfo> list) {
@@ -265,7 +313,19 @@ public class ShadowSubscriptionManager {
}
/**
- * Sets the active list of {@link SubscriptionInfo}. This call internally triggers {@link
+ * Sets the accessible list of {@link SubscriptionInfo}. This call internally triggers {@link
+ * OnSubscriptionsChangedListener#onSubscriptionsChanged()} to all the listeners.
+ */
+ public void setAccessibleSubscriptionInfos(SubscriptionInfo... infos) {
+ if (infos == null) {
+ setAccessibleSubscriptionInfoList(ImmutableList.of());
+ } else {
+ setAccessibleSubscriptionInfoList(Arrays.asList(infos));
+ }
+ }
+
+ /**
+ * Sets the available list of {@link SubscriptionInfo}. This call internally triggers {@link
* OnSubscriptionsChangedListener#onSubscriptionsChanged()} to all the listeners.
*/
public void setAvailableSubscriptionInfos(SubscriptionInfo... infos) {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSurface.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSurface.java
index ba8899bb1..be5ebeda7 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSurface.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSurface.java
@@ -1,7 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.Q;
@@ -52,7 +50,7 @@ public class ShadowSurface {
return surfaceTexture;
}
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
protected void finalize() throws Throwable {
// Suppress noisy CloseGuard errors that may exist in SDK 17+.
CloseGuard closeGuard = surfaceReflector.getCloseGuard();
@@ -129,12 +127,12 @@ public class ShadowSurface {
canvasLocked.set(false);
}
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected static Object nativeCreateFromSurfaceTexture(Object surfaceTexture) {
return nativeObject.incrementAndGet();
}
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected static Object nativeCreateFromSurfaceControl(Object surfaceControlNativeObject) {
return nativeObject.incrementAndGet();
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSurfaceControl.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSurfaceControl.java
index b279892c9..0ec68c381 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSurfaceControl.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSurfaceControl.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.N_MR1;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.P;
@@ -22,7 +21,7 @@ import org.robolectric.util.reflector.ForType;
import org.robolectric.versioning.AndroidVersions.U;
/** Shadow for {@link android.view.SurfaceControl} */
-@Implements(value = SurfaceControl.class, isInAndroidSdk = false, minSdk = JELLY_BEAN_MR2)
+@Implements(value = SurfaceControl.class, isInAndroidSdk = false)
public class ShadowSurfaceControl {
private static final AtomicInteger nativeObject = new AtomicInteger();
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemClock.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemClock.java
index 03d756c4e..bba7005c4 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemClock.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemClock.java
@@ -83,6 +83,18 @@ public abstract class ShadowSystemClock {
SystemClock.setCurrentTimeMillis(SystemClock.uptimeMillis() + duration.toMillis());
}
+ /**
+ * In a deep sleep scenario, {@param elapsedRealtime} is advanced for this duration when in deep
+ * sleep whilst {@param uptime} maintains its original value.
+ *
+ * <p>May only be used for {@link LooperMode.Mode#PAUSED}. For {@link LooperMode.Mode#LEGACY},
+ * {@param elapsedRealtime} is equal to {@param uptime}.
+ */
+ public static void simulateDeepSleep(Duration duration) {
+ assertLooperMode(Mode.PAUSED);
+ ShadowPausedSystemClock.deepSleep(duration.toMillis());
+ }
+
@Implementation(minSdk = Q)
protected static Object currentGnssTimeClock() {
if (gnssTimeAvailable) {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemHealthManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemHealthManager.java
new file mode 100644
index 000000000..4eff63dfb
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemHealthManager.java
@@ -0,0 +1,52 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.N;
+
+import android.os.Process;
+import android.os.health.HealthStats;
+import android.os.health.SystemHealthManager;
+import java.util.HashMap;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+/** Shadow for {@link android.os.health.SystemHealthManager} */
+@Implements(value = SystemHealthManager.class, minSdk = N)
+public class ShadowSystemHealthManager {
+
+ private static final HealthStats DEFAULT_HEALTH_STATS =
+ HealthStatsBuilder.newBuilder().setDataType("default_health_stats").build();
+
+ private final HashMap<Integer, HealthStats> uidToHealthStats = new HashMap<>();
+
+ @Implementation
+ protected HealthStats takeMyUidSnapshot() {
+ return takeUidSnapshot(Process.myUid());
+ }
+
+ @Implementation
+ protected HealthStats takeUidSnapshot(int uid) {
+ return uidToHealthStats.getOrDefault(uid, DEFAULT_HEALTH_STATS);
+ }
+
+ @Implementation
+ protected HealthStats[] takeUidSnapshots(int[] uids) {
+ HealthStats[] stats = new HealthStats[uids.length];
+ for (int i = 0; i < uids.length; i++) {
+ stats[i] = takeUidSnapshot(uids[i]);
+ }
+ return stats;
+ }
+
+ /**
+ * Add {@link HealthStats} for the given UID. Calling {@link SystemHealthManager#takeUidSnapshot}
+ * with the given UID will return this HealthStats object.
+ */
+ public void addHealthStatsForUid(int uid, HealthStats stats) {
+ uidToHealthStats.put(uid, stats);
+ }
+
+ /** The same as {@code addHealthStatsForUid(android.os.Process.myUid(), stats)}. */
+ public void addHealthStats(HealthStats stats) {
+ addHealthStatsForUid(Process.myUid(), stats);
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemVibrator.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemVibrator.java
index 41fbf4e7d..1b44991e3 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemVibrator.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemVibrator.java
@@ -1,7 +1,6 @@
package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.KITKAT_WATCH;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.N_MR1;
@@ -47,7 +46,7 @@ public class ShadowSystemVibrator extends ShadowVibrator {
recordVibratePattern(pattern, repeat);
}
- @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = KITKAT_WATCH)
+ @Implementation(maxSdk = KITKAT_WATCH)
protected void vibrate(int owningUid, String owningPackage, long[] pattern, int repeat) {
recordVibratePattern(pattern, repeat);
}
@@ -63,7 +62,7 @@ public class ShadowSystemVibrator extends ShadowVibrator {
recordVibrate(milliseconds);
}
- @Implementation(minSdk = JELLY_BEAN_MR2, maxSdk = KITKAT_WATCH)
+ @Implementation(maxSdk = KITKAT_WATCH)
public void vibrate(int owningUid, String owningPackage, long milliseconds) {
recordVibrate(milliseconds);
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelecomManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelecomManager.java
index 87dbf83e1..2a8459609 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelecomManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelecomManager.java
@@ -1,13 +1,16 @@
package org.robolectric.shadows;
+import static android.os.Build.VERSION.SDK_INT;
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 android.os.Build.VERSION_CODES.N;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.R;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
import static com.google.common.base.Verify.verifyNotNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TargetApi;
import android.bluetooth.BluetoothDevice;
@@ -27,7 +30,6 @@ import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
import android.text.TextUtils;
import android.util.ArrayMap;
-import androidx.annotation.Nullable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
@@ -245,9 +247,22 @@ public class ShadowTelecomManager {
@Implementation
protected void registerPhoneAccount(PhoneAccount account) {
+ account = adjustCapabilities(account);
accounts.put(account.getAccountHandle(), account);
}
+ private PhoneAccount adjustCapabilities(PhoneAccount account) {
+ // Mirror the capabilities adjustments done in com.android.server.telecom.PhoneAccountRegistrar.
+ if (SDK_INT >= UPSIDE_DOWN_CAKE
+ && account.hasCapabilities(PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS)
+ && !account.hasCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)) {
+ return account.toBuilder()
+ .setCapabilities(account.getCapabilities() | PhoneAccount.CAPABILITY_SELF_MANAGED)
+ .build();
+ }
+ return account;
+ }
+
@Implementation
protected void unregisterPhoneAccount(PhoneAccountHandle accountHandle) {
accounts.remove(accountHandle);
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephony.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephony.java
index 7f3f57aec..aba5d2ad2 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephony.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephony.java
@@ -2,16 +2,15 @@ package org.robolectric.shadows;
import android.annotation.Nullable;
import android.content.Context;
-import android.os.Build.VERSION_CODES;
import android.provider.Telephony;
import android.provider.Telephony.Sms;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.Resetter;
-@Implements(value = Telephony.class, minSdk = VERSION_CODES.KITKAT)
+@Implements(value = Telephony.class)
public class ShadowTelephony {
- @Implements(value = Sms.class, minSdk = VERSION_CODES.KITKAT)
+ @Implements(value = Sms.class)
public static class ShadowSms {
@Nullable private static String defaultSmsPackage;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java
index 7b2a756b2..871e9f3a8 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java
@@ -1,8 +1,6 @@
package org.robolectric.shadows;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
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;
@@ -54,19 +52,17 @@ import android.telephony.TelephonyManager.CellInfoCallback;
import android.telephony.VisualVoicemailSmsFilterSettings;
import android.telephony.emergency.EmergencyNumber;
import android.text.TextUtils;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import android.util.SparseIntArray;
import com.google.common.base.Ascii;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -88,17 +84,24 @@ public class ShadowTelephonyManager {
@RealObject protected TelephonyManager realTelephonyManager;
- private final Map<PhoneStateListener, Integer> phoneStateRegistrations = new HashMap<>();
+ private final Map<PhoneStateListener, Integer> phoneStateRegistrations =
+ Collections.synchronizedMap(new LinkedHashMap<>());
private final /*List<TelephonyCallback>*/ List<Object> telephonyCallbackRegistrations =
new ArrayList<>();
- private final Map<Integer, String> slotIndexToDeviceId = new HashMap<>();
- private final Map<Integer, String> slotIndexToImei = new HashMap<>();
- private final Map<Integer, String> slotIndexToMeid = new HashMap<>();
- private final Map<PhoneAccountHandle, Boolean> voicemailVibrationEnabledMap = new HashMap<>();
- private final Map<PhoneAccountHandle, Uri> voicemailRingtoneUriMap = new HashMap<>();
- private final Map<PhoneAccountHandle, TelephonyManager> phoneAccountToTelephonyManagers =
- new HashMap<>();
- private final Map<PhoneAccountHandle, Integer> phoneAccountHandleSubscriptionId = new HashMap<>();
+ private static final Map<Integer, String> slotIndexToDeviceId =
+ Collections.synchronizedMap(new LinkedHashMap<>());
+ private static final Map<Integer, String> slotIndexToImei =
+ Collections.synchronizedMap(new LinkedHashMap<>());
+ private static final Map<Integer, String> slotIndexToMeid =
+ Collections.synchronizedMap(new LinkedHashMap<>());
+ private static final Map<PhoneAccountHandle, Boolean> voicemailVibrationEnabledMap =
+ Collections.synchronizedMap(new LinkedHashMap<>());
+ private static final Map<PhoneAccountHandle, Uri> voicemailRingtoneUriMap =
+ Collections.synchronizedMap(new LinkedHashMap<>());
+ private static final Map<PhoneAccountHandle, TelephonyManager> phoneAccountToTelephonyManagers =
+ Collections.synchronizedMap(new LinkedHashMap<>());
+ private static final Map<PhoneAccountHandle, Integer> phoneAccountHandleSubscriptionId =
+ Collections.synchronizedMap(new LinkedHashMap<>());
private PhoneStateListener lastListener;
private /*TelephonyCallback*/ Object lastTelephonyCallback;
@@ -117,47 +120,53 @@ public class ShadowTelephonyManager {
private String simOperator = "";
private String simOperatorName;
private String simSerialNumber;
- private boolean readPhoneStatePermission = true;
+ private static volatile boolean readPhoneStatePermission = true;
private int phoneType = TelephonyManager.PHONE_TYPE_GSM;
private String line1Number;
private int networkType;
private int dataNetworkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
private int voiceNetworkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
- private List<CellInfo> allCellInfo = Collections.emptyList();
- private List<CellInfo> callbackCellInfos = null;
- private CellLocation cellLocation = null;
+ private static volatile List<CellInfo> allCellInfo = Collections.emptyList();
+ private static volatile List<CellInfo> callbackCellInfos = null;
+ private static volatile CellLocation cellLocation = null;
private int callState = CALL_STATE_IDLE;
private int dataState = TelephonyManager.DATA_DISCONNECTED;
private int dataActivity = TelephonyManager.DATA_ACTIVITY_NONE;
private String incomingPhoneNumber = null;
- private boolean isSmsCapable = true;
- private boolean voiceCapable = true;
+ private static volatile boolean isSmsCapable = true;
+ private static volatile boolean voiceCapable = true;
private String voiceMailNumber;
private String voiceMailAlphaTag;
- private int phoneCount = 1;
- private int activeModemCount = 1;
- private Map<Integer, TelephonyManager> subscriptionIdsToTelephonyManagers = new HashMap<>();
+ private static volatile int phoneCount = 1;
+ private static volatile int activeModemCount = 1;
+ private static volatile Map<Integer, TelephonyManager> subscriptionIdsToTelephonyManagers =
+ Collections.synchronizedMap(new LinkedHashMap<>());
private PersistableBundle carrierConfig;
private ServiceState serviceState;
private boolean isNetworkRoaming;
- private final SparseIntArray simStates = new SparseIntArray();
- private final SparseIntArray currentPhoneTypes = new SparseIntArray();
- private final SparseArray<List<String>> carrierPackageNames = new SparseArray<>();
- private final Map<Integer, String> simCountryIsoMap = new HashMap<>();
+ private static final Map<Integer, Integer> simStates =
+ Collections.synchronizedMap(new LinkedHashMap<>());
+ private static final Map<Integer, Integer> currentPhoneTypes =
+ Collections.synchronizedMap(new LinkedHashMap<>());
+ private static final Map<Integer, List<String>> carrierPackageNames =
+ Collections.synchronizedMap(new LinkedHashMap<>());
+ private static final Map<Integer, String> simCountryIsoMap =
+ Collections.synchronizedMap(new LinkedHashMap<>());
private int simCarrierId;
private int carrierIdFromSimMccMnc;
private String subscriberId;
- private /*UiccSlotInfo[]*/ Object uiccSlotInfos;
- private /*UiccCardInfo[]*/ Object uiccCardsInfo = new ArrayList<>();
+ private static volatile /*UiccSlotInfo[]*/ Object uiccSlotInfos;
+ private static volatile /*List<UiccCardInfo>*/ Object uiccCardsInfo = new ArrayList<>();
private String visualVoicemailPackageName = null;
private SignalStrength signalStrength;
private boolean dataEnabled = false;
private final Set<Integer> dataDisabledReasons = new HashSet<>();
private boolean isRttSupported;
- private boolean isTtyModeSupported;
- private final SparseBooleanArray subIdToHasCarrierPrivileges = new SparseBooleanArray();
- private final List<String> sentDialerSpecialCodes = new ArrayList<>();
- private boolean hearingAidCompatibilitySupported = false;
+ private static volatile boolean isTtyModeSupported;
+ private static final Map<Integer, Boolean> subIdToHasCarrierPrivileges =
+ Collections.synchronizedMap(new LinkedHashMap<>());
+ private static final List<String> sentDialerSpecialCodes = new ArrayList<>();
+ private static volatile boolean hearingAidCompatibilitySupported = false;
private int requestCellInfoUpdateErrorCode = 0;
private Throwable requestCellInfoUpdateDetail = null;
private Object telephonyDisplayInfo;
@@ -165,7 +174,7 @@ public class ShadowTelephonyManager {
private static int callComposerStatus = 0;
private VisualVoicemailSmsParams lastVisualVoicemailSmsParams;
private VisualVoicemailSmsFilterSettings visualVoicemailSmsFilterSettings;
- private boolean emergencyCallbackMode;
+ private static volatile boolean emergencyCallbackMode;
private static Map<Integer, List<EmergencyNumber>> emergencyNumbersList;
/**
@@ -176,17 +185,45 @@ public class ShadowTelephonyManager {
*/
private Object callback;
- private /*PhoneCapability*/ Object phoneCapability;
+ private static volatile /*PhoneCapability*/ Object phoneCapability;
- {
- resetSimStates();
- resetSimCountryIsos();
+ static {
+ resetAllSimStates();
+ resetAllSimCountryIsos();
}
@Resetter
public static void reset() {
+ subscriptionIdsToTelephonyManagers.clear();
+ resetAllSimStates();
+ currentPhoneTypes.clear();
+ carrierPackageNames.clear();
+ resetAllSimCountryIsos();
+ slotIndexToDeviceId.clear();
+ slotIndexToImei.clear();
+ slotIndexToMeid.clear();
+ voicemailVibrationEnabledMap.clear();
+ voicemailRingtoneUriMap.clear();
+ phoneAccountToTelephonyManagers.clear();
+ phoneAccountHandleSubscriptionId.clear();
+ subIdToHasCarrierPrivileges.clear();
+ allCellInfo = Collections.emptyList();
+ cellLocation = null;
+ callbackCellInfos = null;
+ uiccSlotInfos = null;
+ uiccCardsInfo = new ArrayList<>();
callComposerStatus = 0;
+ emergencyCallbackMode = false;
emergencyNumbersList = null;
+ phoneCapability = null;
+ isTtyModeSupported = false;
+ readPhoneStatePermission = true;
+ isSmsCapable = true;
+ voiceCapable = true;
+ phoneCount = 1;
+ activeModemCount = 1;
+ sentDialerSpecialCodes.clear();
+ hearingAidCompatibilitySupported = false;
}
@Implementation(minSdk = S)
@@ -217,7 +254,7 @@ public class ShadowTelephonyManager {
}
public void setPhoneCapability(/*PhoneCapability*/ Object phoneCapability) {
- this.phoneCapability = phoneCapability;
+ ShadowTelephonyManager.phoneCapability = phoneCapability;
}
@Implementation(minSdk = S)
@@ -548,11 +585,21 @@ public class ShadowTelephonyManager {
}
/** Clears {@code subId} to simCountryIso mapping and resets to default state. */
- public void resetSimCountryIsos() {
+ public static void resetAllSimCountryIsos() {
simCountryIsoMap.clear();
simCountryIsoMap.put(0, "");
}
+ /**
+ * Clears {@code subId} to simCountryIso mapping and resets to default state.
+ *
+ * @deprecated for resetAllSimCountryIsos
+ */
+ @Deprecated
+ public void resetSimCountryIsos() {
+ resetAllSimCountryIsos();
+ }
+
@Implementation
protected int getSimState() {
return getSimState(/* slotIndex= */ 0);
@@ -570,12 +617,12 @@ public class ShadowTelephonyManager {
@Implementation(minSdk = O)
protected int getSimState(int slotIndex) {
- return simStates.get(slotIndex, TelephonyManager.SIM_STATE_UNKNOWN);
+ return simStates.getOrDefault(slotIndex, TelephonyManager.SIM_STATE_UNKNOWN);
}
/** Sets the UICC slots information returned by {@link #getUiccSlotsInfo()}. */
public void setUiccSlotsInfo(/*UiccSlotInfo[]*/ Object uiccSlotsInfos) {
- this.uiccSlotInfos = uiccSlotsInfos;
+ ShadowTelephonyManager.uiccSlotInfos = uiccSlotsInfos;
}
/** Returns the UICC slots information set by {@link #setUiccSlotsInfo}. */
@@ -586,25 +633,35 @@ public class ShadowTelephonyManager {
}
/** Sets the UICC cards information returned by {@link #getUiccCardsInfo()}. */
- public void setUiccCardsInfo(/*UiccCardsInfo[]*/ Object uiccCardsInfo) {
- this.uiccCardsInfo = uiccCardsInfo;
+ public void setUiccCardsInfo(/*List<UiccCardInfo>*/ Object uiccCardsInfo) {
+ ShadowTelephonyManager.uiccCardsInfo = uiccCardsInfo;
}
/** Returns the UICC cards information set by {@link #setUiccCardsInfo}. */
@Implementation(minSdk = Q)
@HiddenApi
- protected /*UiccSlotInfo[]*/ Object getUiccCardsInfo() {
+ protected /*List<UiccCardInfo>*/ Object getUiccCardsInfo() {
return uiccCardsInfo;
}
/** Clears {@code slotIndex} to state mapping and resets to default state. */
- public void resetSimStates() {
+ public static void resetAllSimStates() {
simStates.clear();
simStates.put(0, TelephonyManager.SIM_STATE_READY);
}
+ /**
+ * Clears {@code slotIndex} to state mapping and resets to default state.
+ *
+ * @deprecated use resetAllSimStates()
+ */
+ @Deprecated
+ public void resetSimStates() {
+ resetAllSimStates();
+ }
+
public void setReadPhoneStatePermission(boolean readPhoneStatePermission) {
- this.readPhoneStatePermission = readPhoneStatePermission;
+ ShadowTelephonyManager.readPhoneStatePermission = readPhoneStatePermission;
}
private void checkReadPhoneStatePermission() {
@@ -712,18 +769,16 @@ public class ShadowTelephonyManager {
this.voiceNetworkType = voiceNetworkType;
}
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
protected List<CellInfo> getAllCellInfo() {
return allCellInfo;
}
public void setAllCellInfo(List<CellInfo> allCellInfo) {
- this.allCellInfo = allCellInfo;
+ ShadowTelephonyManager.allCellInfo = allCellInfo;
- if (VERSION.SDK_INT >= JELLY_BEAN_MR1) {
- for (PhoneStateListener listener : getListenersForFlags(LISTEN_CELL_INFO)) {
- listener.onCellInfoChanged(allCellInfo);
- }
+ for (PhoneStateListener listener : getListenersForFlags(LISTEN_CELL_INFO)) {
+ listener.onCellInfoChanged(allCellInfo);
}
if (VERSION.SDK_INT >= S) {
for (CellInfoListener listener : getCallbackForListener(CellInfoListener.class)) {
@@ -739,6 +794,7 @@ public class ShadowTelephonyManager {
@Implementation(minSdk = Q)
protected void requestCellInfoUpdate(Object cellInfoExecutor, Object cellInfoCallback) {
Executor executor = (Executor) cellInfoExecutor;
+ List<CellInfo> callbackCellInfos = ShadowTelephonyManager.callbackCellInfos;
if (callbackCellInfos == null) {
// ignore
} else if (requestCellInfoUpdateErrorCode != 0 || requestCellInfoUpdateDetail != null) {
@@ -768,7 +824,7 @@ public class ShadowTelephonyManager {
* setAllCellInfo}.
*/
public void setCallbackCellInfos(List<CellInfo> callbackCellInfos) {
- this.callbackCellInfos = callbackCellInfos;
+ ShadowTelephonyManager.callbackCellInfos = callbackCellInfos;
}
/**
@@ -782,12 +838,11 @@ public class ShadowTelephonyManager {
@Implementation
protected CellLocation getCellLocation() {
- return this.cellLocation;
+ return ShadowTelephonyManager.cellLocation;
}
public void setCellLocation(CellLocation cellLocation) {
- this.cellLocation = cellLocation;
-
+ ShadowTelephonyManager.cellLocation = cellLocation;
for (PhoneStateListener listener : getListenersForFlags(LISTEN_CELL_LOCATION)) {
listener.onCellLocationChanged(cellLocation);
}
@@ -798,7 +853,7 @@ public class ShadowTelephonyManager {
}
}
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected String getGroupIdLevel1() {
checkReadPhoneStatePermission();
return this.groupIdLevel1;
@@ -810,13 +865,18 @@ public class ShadowTelephonyManager {
@CallSuper
protected void initListener(PhoneStateListener listener, int flags) {
+ // grab the state "atomically" before doing callbacks, in case they modify the state
+ String incomingPhoneNumber = this.incomingPhoneNumber;
+ List<CellInfo> allCellInfo = ShadowTelephonyManager.allCellInfo;
+ CellLocation cellLocation = ShadowTelephonyManager.cellLocation;
+ Object telephonyDisplayInfo = this.telephonyDisplayInfo;
+ ServiceState serviceState = this.serviceState;
+
if ((flags & LISTEN_CALL_STATE) != 0) {
listener.onCallStateChanged(callState, incomingPhoneNumber);
}
if ((flags & LISTEN_CELL_INFO) != 0) {
- if (VERSION.SDK_INT >= JELLY_BEAN_MR1) {
- listener.onCellInfoChanged(allCellInfo);
- }
+ listener.onCellInfoChanged(allCellInfo);
}
if ((flags & LISTEN_CELL_LOCATION) != 0) {
listener.onCellLocationChanged(cellLocation);
@@ -837,6 +897,12 @@ public class ShadowTelephonyManager {
if (VERSION.SDK_INT < S) {
return;
}
+ // grab the state "atomically" before doing callbacks, in case they modify the state
+ int callState = this.callState;
+ List<CellInfo> allCellInfo = ShadowTelephonyManager.allCellInfo;
+ CellLocation cellLocation = ShadowTelephonyManager.cellLocation;
+ Object telephonyDisplayInfo = this.telephonyDisplayInfo;
+ ServiceState serviceState = this.serviceState;
if (callback instanceof CallStateListener) {
((CallStateListener) callback).onCallStateChanged(callState);
@@ -858,7 +924,7 @@ public class ShadowTelephonyManager {
protected Iterable<PhoneStateListener> getListenersForFlags(int flags) {
return Iterables.filter(
- phoneStateRegistrations.keySet(),
+ ImmutableSet.copyOf(phoneStateRegistrations.keySet()),
new Predicate<PhoneStateListener>() {
@Override
public boolean apply(PhoneStateListener input) {
@@ -874,7 +940,7 @@ public class ShadowTelephonyManager {
*/
protected <T> Iterable<T> getCallbackForListener(Class<T> clazz) {
// Only selects TelephonyCallback with matching class.
- return Iterables.filter(telephonyCallbackRegistrations, clazz);
+ return Iterables.filter(ImmutableList.copyOf(telephonyCallbackRegistrations), clazz);
}
/**
@@ -887,7 +953,7 @@ public class ShadowTelephonyManager {
/** Sets the value returned by {@link TelephonyManager#isSmsCapable()}. */
public void setIsSmsCapable(boolean isSmsCapable) {
- this.isSmsCapable = isSmsCapable;
+ ShadowTelephonyManager.isSmsCapable = isSmsCapable;
}
/**
@@ -947,7 +1013,7 @@ public class ShadowTelephonyManager {
/** Sets the value returned by {@link TelephonyManager#getPhoneCount()}. */
public void setPhoneCount(int phoneCount) {
- this.phoneCount = phoneCount;
+ ShadowTelephonyManager.phoneCount = phoneCount;
}
/** Returns 1 by default or the value specified via {@link #setActiveModemCount(int)}. */
@@ -958,7 +1024,7 @@ public class ShadowTelephonyManager {
/** Sets the value returned by {@link TelephonyManager#getActiveModemCount()}. */
public void setActiveModemCount(int activeModemCount) {
- this.activeModemCount = activeModemCount;
+ ShadowTelephonyManager.activeModemCount = activeModemCount;
}
/**
@@ -985,7 +1051,7 @@ public class ShadowTelephonyManager {
/** Sets the value returned by {@link #isVoiceCapable()}. */
public void setVoiceCapable(boolean voiceCapable) {
- this.voiceCapable = voiceCapable;
+ ShadowTelephonyManager.voiceCapable = voiceCapable;
}
/**
@@ -1109,7 +1175,7 @@ public class ShadowTelephonyManager {
@Implementation(minSdk = M)
@HiddenApi
protected int getCurrentPhoneType(int subId) {
- return currentPhoneTypes.get(subId, TelephonyManager.PHONE_TYPE_NONE);
+ return currentPhoneTypes.getOrDefault(subId, TelephonyManager.PHONE_TYPE_NONE);
}
/** Sets the phone type for the given {@code subId}. */
@@ -1118,7 +1184,7 @@ public class ShadowTelephonyManager {
}
/** Removes all {@code subId} to {@code phoneType} mappings. */
- public void clearPhoneTypes() {
+ public static void clearPhoneTypes() {
currentPhoneTypes.clear();
}
@@ -1288,7 +1354,7 @@ public class ShadowTelephonyManager {
* @param emergencyCallbackMode whether the device is in ECBM or not.
*/
public void setEmergencyCallbackMode(boolean emergencyCallbackMode) {
- this.emergencyCallbackMode = emergencyCallbackMode;
+ ShadowTelephonyManager.emergencyCallbackMode = emergencyCallbackMode;
}
@Implementation(minSdk = Build.VERSION_CODES.O)
@@ -1377,7 +1443,7 @@ public class ShadowTelephonyManager {
/** Sets the value to be returned by {@link #isTtyModeSupported()} */
public void setTtyModeSupported(boolean isTtyModeSupported) {
- this.isTtyModeSupported = isTtyModeSupported;
+ ShadowTelephonyManager.isTtyModeSupported = isTtyModeSupported;
}
/**
@@ -1386,7 +1452,7 @@ public class ShadowTelephonyManager {
@Implementation(minSdk = Build.VERSION_CODES.N)
@HiddenApi
protected boolean hasCarrierPrivileges(int subId) {
- return subIdToHasCarrierPrivileges.get(subId);
+ return subIdToHasCarrierPrivileges.getOrDefault(subId, false);
}
public void setHasCarrierPrivileges(boolean hasCarrierPrivileges) {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTextToSpeech.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTextToSpeech.java
index e5b6b5ee7..83be824d9 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTextToSpeech.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTextToSpeech.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.robolectric.util.reflector.Reflector.reflector;
@@ -129,31 +128,29 @@ public class ShadowTextToSpeech {
spokenTextList.add(text.toString());
this.queueMode = queueMode;
- if (RuntimeEnvironment.getApiLevel() >= ICE_CREAM_SANDWICH_MR1) {
- if (utteranceId != null) {
- // The onStart and onDone callbacks are normally delivered asynchronously. Since in
- // Robolectric we don't need the wait for TTS package, the asynchronous callbacks are
- // simulated by posting it on a handler. The behavior of the callback can be changed for
- // each individual test by changing the idling mode of the foreground scheduler.
- Handler handler = new Handler(Looper.getMainLooper());
- handler.post(
- () -> {
- UtteranceProgressListener utteranceProgressListener = getUtteranceProgressListener();
- if (utteranceProgressListener != null) {
- utteranceProgressListener.onStart(utteranceId);
- }
- // The onDone callback is posted in a separate run-loop from onStart, so that tests
- // can pause the scheduler and test the behavior between these two callbacks.
- handler.post(
- () -> {
- UtteranceProgressListener utteranceProgressListener2 =
- getUtteranceProgressListener();
- if (utteranceProgressListener2 != null) {
- utteranceProgressListener2.onDone(utteranceId);
- }
- });
- });
- }
+ if (utteranceId != null) {
+ // The onStart and onDone callbacks are normally delivered asynchronously. Since in
+ // Robolectric we don't need the wait for TTS package, the asynchronous callbacks are
+ // simulated by posting it on a handler. The behavior of the callback can be changed for
+ // each individual test by changing the idling mode of the foreground scheduler.
+ Handler handler = new Handler(Looper.getMainLooper());
+ handler.post(
+ () -> {
+ UtteranceProgressListener utteranceProgressListener = getUtteranceProgressListener();
+ if (utteranceProgressListener != null) {
+ utteranceProgressListener.onStart(utteranceId);
+ }
+ // The onDone callback is posted in a separate run-loop from onStart, so that tests
+ // can pause the scheduler and test the behavior between these two callbacks.
+ handler.post(
+ () -> {
+ UtteranceProgressListener utteranceProgressListener2 =
+ getUtteranceProgressListener();
+ if (utteranceProgressListener2 != null) {
+ utteranceProgressListener2.onDone(utteranceId);
+ }
+ });
+ });
}
return TextToSpeech.SUCCESS;
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowToneGenerator.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowToneGenerator.java
index f24497647..e6c08a998 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowToneGenerator.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowToneGenerator.java
@@ -1,7 +1,7 @@
package org.robolectric.shadows;
import android.media.ToneGenerator;
-import androidx.annotation.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import java.time.Duration;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTrace.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTrace.java
index 16e501563..63a7d7c8c 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTrace.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTrace.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.Q;
import static com.google.common.base.Verify.verifyNotNull;
@@ -51,7 +50,7 @@ public class ShadowTrace {
private static long tags = TRACE_TAG_APP;
/** Starts a new trace section with given name. */
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected static void beginSection(String sectionName) {
if (tags == 0) {
return;
@@ -63,7 +62,7 @@ public class ShadowTrace {
}
/** Ends the most recent active trace section. */
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected static void endSection() {
if (tags == 0) {
return;
@@ -112,12 +111,12 @@ public class ShadowTrace {
previousAsyncSections.add(section);
}
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected static long nativeGetEnabledTags() {
return tags;
}
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected static void setAppTracingAllowed(boolean appTracingAllowed) {
tags = appTracingAllowed ? TRACE_TAG_APP : 0;
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTransportControls.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTransportControls.java
new file mode 100644
index 000000000..cb5d112ab
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTransportControls.java
@@ -0,0 +1,222 @@
+package org.robolectric.shadows;
+
+import static android.media.session.PlaybackState.ACTION_PAUSE;
+import static android.media.session.PlaybackState.ACTION_PLAY;
+import static android.media.session.PlaybackState.ACTION_PLAY_FROM_SEARCH;
+import static android.media.session.PlaybackState.ACTION_PLAY_FROM_URI;
+import static android.media.session.PlaybackState.ACTION_PREPARE_FROM_SEARCH;
+import static android.media.session.PlaybackState.ACTION_PREPARE_FROM_URI;
+import static android.media.session.PlaybackState.ACTION_SEEK_TO;
+import static android.media.session.PlaybackState.ACTION_SET_RATING;
+import static android.media.session.PlaybackState.ACTION_SKIP_TO_NEXT;
+import static android.media.session.PlaybackState.ACTION_SKIP_TO_PREVIOUS;
+import static android.media.session.PlaybackState.ACTION_SKIP_TO_QUEUE_ITEM;
+import static android.media.session.PlaybackState.ACTION_STOP;
+import static android.media.session.PlaybackState.STATE_NONE;
+import static android.os.Build.VERSION_CODES.LOLLIPOP;
+import static android.os.Build.VERSION_CODES.M;
+import static android.os.Build.VERSION_CODES.N;
+import static org.robolectric.util.reflector.Reflector.reflector;
+
+import android.annotation.Nullable;
+import android.media.Rating;
+import android.media.session.MediaController.TransportControls;
+import android.net.Uri;
+import android.os.Bundle;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.RealObject;
+import org.robolectric.util.reflector.Direct;
+import org.robolectric.util.reflector.ForType;
+
+/**
+ * Shadow class for using {@link TransportControls} in tests.
+ *
+ * <p>TransportControls should always be created by first creating a corresponding MediaController;
+ * *NOT*, for instance, via Shadows.newInstanceOf(TransportControls.class).
+ */
+@Implements(value = TransportControls.class, minSdk = LOLLIPOP)
+public class ShadowTransportControls {
+ @RealObject protected TransportControls realTransportControls;
+
+ private long lastPerformedAction = STATE_NONE;
+
+ @Nullable private String customAction;
+ @Nullable private Bundle customActionArgs;
+
+ /** The current item id in playlist set by {@link TransportControls#skipToQueueItem(long)}. */
+ private long queueItemId;
+
+ /** The rating value set by last call of {@link TransportControls#setRating(Rating)}. */
+ @Nullable private Rating rating;
+
+ /** The current position in milliseconds set by {@link TransportControls#seekTo(long)} method. */
+ private long seekToPositionMs;
+
+ /**
+ * URI argument provided when {@link TransportControls#prepareFromUri(Uri, Bundle)} or {@link
+ * TransportControls#playFromUri(Uri, Bundle)} was called.
+ */
+ @Nullable private Uri uri;
+
+ @Implementation
+ protected void pause() {
+ lastPerformedAction = ACTION_PAUSE;
+ reflector(TransportControlsReflector.class, realTransportControls).pause();
+ }
+
+ @Implementation
+ protected void play() {
+ lastPerformedAction = ACTION_PLAY;
+ reflector(TransportControlsReflector.class, realTransportControls).play();
+ }
+
+ @Implementation
+ protected void playFromSearch(String query, Bundle extras) {
+ lastPerformedAction = ACTION_PLAY_FROM_SEARCH;
+ reflector(TransportControlsReflector.class, realTransportControls)
+ .playFromSearch(query, extras);
+ }
+
+ @Implementation(minSdk = M)
+ protected void playFromUri(Uri uri, Bundle extras) {
+ lastPerformedAction = ACTION_PLAY_FROM_URI;
+ this.uri = uri;
+ reflector(TransportControlsReflector.class, realTransportControls).playFromUri(uri, extras);
+ }
+
+ @Implementation(minSdk = N)
+ protected void prepareFromSearch(String query, Bundle extras) {
+ lastPerformedAction = ACTION_PREPARE_FROM_SEARCH;
+ reflector(TransportControlsReflector.class, realTransportControls)
+ .prepareFromSearch(query, extras);
+ }
+
+ @Implementation(minSdk = N)
+ protected void prepareFromUri(Uri uri, Bundle extras) {
+ lastPerformedAction = ACTION_PREPARE_FROM_URI;
+ this.uri = uri;
+ reflector(TransportControlsReflector.class, realTransportControls).prepareFromUri(uri, extras);
+ }
+
+ @Implementation
+ protected void seekTo(long pos) {
+ lastPerformedAction = ACTION_SEEK_TO;
+ seekToPositionMs = pos;
+ reflector(TransportControlsReflector.class, realTransportControls).seekTo(pos);
+ }
+
+ @Implementation
+ protected void sendCustomAction(String action, Bundle args) {
+ customAction = action;
+ customActionArgs = args;
+ reflector(TransportControlsReflector.class, realTransportControls)
+ .sendCustomAction(action, args);
+ }
+
+ @Implementation
+ protected void setRating(Rating rating) {
+ lastPerformedAction = ACTION_SET_RATING;
+ this.rating = rating;
+ reflector(TransportControlsReflector.class, realTransportControls).setRating(rating);
+ }
+
+ @Implementation
+ protected void skipToNext() {
+ lastPerformedAction = ACTION_SKIP_TO_NEXT;
+ reflector(TransportControlsReflector.class, realTransportControls).skipToNext();
+ }
+
+ @Implementation
+ protected void skipToPrevious() {
+ lastPerformedAction = ACTION_SKIP_TO_PREVIOUS;
+ reflector(TransportControlsReflector.class, realTransportControls).skipToPrevious();
+ }
+
+ @Implementation
+ protected void skipToQueueItem(long id) {
+ lastPerformedAction = ACTION_SKIP_TO_QUEUE_ITEM;
+ queueItemId = id;
+ reflector(TransportControlsReflector.class, realTransportControls).skipToQueueItem(id);
+ }
+
+ @Implementation
+ protected void stop() {
+ lastPerformedAction = ACTION_STOP;
+ reflector(TransportControlsReflector.class, realTransportControls).stop();
+ }
+
+ public long getLastPerformedAction() {
+ return lastPerformedAction;
+ }
+
+ @Nullable
+ public String getCustomAction() {
+ return customAction;
+ }
+
+ @Nullable
+ public Bundle getCustomActionArgs() {
+ return customActionArgs;
+ }
+
+ public long getSeekToPositionMs() {
+ return seekToPositionMs;
+ }
+
+ @Nullable
+ public Uri getUri() {
+ return uri;
+ }
+
+ @Nullable
+ public Rating getRating() {
+ return rating;
+ }
+
+ public long getQueueItemId() {
+ return queueItemId;
+ }
+
+ @ForType(TransportControls.class)
+ private interface TransportControlsReflector {
+ @Direct
+ void pause();
+
+ @Direct
+ void play();
+
+ @Direct
+ void playFromSearch(String query, Bundle extras);
+
+ @Direct
+ void playFromUri(Uri uri, Bundle extras);
+
+ @Direct
+ void prepareFromSearch(String query, Bundle extras);
+
+ @Direct
+ void prepareFromUri(Uri uri, Bundle extras);
+
+ @Direct
+ void seekTo(long pos);
+
+ @Direct
+ void sendCustomAction(String action, Bundle args);
+
+ @Direct
+ void setRating(Rating rating);
+
+ @Direct
+ void skipToNext();
+
+ @Direct
+ void skipToPrevious();
+
+ @Direct
+ void skipToQueueItem(long id);
+
+ @Direct
+ void stop();
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUIModeManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUIModeManager.java
index 04c574401..af3cfe8fe 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUIModeManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUIModeManager.java
@@ -12,9 +12,11 @@ import android.content.res.Configuration;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.provider.Settings;
-import androidx.annotation.GuardedBy;
+import com.android.internal.annotations.GuardedBy;
import com.google.common.collect.ImmutableSet;
+import java.util.HashMap;
import java.util.HashSet;
+import java.util.Map;
import java.util.Set;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.HiddenApi;
@@ -32,7 +34,7 @@ public class ShadowUIModeManager {
public int lastFlags;
public int lastCarModePriority;
private int currentApplicationNightMode = 0;
- private final Set<Integer> activeProjectionTypes = new HashSet<>();
+ private final Map<Integer, Set<String>> activeProjectionTypes = new HashMap<>();
private boolean failOnProjectionToggle;
private static final ImmutableSet<Integer> VALID_NIGHT_MODES =
@@ -116,12 +118,22 @@ public class ShadowUIModeManager {
}
}
+ @Implementation(minSdk = VERSION_CODES.S)
+ protected Set<String> getProjectingPackages(int projectionType) {
+ if (projectionType == UiModeManager.PROJECTION_TYPE_ALL) {
+ Set<String> projections = new HashSet<>();
+ activeProjectionTypes.values().forEach(projections::addAll);
+ return projections;
+ }
+ return activeProjectionTypes.getOrDefault(projectionType, new HashSet<>());
+ }
+
public int getApplicationNightMode() {
return currentApplicationNightMode;
}
public Set<Integer> getActiveProjectionTypes() {
- return new HashSet<>(activeProjectionTypes);
+ return new HashSet<>(activeProjectionTypes.keySet());
}
public void setFailOnProjectionToggle(boolean failOnProjectionToggle) {
@@ -143,7 +155,10 @@ public class ShadowUIModeManager {
if (failOnProjectionToggle) {
return false;
}
- activeProjectionTypes.add(projectionType);
+ Set<String> projections = activeProjectionTypes.getOrDefault(projectionType, new HashSet<>());
+ projections.add(RuntimeEnvironment.getApplication().getPackageName());
+ activeProjectionTypes.put(projectionType, projections);
+
return true;
}
@@ -156,7 +171,19 @@ public class ShadowUIModeManager {
if (failOnProjectionToggle) {
return false;
}
- return activeProjectionTypes.remove(projectionType);
+ String packageName = RuntimeEnvironment.getApplication().getPackageName();
+ Set<String> projections = activeProjectionTypes.getOrDefault(projectionType, new HashSet<>());
+ if (projections.contains(packageName)) {
+ projections.remove(packageName);
+ if (projections.isEmpty()) {
+ activeProjectionTypes.remove(projectionType);
+ } else {
+ activeProjectionTypes.put(projectionType, projections);
+ }
+ return true;
+ }
+
+ return false;
}
@Implementation(minSdk = TIRAMISU)
@@ -228,8 +255,10 @@ public class ShadowUIModeManager {
private void assertHasPermission(String... permissions) {
Context context = RuntimeEnvironment.getApplication();
for (String permission : permissions) {
- if (context.getPackageManager().checkPermission(permission, context.getPackageName())
- != PackageManager.PERMISSION_GRANTED) {
+ // Check both the Runtime based and Manifest based permissions
+ if (context.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED
+ && context.getPackageManager().checkPermission(permission, context.getPackageName())
+ != PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Missing required permission: " + permission);
}
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUiAutomation.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUiAutomation.java
index 956afb008..bfe39ea95 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUiAutomation.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUiAutomation.java
@@ -2,8 +2,6 @@ package org.robolectric.shadows;
import static android.app.UiAutomation.ROTATION_FREEZE_0;
import static android.app.UiAutomation.ROTATION_FREEZE_180;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.TIRAMISU;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
@@ -25,10 +23,8 @@ import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Point;
-import android.os.Build;
import android.os.IBinder;
import android.provider.Settings;
-import android.util.Log;
import android.view.Display;
import android.view.InputEvent;
import android.view.KeyEvent;
@@ -37,7 +33,6 @@ import android.view.View;
import android.view.ViewRootImpl;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
-import android.view.WindowManagerImpl;
import androidx.test.runner.lifecycle.ActivityLifecycleMonitor;
import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
import androidx.test.runner.lifecycle.Stage;
@@ -55,7 +50,7 @@ import org.robolectric.annotation.Implements;
import org.robolectric.util.ReflectionHelpers;
/** Shadow for {@link UiAutomation}. */
-@Implements(value = UiAutomation.class, minSdk = JELLY_BEAN_MR2)
+@Implements(value = UiAutomation.class)
public class ShadowUiAutomation {
private static final Predicate<Root> IS_FOCUSABLE = hasLayoutFlag(FLAG_NOT_FOCUSABLE).negate();
@@ -69,18 +64,11 @@ public class ShadowUiAutomation {
* Sets the animation scale, see {@link UiAutomation#setAnimationScale(float)}. Provides backwards
* compatible access to SDKs < T.
*/
- @SuppressWarnings("deprecation")
public static void setAnimationScaleCompat(float scale) {
ContentResolver cr = RuntimeEnvironment.getApplication().getContentResolver();
- if (RuntimeEnvironment.getApiLevel() >= JELLY_BEAN_MR1) {
- Settings.Global.putFloat(cr, Settings.Global.ANIMATOR_DURATION_SCALE, scale);
- Settings.Global.putFloat(cr, Settings.Global.TRANSITION_ANIMATION_SCALE, scale);
- Settings.Global.putFloat(cr, Settings.Global.WINDOW_ANIMATION_SCALE, scale);
- } else {
- Settings.System.putFloat(cr, Settings.System.ANIMATOR_DURATION_SCALE, scale);
- Settings.System.putFloat(cr, Settings.System.TRANSITION_ANIMATION_SCALE, scale);
- Settings.System.putFloat(cr, Settings.System.WINDOW_ANIMATION_SCALE, scale);
- }
+ Settings.Global.putFloat(cr, Settings.Global.ANIMATOR_DURATION_SCALE, scale);
+ Settings.Global.putFloat(cr, Settings.Global.TRANSITION_ANIMATION_SCALE, scale);
+ Settings.Global.putFloat(cr, Settings.Global.WINDOW_ANIMATION_SCALE, scale);
}
@Implementation(minSdk = TIRAMISU)
@@ -144,7 +132,6 @@ public class ShadowUiAutomation {
Bitmap.createBitmap(
rootView.getWidth(), rootView.getHeight(), Bitmap.Config.ARGB_8888);
if (HardwareRenderingScreenshot.canTakeScreenshot()) {
- Log.d("@@", "@@ USE NEW takeScreenshot"); // RM DEBUG
HardwareRenderingScreenshot.takeScreenshot(rootView, window);
} else {
Canvas windowCanvas = new Canvas(window);
@@ -271,11 +258,7 @@ public class ShadowUiAutomation {
}
private static Object getViewRootsContainer() {
- if (RuntimeEnvironment.getApiLevel() <= Build.VERSION_CODES.JELLY_BEAN) {
- return ReflectionHelpers.callStaticMethod(WindowManagerImpl.class, "getDefault");
- } else {
- return WindowManagerGlobal.getInstance();
- }
+ return WindowManagerGlobal.getInstance();
}
private static Set<IBinder> getStartedActivityTokens() {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUsbDeviceConnection.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUsbDeviceConnection.java
index 45fd5eb13..dcfdc5ae3 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUsbDeviceConnection.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUsbDeviceConnection.java
@@ -1,7 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.O;
@@ -64,13 +62,13 @@ public class ShadowUsbDeviceConnection {
return true;
}
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected int controlTransfer(
int requestType, int request, int value, int index, byte[] buffer, int length, int timeout) {
return length;
}
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected int controlTransfer(
int requestType,
int request,
@@ -97,7 +95,7 @@ public class ShadowUsbDeviceConnection {
return requestWait();
}
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected int bulkTransfer(
UsbEndpoint endpoint, byte[] buffer, int offset, int length, int timeout) {
try {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUserManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUserManager.java
index f6ef2deb5..78a96ec31 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUserManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUserManager.java
@@ -1,7 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.N;
@@ -59,7 +57,7 @@ import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;
/** Robolectric implementation of {@link android.os.UserManager}. */
-@Implements(value = UserManager.class, minSdk = JELLY_BEAN_MR1)
+@Implements(value = UserManager.class)
public class ShadowUserManager {
/**
@@ -172,7 +170,7 @@ public class ShadowUserManager {
*
* @see #setApplicationRestrictions(String, Bundle)
*/
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected Bundle getApplicationRestrictions(String packageName) {
Bundle bundle = userManagerState.applicationRestrictions.get(packageName);
return bundle != null ? bundle : new Bundle();
@@ -501,7 +499,7 @@ public class ShadowUserManager {
* return meaningful results in test environment; thus, allowing test to verify the invoking of
* UserManager.setUserRestriction().
*/
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected void setUserRestriction(String key, boolean value, UserHandle userHandle) {
Bundle bundle = getUserRestrictionsForUser(userHandle);
synchronized (lock) {
@@ -509,7 +507,7 @@ public class ShadowUserManager {
}
}
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected void setUserRestriction(String key, boolean value) {
setUserRestriction(key, value, Process.myUserHandle());
}
@@ -528,7 +526,7 @@ public class ShadowUserManager {
userManagerState.userRestrictions.remove(userHandle.getIdentifier());
}
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected Bundle getUserRestrictions(UserHandle userHandle) {
return new Bundle(getUserRestrictionsForUser(userHandle));
}
@@ -643,9 +641,11 @@ public class ShadowUserManager {
userManagerState.userIcon.put(userId, icon);
}
- /** @return user id for given user serial number. */
+ /**
+ * @return user id for given user serial number.
+ */
@HiddenApi
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
@UserIdInt
protected int getUserHandle(int serialNumber) {
Integer userHandle = userManagerState.userSerialNumbers.inverse().get((long) serialNumber);
@@ -663,7 +663,7 @@ public class ShadowUserManager {
}
@HiddenApi
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
protected static int getMaxSupportedUsers() {
return maxSupportedUsers;
}
@@ -769,8 +769,10 @@ public class ShadowUserManager {
}
}
- /** @return 'false' by default, or the value specified via {@link #setIsLinkedUser(boolean)} */
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ /**
+ * @return 'false' by default, or the value specified via {@link #setIsLinkedUser(boolean)}
+ */
+ @Implementation
protected boolean isLinkedUser() {
return isRestrictedProfile();
}
@@ -1059,7 +1061,7 @@ public class ShadowUserManager {
seedAccountOptions = null;
}
- @Implementation(minSdk = JELLY_BEAN_MR1)
+ @Implementation
protected boolean removeUser(int userHandle) {
if (!userManagerState.userInfoMap.containsKey(userHandle)) {
return false;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUwbManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUwbManager.java
index 18b08f6a4..118b155c1 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUwbManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUwbManager.java
@@ -5,8 +5,11 @@ import static android.os.Build.VERSION_CODES.TIRAMISU;
import android.os.Build.VERSION_CODES;
import android.os.CancellationSignal;
import android.os.PersistableBundle;
+import android.uwb.AdapterState;
import android.uwb.RangingSession;
+import android.uwb.StateChangeReason;
import android.uwb.UwbManager;
+import android.uwb.UwbManager.AdapterStateCallback;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.List;
@@ -19,6 +22,12 @@ import org.robolectric.shadow.api.Shadow;
@Implements(value = UwbManager.class, minSdk = VERSION_CODES.S, isInAndroidSdk = false)
public class ShadowUwbManager {
+ private AdapterStateCallback callback;
+
+ private int adapterState = AdapterStateCallback.STATE_ENABLED_INACTIVE;
+
+ private int stateChangedReason = AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_POLICY;
+
private PersistableBundle specificationInfo = new PersistableBundle();
private List<PersistableBundle> chipInfos = new ArrayList<>();
@@ -44,6 +53,32 @@ public class ShadowUwbManager {
public void onClose(RangingSession session, RangingSession.Callback callback) {}
};
+ @Implementation
+ protected void registerAdapterStateCallback(Executor executor, AdapterStateCallback callback) {
+ this.callback = callback;
+ callback.onStateChanged(adapterState, stateChangedReason);
+ }
+
+ /**
+ * Simulates adapter state change by invoking a callback registered by {@link
+ * ShadowUwbManager#registerAdapterStateCallback(Executor executor, AdapterStateCallback
+ * callback)}.
+ *
+ * @param state A state that should be passed to the callback.
+ * @param reason A reason that should be passed to the callback.
+ * @throws IllegalArgumentException if the callback is missing.
+ */
+ public void simulateAdapterStateChange(@AdapterState int state, @StateChangeReason int reason) {
+ if (this.callback == null) {
+ throw new IllegalArgumentException("AdapterStateCallback should not be null");
+ }
+
+ adapterState = state;
+ stateChangedReason = reason;
+
+ this.callback.onStateChanged(state, reason);
+ }
+
/**
* Simply returns the bundle provided by {@link ShadowUwbManager#setSpecificationInfo()}, allowing
* the tester to dictate available features.
@@ -56,10 +91,15 @@ public class ShadowUwbManager {
/**
* Instantiates a {@link ShadowRangingSession} with the adapter provided by {@link
* ShadowUwbManager#setUwbAdapter()}, allowing the tester dictate the results of ranging attempts.
+ *
+ * @throws IllegalArgumentException if UWB is disabled.
*/
@Implementation
protected CancellationSignal openRangingSession(
PersistableBundle params, Executor executor, RangingSession.Callback callback) {
+ if (!isUwbEnabled()) {
+ throw new IllegalStateException("Uwb is not enabled");
+ }
RangingSession session = ShadowRangingSession.newInstance(executor, callback, adapter);
CancellationSignal cancellationSignal = new CancellationSignal();
cancellationSignal.setOnCancelListener(session::close);
@@ -91,6 +131,35 @@ public class ShadowUwbManager {
return openRangingSession(params, executor, callback);
}
+ /** Returns whether UWB is enabled or disabled. */
+ @Implementation(minSdk = TIRAMISU)
+ protected boolean isUwbEnabled() {
+ return adapterState != AdapterStateCallback.STATE_DISABLED;
+ }
+
+ /**
+ * Disables or enables UWB by the user.
+ *
+ * @param enabled value representing intent to disable or enable UWB.
+ */
+ @Implementation
+ protected void setUwbEnabled(boolean enabled) {
+ boolean stateChanged = false;
+
+ if (enabled && adapterState == AdapterStateCallback.STATE_DISABLED) {
+ adapterState = AdapterStateCallback.STATE_ENABLED_INACTIVE;
+ stateChanged = true;
+ } else if (!enabled && adapterState != AdapterStateCallback.STATE_DISABLED) {
+ adapterState = AdapterStateCallback.STATE_DISABLED;
+ stateChanged = true;
+ }
+
+ if (this.callback != null && stateChanged) {
+ this.callback.onStateChanged(
+ adapterState, AdapterStateCallback.STATE_CHANGED_REASON_SYSTEM_POLICY);
+ }
+ }
+
/**
* Simply returns the List of bundles provided by {@link ShadowUwbManager#setChipInfos(List)} ,
* allowing the tester to set multi-chip configuration.
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowView.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowView.java
index 78526fade..848502e60 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowView.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowView.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.KITKAT_WATCH;
import static android.os.Build.VERSION_CODES.N;
@@ -793,7 +792,7 @@ public class ShadowView {
}
}
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected boolean isAttachedToWindow() {
return getAttachInfo() != null;
}
@@ -954,7 +953,7 @@ public class ShadowView {
reflector(_View_.class, realView).onDetachedFromWindow();
}
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected WindowId getWindowId() {
return WindowIdHelper.getWindowId(this);
}
@@ -1092,6 +1091,7 @@ public class ShadowView {
* set.
*/
static boolean useRealScrolling() {
- return useRealGraphics() || Boolean.getBoolean("robolectric.useRealScrolling");
+ return useRealGraphics()
+ || Boolean.parseBoolean(System.getProperty("robolectric.useRealScrolling", "true"));
}
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowViewRootImpl.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowViewRootImpl.java
index 9dd19321b..744b1c02e 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowViewRootImpl.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowViewRootImpl.java
@@ -6,7 +6,6 @@ import static android.os.Build.VERSION_CODES.S_V2;
import static org.robolectric.annotation.TextLayoutMode.Mode.REALISTIC;
import static org.robolectric.util.reflector.Reflector.reflector;
-import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Build;
@@ -222,14 +221,7 @@ public class ShadowViewRootImpl {
}
protected Display getDisplay() {
- if (RuntimeEnvironment.getApiLevel() > VERSION_CODES.JELLY_BEAN_MR1) {
- return reflector(ViewRootImplReflector.class, realObject).getDisplay();
- } else {
- WindowManager windowManager =
- (WindowManager)
- realObject.getView().getContext().getSystemService(Context.WINDOW_SERVICE);
- return windowManager.getDefaultDisplay();
- }
+ return reflector(ViewRootImplReflector.class, realObject).getDisplay();
}
@Implementation
@@ -356,24 +348,7 @@ public class ShadowViewRootImpl {
@Accessor("mWindowAttributes")
WindowManager.LayoutParams getWindowAttributes();
- // <= JELLY_BEAN
- void dispatchResized(
- int w,
- int h,
- Rect contentInsets,
- Rect visibleInsets,
- boolean reportDraw,
- Configuration newConfig);
-
- // <= JELLY_BEAN_MR1
- void dispatchResized(
- Rect frame,
- Rect contentInsets,
- Rect visibleInsets,
- boolean reportDraw,
- Configuration newConfig);
-
- // <= KITKAT
+ // == KITKAT
void dispatchResized(
Rect frame,
Rect overscanInsets,
@@ -452,11 +427,7 @@ public class ShadowViewRootImpl {
Rect emptyRect = new Rect(0, 0, 0, 0);
int apiLevel = RuntimeEnvironment.getApiLevel();
- if (apiLevel <= Build.VERSION_CODES.JELLY_BEAN) {
- dispatchResized(frame.width(), frame.height(), emptyRect, emptyRect, true, null);
- } else if (apiLevel <= VERSION_CODES.JELLY_BEAN_MR1) {
- dispatchResized(frame, emptyRect, emptyRect, true, null);
- } else if (apiLevel <= Build.VERSION_CODES.KITKAT) {
+ if (apiLevel == Build.VERSION_CODES.KITKAT) {
dispatchResized(frame, emptyRect, emptyRect, emptyRect, true, null);
} else if (apiLevel <= Build.VERSION_CODES.LOLLIPOP_MR1) {
dispatchResized(frame, emptyRect, emptyRect, emptyRect, emptyRect, true, null);
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualDeviceManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualDeviceManager.java
index ea978511f..f4f15fb79 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualDeviceManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualDeviceManager.java
@@ -14,6 +14,15 @@ import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtual.sensor.VirtualSensorCallback;
import android.companion.virtual.sensor.VirtualSensorDirectChannelCallback;
import android.content.Context;
+import android.hardware.display.VirtualDisplay;
+import android.hardware.input.VirtualKeyboard;
+import android.hardware.input.VirtualKeyboardConfig;
+import android.hardware.input.VirtualMouse;
+import android.hardware.input.VirtualMouseConfig;
+import android.hardware.input.VirtualTouchscreen;
+import android.hardware.input.VirtualTouchscreenConfig;
+import android.os.Binder;
+import android.os.IBinder;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
@@ -21,6 +30,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.IntConsumer;
import java.util.stream.Collectors;
+import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
@@ -83,8 +93,8 @@ public class ShadowVirtualDeviceManager {
// Use the new constructor when the old constructor does not exist
DeviceManagerVirtualDeviceReflector virtualDeviceReflector =
reflector(DeviceManagerVirtualDeviceReflector.class, virtualDevice);
- return accessor.newInstance(
- virtualDeviceReflector.getVirtualDevice(),
+ return accessor.newInstanceV(
+ ReflectionHelpers.createNullProxy(IVirtualDevice.class),
virtualDevice.getDeviceId(),
virtualDeviceReflector.getPersistentDeviceId(),
deviceName);
@@ -193,6 +203,75 @@ public class ShadowVirtualDeviceManager {
executor.execute(() -> listener.accept(pendingIntentResultCode));
}
+ @Implementation
+ protected VirtualMouse createVirtualMouse(
+ @NonNull VirtualDisplay display,
+ @NonNull String inputDeviceName,
+ int vendorId,
+ int productId) {
+ return createVirtualMouse(
+ new VirtualMouseConfig.Builder().setInputDeviceName(inputDeviceName).build());
+ }
+
+ @Implementation
+ protected VirtualMouse createVirtualMouse(@NonNull VirtualMouseConfig config) {
+ IBinder token =
+ new Binder("android.hardware.input.VirtualMouse:" + config.getInputDeviceName());
+ VirtualMouseReflector accessor = reflector(VirtualMouseReflector.class);
+ if (RuntimeEnvironment.getApiLevel() <= U.SDK_INT) {
+ return accessor.newInstance(ReflectionHelpers.createNullProxy(IVirtualDevice.class), token);
+ } else {
+ return accessor.newInstanceV(
+ config, ReflectionHelpers.createNullProxy(IVirtualDevice.class), token);
+ }
+ }
+
+ @Implementation
+ protected void setShowPointerIcon(boolean showPointerIcon) {
+ // no-op
+ }
+
+ @Implementation
+ protected VirtualTouchscreen createVirtualTouchscreen(
+ @NonNull VirtualDisplay display,
+ @NonNull String inputDeviceName,
+ int vendorId,
+ int productId) {
+ int displayWidth = 720;
+ int displayHeight = 1280;
+ return createVirtualTouchscreen(
+ new VirtualTouchscreenConfig.Builder(displayWidth, displayHeight)
+ .setInputDeviceName(inputDeviceName)
+ .build());
+ }
+
+ @Implementation
+ protected VirtualTouchscreen createVirtualTouchscreen(
+ @NonNull VirtualTouchscreenConfig config) {
+ IBinder token =
+ new Binder("android.hardware.input.VirtualTouchscreen:" + config.getInputDeviceName());
+ VirtualTouchscreenReflector accessor = reflector(VirtualTouchscreenReflector.class);
+ if (RuntimeEnvironment.getApiLevel() <= U.SDK_INT) {
+ return accessor.newInstance(ReflectionHelpers.createNullProxy(IVirtualDevice.class), token);
+ } else {
+ return accessor.newInstanceV(
+ config, ReflectionHelpers.createNullProxy(IVirtualDevice.class), token);
+ }
+ }
+
+ @Implementation
+ protected VirtualKeyboard createVirtualKeyboard(@NonNull VirtualKeyboardConfig config) {
+ IBinder token =
+ new Binder("android.hardware.input.VirtualKeyboard:" + config.getInputDeviceName());
+ VirtualKeyboardReflector accessor = reflector(VirtualKeyboardReflector.class);
+ if (RuntimeEnvironment.getApiLevel() <= U.SDK_INT) {
+ return accessor.newInstance(ReflectionHelpers.createNullProxy(IVirtualDevice.class), token);
+ } else {
+ return accessor.newInstanceV(
+ config, ReflectionHelpers.createNullProxy(IVirtualDevice.class), token);
+ }
+ }
+
public void setPendingIntentCallbackResultCode(int resultCode) {
this.pendingIntentResultCode = resultCode;
}
@@ -234,12 +313,40 @@ public class ShadowVirtualDeviceManager {
VirtualSensorDirectChannelCallback getDirectChannelCallback();
}
+ @ForType(VirtualMouse.class)
+ private interface VirtualMouseReflector {
+ @Constructor
+ VirtualMouse newInstanceV(
+ VirtualMouseConfig config, IVirtualDevice virtualDevice, IBinder token);
+
+ @Constructor
+ VirtualMouse newInstance(IVirtualDevice virtualDevice, IBinder token);
+ }
+
+ @ForType(VirtualTouchscreen.class)
+ private interface VirtualTouchscreenReflector {
+ @Constructor
+ VirtualTouchscreen newInstanceV(
+ VirtualTouchscreenConfig config, IVirtualDevice virtualDevice, IBinder token);
+
+ @Constructor
+ VirtualTouchscreen newInstance(IVirtualDevice virtualDevice, IBinder token);
+ }
+
+ @ForType(VirtualKeyboard.class)
+ private interface VirtualKeyboardReflector {
+ @Constructor
+ VirtualKeyboard newInstanceV(
+ VirtualKeyboardConfig config, IVirtualDevice virtualDevice, IBinder token);
+
+ @Constructor
+ VirtualKeyboard newInstance(IVirtualDevice virtualDevice, IBinder token);
+ }
+
@ForType(VirtualDevice.class)
private interface VirtualDeviceReflector {
-
- // new constructor after U
@Constructor
- VirtualDevice newInstance(
+ VirtualDevice newInstanceV(
IVirtualDevice virtualDevice, int id, String persistentId, String name);
@Constructor
@@ -248,10 +355,6 @@ public class ShadowVirtualDeviceManager {
@ForType(VirtualDeviceManager.VirtualDevice.class)
private interface DeviceManagerVirtualDeviceReflector {
- // U and before var and method
- @Accessor("mVirtualDevice")
- IVirtualDevice getVirtualDevice();
-
String getPersistentDeviceId();
}
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualInputDevice.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualInputDevice.java
new file mode 100644
index 000000000..f7fa9ee84
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualInputDevice.java
@@ -0,0 +1,25 @@
+package org.robolectric.shadows;
+
+import android.os.Build.VERSION_CODES;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+/** Shadow for VirtualInputDevice. */
+@Implements(
+ className = "android.hardware.input.VirtualInputDevice",
+ isInAndroidSdk = false,
+ minSdk = VERSION_CODES.UPSIDE_DOWN_CAKE)
+public class ShadowVirtualInputDevice {
+
+ private final AtomicBoolean isClosed = new AtomicBoolean(false);
+
+ @Implementation
+ protected void close() {
+ isClosed.set(true);
+ }
+
+ public boolean isClosed() {
+ return isClosed.get();
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualKeyboard.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualKeyboard.java
new file mode 100644
index 000000000..65b797064
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualKeyboard.java
@@ -0,0 +1,25 @@
+package org.robolectric.shadows;
+
+import android.hardware.input.VirtualKeyEvent;
+import android.hardware.input.VirtualKeyboard;
+import android.os.Build.VERSION_CODES;
+import java.util.ArrayList;
+import java.util.List;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+/** Shadow for VirtualKeyboard. */
+@Implements(value = VirtualKeyboard.class, minSdk = VERSION_CODES.TIRAMISU, isInAndroidSdk = false)
+public class ShadowVirtualKeyboard extends ShadowVirtualInputDevice {
+
+ private final List<VirtualKeyEvent> sentEvents = new ArrayList<>();
+
+ @Implementation
+ protected void sendKeyEvent(VirtualKeyEvent event) {
+ sentEvents.add(event);
+ }
+
+ public List<VirtualKeyEvent> getSentEvents() {
+ return sentEvents;
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualMouse.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualMouse.java
new file mode 100644
index 000000000..08fa1d654
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualMouse.java
@@ -0,0 +1,47 @@
+package org.robolectric.shadows;
+
+import android.hardware.input.VirtualMouse;
+import android.hardware.input.VirtualMouseButtonEvent;
+import android.hardware.input.VirtualMouseRelativeEvent;
+import android.hardware.input.VirtualMouseScrollEvent;
+import android.os.Build.VERSION_CODES;
+import java.util.ArrayList;
+import java.util.List;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+/** Shadow for VirtualMouse. */
+@Implements(value = VirtualMouse.class, minSdk = VERSION_CODES.TIRAMISU, isInAndroidSdk = false)
+public class ShadowVirtualMouse extends ShadowVirtualInputDevice {
+
+ private final List<VirtualMouseButtonEvent> sentButtonEvents = new ArrayList<>();
+ private final List<VirtualMouseScrollEvent> sentScrollEvents = new ArrayList<>();
+ private final List<VirtualMouseRelativeEvent> sentRelativeEvents = new ArrayList<>();
+
+ @Implementation
+ protected void sendButtonEvent(VirtualMouseButtonEvent event) {
+ sentButtonEvents.add(event);
+ }
+
+ @Implementation
+ protected void sendScrollEvent(VirtualMouseScrollEvent event) {
+ sentScrollEvents.add(event);
+ }
+
+ @Implementation
+ protected void sendRelativeEvent(VirtualMouseRelativeEvent event) {
+ sentRelativeEvents.add(event);
+ }
+
+ public List<VirtualMouseButtonEvent> getSentButtonEvents() {
+ return sentButtonEvents;
+ }
+
+ public List<VirtualMouseScrollEvent> getSentScrollEvents() {
+ return sentScrollEvents;
+ }
+
+ public List<VirtualMouseRelativeEvent> getSentRelativeEvents() {
+ return sentRelativeEvents;
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualTouchscreen.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualTouchscreen.java
new file mode 100644
index 000000000..403212a07
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVirtualTouchscreen.java
@@ -0,0 +1,28 @@
+package org.robolectric.shadows;
+
+import android.hardware.input.VirtualTouchEvent;
+import android.hardware.input.VirtualTouchscreen;
+import android.os.Build.VERSION_CODES;
+import java.util.ArrayList;
+import java.util.List;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+/** Shadow for VirtualTouchscreen. */
+@Implements(
+ value = VirtualTouchscreen.class,
+ minSdk = VERSION_CODES.TIRAMISU,
+ isInAndroidSdk = false)
+public class ShadowVirtualTouchscreen extends ShadowVirtualInputDevice {
+
+ private final List<VirtualTouchEvent> sentEvents = new ArrayList<>();
+
+ @Implementation
+ protected void sendTouchEvent(VirtualTouchEvent event) {
+ sentEvents.add(event);
+ }
+
+ public List<VirtualTouchEvent> getSentEvents() {
+ return sentEvents;
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVisualizer.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVisualizer.java
index 9451cb0e4..ca559490b 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVisualizer.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVisualizer.java
@@ -1,7 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.GINGERBREAD;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static org.robolectric.util.reflector.Reflector.reflector;
import android.media.audiofx.Visualizer;
@@ -15,7 +13,7 @@ import org.robolectric.util.reflector.Accessor;
import org.robolectric.util.reflector.ForType;
/** Shadow for the {@link Visualizer} class. */
-@Implements(value = Visualizer.class, minSdk = GINGERBREAD)
+@Implements(value = Visualizer.class)
public class ShadowVisualizer {
@RealObject private Visualizer realObject;
@@ -34,7 +32,7 @@ public class ShadowVisualizer {
this.source.set(source);
}
- @Implementation(minSdk = GINGERBREAD)
+ @Implementation
protected int setDataCaptureListener(
OnDataCaptureListener listener, int rate, boolean waveform, boolean fft) {
if (errorCode != Visualizer.SUCCESS) {
@@ -46,27 +44,27 @@ public class ShadowVisualizer {
return Visualizer.SUCCESS;
}
- @Implementation(minSdk = GINGERBREAD)
+ @Implementation
protected int native_getSamplingRate() {
return source.get().getSamplingRate();
}
- @Implementation(minSdk = GINGERBREAD)
+ @Implementation
protected int native_getWaveForm(byte[] waveform) {
return source.get().getWaveForm(waveform);
}
- @Implementation(minSdk = GINGERBREAD)
+ @Implementation
protected int native_getFft(byte[] fft) {
return source.get().getFft(fft);
}
- @Implementation(minSdk = GINGERBREAD)
+ @Implementation
protected boolean native_getEnabled() {
return enabled;
}
- @Implementation(minSdk = GINGERBREAD)
+ @Implementation
protected int native_setCaptureSize(int size) {
if (errorCode != Visualizer.SUCCESS) {
return errorCode;
@@ -75,12 +73,12 @@ public class ShadowVisualizer {
return Visualizer.SUCCESS;
}
- @Implementation(minSdk = GINGERBREAD)
+ @Implementation
protected int native_getCaptureSize() {
return captureSize;
}
- @Implementation(minSdk = GINGERBREAD)
+ @Implementation
protected int native_setEnabled(boolean enabled) {
if (errorCode != Visualizer.SUCCESS) {
return errorCode;
@@ -89,12 +87,12 @@ public class ShadowVisualizer {
return Visualizer.SUCCESS;
}
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected int native_getPeakRms(MeasurementPeakRms measurement) {
return source.get().getPeakRms(measurement);
}
- @Implementation(minSdk = GINGERBREAD)
+ @Implementation
protected void native_release() {
source.get().release();
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWallpaperManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWallpaperManager.java
index 648b44988..1b59b3d5d 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWallpaperManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWallpaperManager.java
@@ -18,7 +18,6 @@ import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.graphics.Rect;
-import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
@@ -52,6 +51,7 @@ public class ShadowWallpaperManager {
private int homeScreenId;
private float wallpaperDimAmount = 0.0f;
+ private final ArrayList<Float> allWallpaperDimAmounts = new ArrayList<>();
@Implementation
protected void sendWallpaperCommand(
@@ -88,7 +88,7 @@ public class ShadowWallpaperManager {
* Returns whether the current wallpaper has been set through {@link #setResource(int)} or {@link
* #setResource(int, int)} with the same resource id.
*/
- @Implementation(minSdk = VERSION_CODES.JELLY_BEAN_MR1)
+ @Implementation
protected boolean hasResourceWallpaper(int resid) {
return resid == this.lockScreenId || resid == this.homeScreenId;
}
@@ -240,6 +240,15 @@ public class ShadowWallpaperManager {
@Implementation(minSdk = TIRAMISU)
protected void setWallpaperDimAmount(@FloatRange(from = 0f, to = 1f) float dimAmount) {
wallpaperDimAmount = MathUtils.saturate(dimAmount);
+ allWallpaperDimAmounts.add(dimAmount);
+ }
+
+ /**
+ * Returns a list of all dim amounts set from calls to setWallpaperDimAmount. This can be used to
+ * verify that repeated calls to setWallpaperDimAmount are not done which can cause issues.
+ */
+ public List<Float> getAllWallpaperDimAmounts() {
+ return Collections.unmodifiableList(allWallpaperDimAmounts);
}
@Implementation(minSdk = TIRAMISU)
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWebSettings.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWebSettings.java
index 555a21122..427a10190 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWebSettings.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWebSettings.java
@@ -1,7 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
-
import android.content.Context;
import android.webkit.WebSettings;
import org.robolectric.annotation.Implementation;
@@ -23,7 +21,7 @@ public class ShadowWebSettings {
*
* @param context a Context object used to access application assets
*/
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected static String getDefaultUserAgent(Context context) {
return defaultUserAgent;
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWebView.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWebView.java
index 1a8131f01..8847b5e2f 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWebView.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWebView.java
@@ -542,7 +542,7 @@ public class ShadowWebView extends ShadowViewGroup {
currentFavicon = favicon;
}
- @Implementation(minSdk = Build.VERSION_CODES.KITKAT)
+ @Implementation
protected void evaluateJavascript(String script, ValueCallback<String> callback) {
this.lastEvaluatedJavascript = script;
this.lastEvaluatedJavascriptCallback = callback;
@@ -645,7 +645,7 @@ public class ShadowWebView extends ShadowViewGroup {
packageInfo = null;
}
- @Implementation(minSdk = VERSION_CODES.KITKAT)
+ @Implementation
public static void setWebContentsDebuggingEnabled(boolean enabled) {}
/**
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWifiInfo.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWifiInfo.java
index 7f3b1c0a1..183637167 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWifiInfo.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWifiInfo.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static org.robolectric.util.reflector.Reflector.reflector;
@@ -8,7 +7,6 @@ import android.net.wifi.SupplicantState;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiSsid;
import java.net.InetAddress;
-import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
@@ -37,13 +35,8 @@ public class ShadowWifiInfo {
reflector(WifiInfoReflector.class, realObject).setMacAddress(newMacAddress);
}
- @Implementation(maxSdk = JELLY_BEAN)
public void setSSID(String ssid) {
- if (RuntimeEnvironment.getApiLevel() <= JELLY_BEAN) {
- reflector(WifiInfoReflector.class, realObject).setSSID(ssid);
- } else {
- reflector(WifiInfoReflector.class, realObject).setSSID(getWifiSsid(ssid));
- }
+ reflector(WifiInfoReflector.class, realObject).setSSID(getWifiSsid(ssid));
}
private static Object getWifiSsid(String ssid) {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWifiManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWifiManager.java
index 72c000931..9e17e3531 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWifiManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWifiManager.java
@@ -1,7 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.Q;
import static android.os.Build.VERSION_CODES.R;
@@ -402,7 +400,7 @@ public class ShadowWifiManager {
return dhcpInfo;
}
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected boolean isScanAlwaysAvailable() {
return Settings.Global.getInt(
getContext().getContentResolver(), Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 1)
@@ -410,7 +408,7 @@ public class ShadowWifiManager {
}
@HiddenApi
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected void connect(WifiConfiguration wifiConfiguration, WifiManager.ActionListener listener) {
WifiInfo wifiInfo = getConnectionInfo();
@@ -450,7 +448,7 @@ public class ShadowWifiManager {
}
@HiddenApi
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected void connect(int networkId, WifiManager.ActionListener listener) {
WifiConfiguration wifiConfiguration = new WifiConfiguration();
wifiConfiguration.networkId = networkId;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWifiP2pManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWifiP2pManager.java
index 54e6b2800..a433db1e5 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWifiP2pManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWifiP2pManager.java
@@ -1,7 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.KITKAT;
-
import android.content.Context;
import android.net.wifi.p2p.WifiP2pGroup;
import android.net.wifi.p2p.WifiP2pManager;
@@ -40,7 +38,7 @@ public class ShadowWifiP2pManager {
return groupInfoListener;
}
- @Implementation(minSdk = KITKAT)
+ @Implementation
protected void setWifiP2pChannels(
Channel c, int listeningChannel, int operatingChannel, ActionListener al) {
Preconditions.checkNotNull(c);
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindow.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindow.java
index 91e10364e..78feb1bb4 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindow.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindow.java
@@ -1,6 +1,5 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.N;
import static android.os.Build.VERSION_CODES.Q;
@@ -60,7 +59,7 @@ public class ShadowWindow {
reflector(WindowReflector.class, realWindow).addSystemFlags(flags);
}
- @Implementation(minSdk = KITKAT, maxSdk = R)
+ @Implementation(maxSdk = R)
@HiddenApi
protected void addPrivateFlags(int flags) {
this.privateFlags |= flags;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindowManagerGlobal.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindowManagerGlobal.java
index 72992fa80..4f988c39f 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindowManagerGlobal.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindowManagerGlobal.java
@@ -1,11 +1,11 @@
package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.P;
import static org.robolectric.shadows.ShadowView.useRealGraphics;
import static org.robolectric.util.reflector.Reflector.reflector;
+import android.annotation.Nullable;
import android.app.Instrumentation;
import android.content.ClipData;
import android.content.Context;
@@ -18,7 +18,6 @@ import android.view.IWindowManager;
import android.view.IWindowSession;
import android.view.View;
import android.view.WindowManagerGlobal;
-import androidx.annotation.Nullable;
import java.lang.reflect.Proxy;
import java.util.List;
import org.robolectric.RuntimeEnvironment;
@@ -35,7 +34,6 @@ import org.robolectric.util.reflector.Static;
@Implements(
value = WindowManagerGlobal.class,
isInAndroidSdk = false,
- minSdk = JELLY_BEAN_MR1,
looseSignatures = true)
public class ShadowWindowManagerGlobal {
private static WindowSessionDelegate windowSessionDelegate = new WindowSessionDelegate();
@@ -74,7 +72,7 @@ public class ShadowWindowManagerGlobal {
windowSessionDelegate.lastDragClipData = null;
}
- @Implementation(minSdk = JELLY_BEAN_MR2)
+ @Implementation
protected static synchronized IWindowSession getWindowSession() {
if (windowSession == null) {
// Use Proxy.newProxyInstance instead of ReflectionHelpers.createDelegatingProxy as there are
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindowManagerImpl.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindowManagerImpl.java
index 0f6972b26..9fd17e952 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindowManagerImpl.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindowManagerImpl.java
@@ -9,11 +9,8 @@ import static org.robolectric.RuntimeEnvironment.getApiLevel;
import static org.robolectric.util.reflector.Reflector.reflector;
import android.content.Context;
-import android.content.res.Configuration;
import android.graphics.Insets;
import android.graphics.Rect;
-import android.os.Build.VERSION_CODES;
-import android.util.DisplayMetrics;
import android.view.Display;
import android.view.DisplayCutout;
import android.view.InsetsState;
@@ -25,13 +22,11 @@ import android.view.WindowManagerImpl;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Multimap;
-import java.util.HashMap;
import java.util.List;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.annotation.Resetter;
-import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowViewRootImpl.ViewRootImplReflector;
import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.ReflectionHelpers.ClassParameter;
@@ -42,28 +37,12 @@ import org.robolectric.util.reflector.ForType;
@Implements(value = WindowManagerImpl.class, isInAndroidSdk = false)
public class ShadowWindowManagerImpl extends ShadowWindowManager {
- private static Display defaultDisplayJB;
-
@RealObject WindowManagerImpl realObject;
private static final Multimap<Integer, View> views = ArrayListMultimap.create();
// removed from WindowManagerImpl in S
public static final int NEW_INSETS_MODE_FULL = 2;
- /** internal only */
- public static void configureDefaultDisplayForJBOnly(
- Configuration configuration, DisplayMetrics displayMetrics) {
- Class<?> arg2Type =
- ReflectionHelpers.loadClass(
- ShadowWindowManagerImpl.class.getClassLoader(), "android.view.CompatibilityInfoHolder");
-
- defaultDisplayJB =
- ReflectionHelpers.callConstructor(
- Display.class, ClassParameter.from(int.class, 0), ClassParameter.from(arg2Type, null));
- ShadowDisplay shadowDisplay = Shadow.extract(defaultDisplayJB);
- shadowDisplay.configureForJBOnly(configuration, displayMetrics);
- }
-
@Implementation
public void addView(View view, android.view.ViewGroup.LayoutParams layoutParams) {
views.put(realObject.getDefaultDisplay().getDisplayId(), view);
@@ -89,19 +68,7 @@ public class ShadowWindowManagerImpl extends ShadowWindowManager {
@Implementation(maxSdk = JELLY_BEAN)
public Display getDefaultDisplay() {
- if (getApiLevel() > JELLY_BEAN) {
- return reflector(ReflectorWindowManagerImpl.class, realObject).getDefaultDisplay();
- } else {
- return defaultDisplayJB;
- }
- }
-
- @Implements(className = "android.view.WindowManagerImpl$CompatModeWrapper", maxSdk = JELLY_BEAN)
- public static class ShadowCompatModeWrapper {
- @Implementation(maxSdk = JELLY_BEAN)
- protected Display getDefaultDisplay() {
- return defaultDisplayJB;
- }
+ return reflector(ReflectorWindowManagerImpl.class, realObject).getDefaultDisplay();
}
/** Re implement to avoid server call */
@@ -160,16 +127,6 @@ public class ShadowWindowManagerImpl extends ShadowWindowManager {
@Resetter
public static void reset() {
- defaultDisplayJB = null;
views.clear();
- if (getApiLevel() <= VERSION_CODES.JELLY_BEAN) {
- ReflectionHelpers.setStaticField(
- WindowManagerImpl.class,
- "sWindowManager",
- ReflectionHelpers.newInstance(WindowManagerImpl.class));
- HashMap windowManagers =
- ReflectionHelpers.getStaticField(WindowManagerImpl.class, "sCompatWindowManagers");
- windowManagers.clear();
- }
}
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/UiccCardInfoBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/UiccCardInfoBuilder.java
index 77db2609c..0ae152b0b 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/UiccCardInfoBuilder.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/UiccCardInfoBuilder.java
@@ -1,10 +1,10 @@
package org.robolectric.shadows;
+import android.annotation.NonNull;
+import android.annotation.RequiresApi;
import android.os.Build.VERSION_CODES;
import android.telephony.UiccCardInfo;
import android.telephony.UiccPortInfo;
-import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.util.ArrayList;
import java.util.List;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/UiccPortInfoBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/UiccPortInfoBuilder.java
index 23b45b0e5..149596926 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/UiccPortInfoBuilder.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/UiccPortInfoBuilder.java
@@ -1,8 +1,8 @@
package org.robolectric.shadows;
+import android.annotation.RequiresApi;
import android.os.Build.VERSION_CODES;
import android.telephony.UiccPortInfo;
-import androidx.annotation.RequiresApi;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.ReflectionHelpers.ClassParameter;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/_Activity_.java b/shadows/framework/src/main/java/org/robolectric/shadows/_Activity_.java
index f6ab51296..fcfcfda7c 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/_Activity_.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/_Activity_.java
@@ -28,7 +28,7 @@ public interface _Activity_ {
@Accessor("mToken")
IBinder getToken();
- // <= KITKAT:
+ // == KITKAT:
void attach(
Context context,
ActivityThread activityThread,
@@ -181,7 +181,7 @@ public interface _Activity_ {
@WithType("android.app.Activity$NonConfigurationInstances")
Object lastNonConfigurationInstances) {
int apiLevel = RuntimeEnvironment.getApiLevel();
- if (apiLevel <= Build.VERSION_CODES.KITKAT) {
+ if (apiLevel == Build.VERSION_CODES.KITKAT) {
attach(
baseContext,
activityThread,
diff --git a/shadows/httpclient/src/main/java/org/robolectric/shadows/httpclient/ShadowDefaultRequestDirector.java b/shadows/httpclient/src/main/java/org/robolectric/shadows/httpclient/ShadowDefaultRequestDirector.java
index 12fecbb02..6160dfa0c 100644
--- a/shadows/httpclient/src/main/java/org/robolectric/shadows/httpclient/ShadowDefaultRequestDirector.java
+++ b/shadows/httpclient/src/main/java/org/robolectric/shadows/httpclient/ShadowDefaultRequestDirector.java
@@ -138,12 +138,16 @@ public class ShadowDefaultRequestDirector {
* Get the sent {@link HttpRequest} for the given index.
*
* @param index The index
- * @deprecated Use {@link FakeHttp#getSentHttpRequestInfo(int)} instead.)
+ * @deprecated Use {@link FakeHttp#getSentHttpRequestInfo(int)} instead. This method will be
+ * removed in Robolectric 4.13.
* @return HttpRequest
*/
@Deprecated
+ @InlineMe(
+ replacement = "FakeHttp.getFakeHttpLayer().getSentHttpRequestInfo(index).getHttpRequest()",
+ imports = "org.robolectric.shadows.httpclient.FakeHttp")
public static HttpRequest getSentHttpRequest(int index) {
- return getSentHttpRequestInfo(index).getHttpRequest();
+ return FakeHttp.getFakeHttpLayer().getSentHttpRequestInfo(index).getHttpRequest();
}
public static HttpRequest getLatestSentHttpRequest() {
@@ -159,14 +163,15 @@ public class ShadowDefaultRequestDirector {
* Get the sent {@link HttpRequestInfo} for the given index.
*
* @param index The index
- * @deprecated Use {@link FakeHttp#getSentHttpRequest(int)} instead.)
+ * @deprecated Use {@link FakeHttp#getSentHttpRequest(int)} instead. This method will be removed
+ * in Robolectric 4.13.
* @return HttpRequestInfo
*/
@Deprecated
@InlineMe(
replacement = "FakeHttp.getFakeHttpLayer().getSentHttpRequestInfo(index)",
imports = "org.robolectric.shadows.httpclient.FakeHttp")
- public static final HttpRequestInfo getSentHttpRequestInfo(int index) {
+ public static HttpRequestInfo getSentHttpRequestInfo(int index) {
return FakeHttp.getFakeHttpLayer().getSentHttpRequestInfo(index);
}