aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/tests.yml12
-rw-r--r--.gitignore4
-rw-r--r--Android.bp223
-rw-r--r--OWNERS4
-rw-r--r--annotations/Android.bp22
-rw-r--r--annotations/src/main/java/org/robolectric/annotation/GraphicsMode.java27
-rw-r--r--buildSrc/src/main/groovy/org/robolectric/gradle/GradleManagedDevicePlugin.groovy1
-rw-r--r--errorprone/src/main/java/org/robolectric/errorprone/bugpatterns/RobolectricShadow.java12
-rw-r--r--integration_tests/ctesque/src/sharedTest/java/android/graphics/BitmapTest.java2
-rw-r--r--integration_tests/ctesque/src/sharedTest/java/android/text/format/DateFormatTest.java6
-rw-r--r--junit/Android.bp30
-rw-r--r--nativeruntime/Android.bp46
-rw-r--r--nativeruntime/build.gradle7
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/AnimatedImageDrawableNatives.java69
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/AnimatedVectorDrawableNatives.java70
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/BaseCanvasNatives.java344
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/BaseRecordingCanvasNatives.java343
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/BitmapFactoryNatives.java57
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/BitmapNatives.java134
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/BitmapShaderNatives.java19
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/BlendModeColorFilterNatives.java30
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/BlurMaskFilterNatives.java30
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/CanvasNatives.java94
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/CanvasPropertyNatives.java32
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/ColorFilterNatives.java32
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/ColorMatrixColorFilterNatives.java30
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/ColorNatives.java16
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/ColorSpaceRgbNatives.java33
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/ComposePathEffectNatives.java14
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/ComposeShaderNatives.java14
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/CornerPathEffectNatives.java13
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/DashPathEffectNatives.java13
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/DiscretePathEffectNatives.java13
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/EmbossMaskFilterNatives.java30
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/FontBuilderNatives.java47
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/FontFamilyBuilderNatives.java37
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/FontFamilyNatives.java51
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/FontFileUtilNatives.java35
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/FontNatives.java61
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/FontsFontFamilyNatives.java37
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/HardwareRendererNatives.java173
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/HardwareRendererObserverNatives.java29
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/ImageDecoderNatives.java81
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/InterpolatorNatives.java25
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/LightingColorFilterNatives.java30
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/LineBreakerNatives.java87
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/LinearGradientNatives.java35
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/MaskFilterNatives.java14
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/MatrixNatives.java123
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/MeasuredTextBuilderNatives.java56
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/MeasuredTextNatives.java42
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/NIOAccess.java100
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/NativeAllocationRegistryNatives.java13
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/NativeInterpolatorFactoryNatives.java34
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/NinePatchNatives.java39
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/PaintNatives.java442
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/PathDashPathEffectNatives.java15
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/PathEffectNatives.java14
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/PathMeasureNatives.java34
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/PathNatives.java111
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/PathParserNatives.java31
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/PictureNatives.java32
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/PorterDuffColorFilterNatives.java30
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/PropertyValuesHolderNatives.java41
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/RadialGradientNatives.java33
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/RecordingCanvasNatives.java70
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/RegionIteratorNatives.java20
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/RegionNatives.java66
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/RenderEffectNatives.java39
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/RenderNodeAnimatorNatives.java56
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/RenderNodeNatives.java211
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/RuntimeShaderNatives.java23
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/ShaderNatives.java14
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/SumPathEffectNatives.java14
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/SurfaceNatives.java80
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/SweepGradientNatives.java20
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/TableMaskFilterNatives.java34
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/TypefaceNatives.java72
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/VectorDrawableNatives.java150
-rw-r--r--nativeruntime/src/main/java/org/robolectric/nativeruntime/VirtualRefBasePtrNatives.java16
-rw-r--r--nativeruntime/src/main/resources/fonts/AndroidClock.ttfbin0 -> 4540 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/CarroisGothicSC-Regular.ttfbin0 -> 40308 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/ComingSoon.ttfbin0 -> 59136 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/CutiveMono.ttfbin0 -> 69180 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/DancingScript-Bold.ttfbin0 -> 115568 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/DancingScript-Regular.ttfbin0 -> 116580 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/DroidSans-Bold.ttfbin0 -> 2371608 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/DroidSans.ttfbin0 -> 2371608 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/DroidSansMono.ttfbin0 -> 108128 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoColorEmoji.ttfbin0 -> 2796376 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoColorEmojiFlags.ttfbin0 -> 849060 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoColorEmojiLegacy.ttfbin0 -> 9022672 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoNaskhArabic-Bold.ttfbin0 -> 115784 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoNaskhArabic-Regular.ttfbin0 -> 116716 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoNaskhArabicUI-Bold.ttfbin0 -> 119356 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoNaskhArabicUI-Regular.ttfbin0 -> 120800 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansAdlam-VF.ttfbin0 -> 99448 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansAhom-Regular.otfbin0 -> 13852 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansAnatolianHieroglyphs-Regular.otfbin0 -> 134908 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansArmenian-VF.ttfbin0 -> 33844 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansAvestan-Regular.ttfbin0 -> 11852 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansBalinese-Regular.ttfbin0 -> 32084 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansBamum-Regular.ttfbin0 -> 133732 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansBassaVah-Regular.otfbin0 -> 6332 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansBatak-Regular.ttfbin0 -> 12808 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansBengali-VF.ttfbin0 -> 249244 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansBengaliUI-VF.ttfbin0 -> 246772 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansBhaiksuki-Regular.otfbin0 -> 100344 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansBrahmi-Regular.ttfbin0 -> 23652 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansBuginese-Regular.ttfbin0 -> 7844 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansBuhid-Regular.ttfbin0 -> 4896 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansCJK-Regular.ttcbin0 -> 19474972 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansCanadianAboriginal-Regular.ttfbin0 -> 62852 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansCarian-Regular.ttfbin0 -> 6260 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansChakma-Regular.otfbin0 -> 29512 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansCham-Bold.ttfbin0 -> 22272 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansCham-Regular.ttfbin0 -> 22264 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansCherokee-Regular.ttfbin0 -> 65132 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansCoptic-Regular.ttfbin0 -> 21984 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansCuneiform-Regular.ttfbin0 -> 500380 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansCypriot-Regular.ttfbin0 -> 7980 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansDeseret-Regular.ttfbin0 -> 10512 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansDevanagari-VF.ttfbin0 -> 253156 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansDevanagariUI-VF.ttfbin0 -> 247460 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansEgyptianHieroglyphs-Regular.ttfbin0 -> 505436 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansElbasan-Regular.otfbin0 -> 8788 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansEthiopic-VF.ttfbin0 -> 448888 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansGeorgian-VF.ttfbin0 -> 63180 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansGlagolitic-Regular.ttfbin0 -> 15676 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansGothic-Regular.ttfbin0 -> 6032 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansGrantha-Regular.ttfbin0 -> 399684 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansGujarati-Bold.ttfbin0 -> 117020 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansGujarati-Regular.ttfbin0 -> 120040 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansGujaratiUI-Bold.ttfbin0 -> 114312 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansGujaratiUI-Regular.ttfbin0 -> 117380 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansGunjalaGondi-Regular.otfbin0 -> 32336 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansGurmukhi-VF.ttfbin0 -> 63884 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansGurmukhiUI-VF.ttfbin0 -> 64008 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansHanifiRohingya-Regular.otfbin0 -> 13432 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansHanunoo-Regular.ttfbin0 -> 6768 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansHatran-Regular.otfbin0 -> 4388 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansHebrew-Bold.ttfbin0 -> 17540 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansHebrew-Regular.ttfbin0 -> 17352 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansImperialAramaic-Regular.ttfbin0 -> 5632 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansInscriptionalPahlavi-Regular.ttfbin0 -> 5668 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansInscriptionalParthian-Regular.ttfbin0 -> 7508 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansJavanese-Regular.otfbin0 -> 86944 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansKaithi-Regular.ttfbin0 -> 59452 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansKannada-VF.ttfbin0 -> 211752 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansKannadaUI-VF.ttfbin0 -> 212632 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansKayahLi-Regular.ttfbin0 -> 8024 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansKharoshthi-Regular.ttfbin0 -> 21860 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansKhmer-VF.ttfbin0 -> 143484 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansKhmerUI-Bold.ttfbin0 -> 36216 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansKhmerUI-Regular.ttfbin0 -> 37932 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansKhojki-Regular.otfbin0 -> 26928 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansLao-Bold.ttfbin0 -> 18848 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansLao-Regular.ttfbin0 -> 18956 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansLaoUI-Bold.ttfbin0 -> 29228 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansLaoUI-Regular.ttfbin0 -> 30568 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansLepcha-Regular.ttfbin0 -> 102060 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansLimbu-Regular.ttfbin0 -> 11100 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansLinearA-Regular.otfbin0 -> 33916 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansLinearB-Regular.ttfbin0 -> 57936 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansLisu-Regular.ttfbin0 -> 5676 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansLycian-Regular.ttfbin0 -> 4308 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansLydian-Regular.ttfbin0 -> 4268 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansMalayalam-VF.ttfbin0 -> 137732 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansMalayalamUI-VF.ttfbin0 -> 135660 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansMandaic-Regular.ttfbin0 -> 15328 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansManichaean-Regular.otfbin0 -> 16608 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansMarchen-Regular.otfbin0 -> 63992 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansMasaramGondi-Regular.otfbin0 -> 23016 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansMedefaidrin-VF.ttfbin0 -> 55888 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansMeeteiMayek-Regular.ttfbin0 -> 13940 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansMeroitic-Regular.otfbin0 -> 20064 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansMiao-Regular.otfbin0 -> 26768 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansModi-Regular.ttfbin0 -> 42688 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansMongolian-Regular.ttfbin0 -> 113656 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansMro-Regular.otfbin0 -> 5680 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansMultani-Regular.otfbin0 -> 7808 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansMyanmar-Bold.otfbin0 -> 110508 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansMyanmar-Medium.otfbin0 -> 115600 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansMyanmar-Regular.otfbin0 -> 110380 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansMyanmarUI-Bold.otfbin0 -> 112636 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansMyanmarUI-Medium.otfbin0 -> 117084 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansMyanmarUI-Regular.otfbin0 -> 112332 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansNKo-Regular.ttfbin0 -> 18716 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansNabataean-Regular.otfbin0 -> 6624 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansNewTaiLue-Regular.ttfbin0 -> 14908 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansNewa-Regular.otfbin0 -> 66132 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansOgham-Regular.ttfbin0 -> 4464 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansOlChiki-Regular.ttfbin0 -> 7628 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansOldItalic-Regular.ttfbin0 -> 5000 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansOldNorthArabian-Regular.otfbin0 -> 6276 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansOldPermic-Regular.otfbin0 -> 8628 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansOldPersian-Regular.ttfbin0 -> 12420 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansOldSouthArabian-Regular.ttfbin0 -> 4724 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansOldTurkic-Regular.ttfbin0 -> 7856 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansOriya-Bold.ttfbin0 -> 84624 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansOriya-Regular.ttfbin0 -> 85224 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansOriyaUI-Bold.ttfbin0 -> 80180 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansOriyaUI-Regular.ttfbin0 -> 80728 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansOsage-Regular.ttfbin0 -> 10556 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansOsmanya-Regular.ttfbin0 -> 7472 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansPahawhHmong-Regular.otfbin0 -> 13228 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansPalmyrene-Regular.otfbin0 -> 8604 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansPauCinHau-Regular.otfbin0 -> 8204 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansPhagsPa-Regular.ttfbin0 -> 28404 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansPhoenician-Regular.ttfbin0 -> 5404 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansRejang-Regular.ttfbin0 -> 6476 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansRunic-Regular.ttfbin0 -> 8528 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansSamaritan-Regular.ttfbin0 -> 9772 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansSaurashtra-Regular.ttfbin0 -> 18720 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansSharada-Regular.otfbin0 -> 27752 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansShavian-Regular.ttfbin0 -> 6020 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansSinhala-VF.ttfbin0 -> 358248 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansSinhalaUI-VF.ttfbin0 -> 358268 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansSoraSompeng-Regular.otfbin0 -> 6388 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansSoyombo-VF.ttfbin0 -> 72168 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansSundanese-Regular.ttfbin0 -> 10356 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansSylotiNagri-Regular.ttfbin0 -> 14904 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansSymbols-Regular-Subsetted.ttfbin0 -> 708968 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansSymbols-Regular-Subsetted2.ttfbin0 -> 32996 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansSyriacEastern-Regular.ttfbin0 -> 50164 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansSyriacEstrangela-Regular.ttfbin0 -> 46396 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansSyriacWestern-Regular.ttfbin0 -> 52380 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansTagalog-Regular.ttfbin0 -> 5640 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansTagbanwa-Regular.ttfbin0 -> 5596 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansTaiLe-Regular.ttfbin0 -> 10012 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansTaiTham-Regular.ttfbin0 -> 48064 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansTaiViet-Regular.ttfbin0 -> 15852 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansTakri-VF.ttfbin0 -> 22172 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansTamil-VF.ttfbin0 -> 79724 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansTamilUI-VF.ttfbin0 -> 132512 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansTelugu-VF.ttfbin0 -> 275016 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansTeluguUI-VF.ttfbin0 -> 282268 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansThaana-Bold.ttfbin0 -> 14504 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansThaana-Regular.ttfbin0 -> 14412 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansThai-Bold.ttfbin0 -> 18336 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansThai-Regular.ttfbin0 -> 21380 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansThaiUI-Bold.ttfbin0 -> 19000 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansThaiUI-Regular.ttfbin0 -> 22024 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansTifinagh-Regular.otfbin0 -> 24924 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansUgaritic-Regular.ttfbin0 -> 6772 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansVai-Regular.ttfbin0 -> 64292 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansWancho-Regular.otfbin0 -> 14216 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansWarangCiti-Regular.otfbin0 -> 23484 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSansYi-Regular.ttfbin0 -> 122904 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSerif-Bold.ttfbin0 -> 247892 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSerif-BoldItalic.ttfbin0 -> 263080 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSerif-Italic.ttfbin0 -> 249748 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSerif-Regular.ttfbin0 -> 246740 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSerifArmenian-VF.ttfbin0 -> 33992 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSerifBengali-VF.ttfbin0 -> 292736 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSerifCJK-Regular.ttcbin0 -> 24774996 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSerifDevanagari-VF.ttfbin0 -> 304140 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSerifDogra-Regular.ttfbin0 -> 27480 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSerifEthiopic-VF.ttfbin0 -> 412896 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSerifGeorgian-VF.ttfbin0 -> 76856 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSerifGujarati-VF.ttfbin0 -> 177380 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSerifGurmukhi-VF.ttfbin0 -> 57172 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSerifHebrew-Bold.ttfbin0 -> 20232 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSerifHebrew-Regular.ttfbin0 -> 20312 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSerifKannada-VF.ttfbin0 -> 249112 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSerifKhmer-Bold.otfbin0 -> 48076 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSerifKhmer-Regular.otfbin0 -> 40688 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSerifLao-Bold.ttfbin0 -> 23376 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSerifLao-Regular.ttfbin0 -> 23664 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSerifMalayalam-VF.ttfbin0 -> 124804 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSerifMyanmar-Bold.otfbin0 -> 136260 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSerifMyanmar-Regular.otfbin0 -> 137544 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSerifNyiakengPuachueHmong-VF.ttfbin0 -> 41084 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSerifSinhala-VF.ttfbin0 -> 352220 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSerifTamil-VF.ttfbin0 -> 90464 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSerifTelugu-VF.ttfbin0 -> 340420 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSerifThai-Bold.ttfbin0 -> 26856 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSerifThai-Regular.ttfbin0 -> 26824 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSerifTibetan-VF.ttfbin0 -> 1100000 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/NotoSerifYezidi-VF.ttfbin0 -> 23744 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/Roboto-Regular.ttfbin0 -> 2371608 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/RobotoStatic-Regular.ttfbin0 -> 305656 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/SourceSansPro-Bold.ttfbin0 -> 290916 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/SourceSansPro-BoldItalic.ttfbin0 -> 103200 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/SourceSansPro-Italic.ttfbin0 -> 103828 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/SourceSansPro-Regular.ttfbin0 -> 293516 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/SourceSansPro-SemiBold.ttfbin0 -> 291864 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/SourceSansPro-SemiBoldItalic.ttfbin0 -> 103556 bytes
-rw-r--r--nativeruntime/src/main/resources/fonts/fonts.xml1545
-rw-r--r--nativeruntime/src/main/resources/icu/icudt68l.datbin0 -> 25764768 bytes
-rw-r--r--nativeruntime/src/test/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLazyLoadTest.java29
-rw-r--r--nativeruntime/src/test/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLoaderTest.java21
-rw-r--r--pluginapi/Android.bp41
-rw-r--r--plugins/maven-dependency-resolver/src/main/java/org/robolectric/MavenRoboSettings.java34
-rw-r--r--plugins/maven-dependency-resolver/src/main/java/org/robolectric/internal/dependency/MavenArtifactFetcher.java31
-rwxr-xr-xplugins/maven-dependency-resolver/src/main/java/org/robolectric/internal/dependency/MavenDependencyResolver.java29
-rw-r--r--plugins/maven-dependency-resolver/src/test/java/org/robolectric/MavenRoboSettingsTest.java18
-rw-r--r--plugins/maven-dependency-resolver/src/test/java/org/robolectric/internal/dependency/MavenDependencyResolverTest.java12
-rw-r--r--preinstrumented/src/main/java/org/robolectric/preinstrumented/JarInstrumentor.java20
-rw-r--r--processor/Android.bp93
-rw-r--r--processor/sdks.txt15
-rw-r--r--processor/src/main/java/org/robolectric/annotation/processing/RobolectricModel.java8
-rw-r--r--resources/Android.bp46
-rw-r--r--resources/src/main/java/org/robolectric/manifest/AndroidManifest.java7
-rw-r--r--resources/src/main/java/org/robolectric/res/android/FileMap.java61
-rw-r--r--resources/src/test/java/org/robolectric/res/android/ZipFileROTest.java32
-rw-r--r--robolectric/Android.bp98
-rwxr-xr-xrobolectric/src/main/java/org/robolectric/android/internal/AndroidTestEnvironment.java2
-rw-r--r--robolectric/src/main/java/org/robolectric/internal/dependency/LocalDependencyResolver.java11
-rw-r--r--robolectric/src/main/java/org/robolectric/junit/rules/ExpectedLogMessagesRule.java16
-rw-r--r--robolectric/src/main/java/org/robolectric/plugins/DefaultSdkProvider.java5
-rw-r--r--robolectric/src/main/java/org/robolectric/plugins/GraphicsModeConfigurer.java65
-rw-r--r--robolectric/src/main/java/org/robolectric/plugins/LegacyDependencyResolver.java11
-rw-r--r--robolectric/src/test/java/org/robolectric/junit/rules/ExpectedLogMessagesRuleTest.java8
-rw-r--r--robolectric/src/test/java/org/robolectric/plugins/GraphicsModeConfigurerTest.java21
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ResponderLocationBuilderTest.java93
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowAbstractCursorTest.java6
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowAccessibilityNodeInfoTest.java12
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowAlarmManagerTest.java850
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowAnimationUtilsTest.java15
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowAudioManagerTest.java236
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowBitmapTest.java36
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothAdapterTest.java45
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothGattTest.java450
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothHeadsetTest.java15
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowDevicePolicyManagerTest.java88
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowImsMmTelManagerTest.java171
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowInsetsControllerTest.java72
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowLayoutAnimationControllerTest.java31
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowMatrixTest.java17
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowMediaControllerTest.java28
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowNetworkCapabilitiesTest.java7
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowPackageManagerTest.java137
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowPhoneWindowTest.java24
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowSensorManagerTest.java63
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowSubscriptionManagerTest.java34
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyManagerTest.java12
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowTypefaceTest.java2
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowUserManagerTest.java15
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowViewRootImplTest.java55
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowWallpaperManagerTest.java147
-rw-r--r--robolectric/src/test/java/org/robolectric/shadows/ShadowWifiManagerTest.java15
-rw-r--r--sandbox/Android.bp69
-rw-r--r--sandbox/src/main/java/org/robolectric/config/AndroidConfigurer.java4
-rw-r--r--sandbox/src/main/java/org/robolectric/internal/bytecode/ClassInstrumentor.java51
-rw-r--r--sandbox/src/main/java/org/robolectric/internal/bytecode/MutableClass.java4
-rw-r--r--sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowMap.java45
-rw-r--r--shadowapi/Android.bp45
-rw-r--r--shadows/framework/Android.bp68
-rw-r--r--shadows/framework/build.gradle2
-rw-r--r--shadows/framework/src/main/java/android/media/Session2Token.java10
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/GraphicsShadowPicker.java32
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ImageUtil.java8
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ResponderLocationBuilder.java211
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowAbstractCursor.java23
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityNodeInfo.java3
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowAlarmManager.java800
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowAnimationUtils.java3
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowAppWidgetManager.java20
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplicationPackageManager.java47
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowAudioManager.java117
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowBackdropFrameRenderer.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowBitmap.java833
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowBitmapDrawable.java4
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowBitmapFactory.java12
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothAdapter.java19
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothDevice.java18
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothGatt.java219
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothHeadset.java23
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowCamcorderProfile.java66
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowCanvas.java639
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowCarrierConfigManager.java12
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentProvider.java10
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentResolver.java40
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyManager.java29
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayListCanvas.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowImsMmTelManager.java95
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowInsetsController.java77
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowLayoutAnimationController.java24
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyBitmap.java922
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyCanvas.java642
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyMatrix.java662
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyPath.java558
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyTypeface.java273
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowMatrix.java646
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaController.java23
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaStore.java6
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeAllocationRegistry.java56
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeAnimatedImageDrawable.java125
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeAnimatedVectorDrawable.java120
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBaseCanvas.java877
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBaseRecordingCanvas.java597
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmap.java504
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmapDrawable.java42
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmapFactory.java153
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmapShader.java69
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBlendModeColorFilter.java29
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBlurMaskFilter.java28
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCanvas.java209
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCanvasProperty.java38
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColor.java34
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorFilter.java32
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorMatrixColorFilter.java28
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorSpaceRgb.java39
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeComposePathEffect.java28
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeComposeShader.java30
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCornerPathEffect.java28
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeDashPathEffect.java28
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeDiscretePathEffect.java28
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeDisplayListCanvas.java72
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeEmbossMaskFilter.java29
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFont.java281
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontFamily.java96
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontFileUtil.java45
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontsFontFamily.java86
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeHardwareRenderer.java396
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeHardwareRendererObserver.java60
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeImageDecoder.java211
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeInterpolator.java55
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLightingColorFilter.java28
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLineBreaker.java97
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLinearGradient.java60
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMaskFilter.java26
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMatrix.java264
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMeasuredParagraph.java73
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMeasuredText.java135
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeNativeInterpolatorFactory.java85
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeNinePatch.java50
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePaint.java847
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePath.java243
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathDashPathEffect.java28
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathEffect.java26
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathMeasure.java76
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathParser.java76
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePicture.java72
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePorterDuffColorFilter.java39
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePropertyValuesHolder.java85
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRadialGradient.java83
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRecordingCanvas.java112
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRegion.java184
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRegionIterator.java39
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderEffect.java77
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderNode.java468
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderNodeAnimator.java96
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderNodeAnimatorQ.java100
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderNodeOP.java465
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRuntimeShader.java180
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeShader.java28
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeStaticLayout.java330
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSumPathEffect.java28
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSurface.java147
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSweepGradient.java44
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSystemFonts.java125
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeTableMaskFilter.java44
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeThreadedRenderer.java183
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeTypeface.java291
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeVectorDrawable.java343
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeVirtualRefBasePtr.java35
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNetworkCapabilities.java10
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNoopNativeAllocationRegistry.java26
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowNumberPicker.java62
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowOutline.java15
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowPath.java557
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowPathMeasure.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedLooper.java7
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowPhoneWindow.java22
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowPosix.java8
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowPreference.java3
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowRecordingCanvas.java2
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowScaleGestureDetector.java43
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowSearchManager.java32
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowSensorManager.java23
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowSettings.java23
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowSubscriptionManager.java40
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemVibrator.java16
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java20
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowTypeface.java266
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowUserManager.java3
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowVibrator.java48
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowViewRootImpl.java84
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowWallpaperManager.java52
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowWifiManager.java27
-rw-r--r--shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindowManagerGlobal.java292
-rw-r--r--shadows/httpclient/Android.bp78
-rw-r--r--shadows/multidex/Android.bp26
-rw-r--r--shadows/playservices/Android.bp22
-rw-r--r--utils/Android.bp56
-rw-r--r--utils/reflector/Android.bp42
488 files changed, 25019 insertions, 4069 deletions
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index b6f85759e..c6d887ee4 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -10,6 +10,9 @@ on:
permissions:
contents: read
+env:
+ cache-version: v1
+
jobs:
build:
runs-on: ubuntu-20.04
@@ -99,7 +102,7 @@ jobs:
path: '**/build/test-results/**/TEST-*.xml'
instrumentation-tests:
- runs-on: macos-11
+ runs-on: macos-12
timeout-minutes: 60
needs: build
@@ -135,7 +138,7 @@ jobs:
path: |
~/.android/avd/*
~/.android/adb*
- key: avd-${{ matrix.api-level }}
+ key: avd-${{ matrix.api-level }}-${{ env.cache-version }}
- name: Create AVD and generate snapshot for caching
if: steps.avd-cache.outputs.cache-hit != 'true'
@@ -156,6 +159,11 @@ jobs:
api-level: ${{ matrix.api-level }}
target: ${{ steps.determine-target.outputs.TARGET }}
arch: x86_64
+ 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
diff --git a/.gitignore b/.gitignore
index 03ef5e0a3..cae6b8035 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,6 +21,9 @@ release.properties
.gradle/
build
+# Android Profiling
+*.hprof
+
# IntelliJ
.idea
*.iml
@@ -40,7 +43,6 @@ classes
tmp
local.properties
-
# CTS stuff
cts/
cts-libs/
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 000000000..250a13b33
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,223 @@
+// Copyright (C) 2019 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 {
+ default_visibility: [":__subpackages__"],
+ default_applicable_licenses: ["external_robolectric_license"],
+}
+
+// Added automatically by a large-scale-change that took the approach of
+// 'apply every license found to every target'. While this makes sure we respect
+// every license restriction, it may not be entirely correct.
+//
+// e.g. GPL in an MIT project might only apply to the contrib/ directory.
+//
+// Please consider splitting the single license below into multiple licenses,
+// taking care not to lose any license_kind information, and overriding the
+// default license using the 'licenses: [...]' property on targets as needed.
+//
+// For unused files, consider creating a 'fileGroup' with "//visibility:private"
+// to attach the license to, and including a comment whether the files may be
+// used in the current project.
+// See: http://go/android-license-faq
+license {
+ name: "external_robolectric_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ "SPDX-license-identifier-MIT",
+ ],
+ license_text: [
+ "LICENSE",
+ ],
+}
+
+// Empty library. Should be removed
+java_library {
+ name: "robolectric_android-all-stub_upstream",
+ visibility: ["//visibility:public"],
+}
+
+// build.prop file created by module type defined in soong/robolectric.go
+robolectric_build_props {
+ name: "robolectric_build_props_upstream",
+}
+
+java_genrule_host {
+ name: "robolectric_framework_res_upstream",
+ tools: ["zip2zip"],
+ srcs: [":framework-res"],
+ out: ["robolectric_framework_res_upstream.jar"],
+ cmd: "$(location zip2zip) " +
+ "-i $(location :framework-res) " +
+ "-o $(location robolectric_framework_res_upstream.jar) " +
+ "-x classes.dex " +
+ "-x META-INF/**/* " +
+ "-0 resources.arsc",
+}
+
+java_device_for_host {
+ name: "robolectric_android-all-device-deps_upstream",
+ libs: [
+ "conscrypt-for-host",
+ "core-icu4j-for-host",
+ "core-libart-for-host",
+ "ext",
+ "framework-all",
+ "icu4j-icudata-jarjar",
+ "icu4j-icutzdata-jarjar",
+ "ims-common",
+ "libphonenumber-platform",
+ "okhttp-for-host",
+ "services",
+ "services.accessibility",
+ "telephony-common",
+ "android.car",
+ "androidx.test.monitor",
+ "androidx.test.ext.truth", // -nodep?
+ ],
+}
+
+java_library_host {
+ name: "robolectric-host-android_all_upstream",
+ static_libs: [
+ "robolectric_android-all-device-deps_upstream",
+ "robolectric_tzdata",
+ "robolectric_framework_res_upstream",
+ ],
+ dist: {
+ targets: [
+ "sdk",
+ "win_sdk",
+ ],
+ dest: "android-all-robolectric_upstream.jar",
+ },
+
+ java_resources: [
+ // Copy the build.prop
+ ":robolectric_build_props_upstream",
+ ],
+ visibility: [
+ ":__subpackages__",
+ "//prebuilts/misc/common/robolectric",
+ ],
+}
+
+//#############################################
+// Assemble Robolectric_all
+//#############################################
+
+// This is a hack and should be removed with proper resource merging a la maven-shaded-plugin
+java_genrule_host {
+ name: "robolectric_meta_service_file",
+ out: ["robolectric_meta_service_file.jar"],
+ tools: ["soong_zip"],
+ cmd: "mkdir -p $(genDir)/META-INF/services/ && " +
+ "echo -e 'org.robolectric.Shadows\norg.robolectric.shadows.httpclient.Shadows\norg.robolectric.shadows.multidex.Shadows' > " +
+ "$(genDir)/META-INF/services/org.robolectric.internal.ShadowProvider &&" +
+ "$(location soong_zip) -o $(out) -C $(genDir) -D $(genDir)/META-INF/services/",
+}
+
+java_library_host {
+ name: "Robolectric_all_upstream",
+
+ static_libs: [
+ "robolectric_meta_service_file",
+ "Robolectric_shadows_httpclient_upstream",
+ "Robolectric_shadows_framework_upstream",
+ "Robolectric_shadows_multidex_upstream",
+ "Robolectric_robolectric_upstream",
+ "Robolectric_annotations_upstream",
+ "Robolectric_resources_upstream",
+ "Robolectric_shadowapi_upstream",
+ "Robolectric_sandbox_upstream",
+ "Robolectric_junit_upstream",
+ "Robolectric_utils_upstream",
+ "Robolectric_utils_reflector_upstream",
+ "Robolectric_nativeruntime_upstream",
+ "asm-9.2",
+ "junit",
+ "asm-tree-9.2",
+ "guava",
+ "asm-commons-9.2",
+ "bouncycastle-unbundled",
+ "conscrypt-unbundled",
+ "robolectric-sqlite4java-0.282",
+ "hamcrest",
+ "hamcrest-library",
+ "robolectric-host-androidx-test-runner_upstream",
+ "robolectric-host-org_apache_http_legacy_upstream", //TODO: remove
+ ],
+
+ java_resource_dirs: [
+ "shadows/framework/src/main/resources",
+ "src/main/resources",
+ ],
+}
+
+// Make Robolectric_all available as a target jar, but treated as an aar
+java_host_for_device {
+ name: "Robolectric_all-target_upstream",
+ libs: ["Robolectric_all_upstream"],
+ visibility: ["//visibility:public"],
+}
+
+// Make dependencies available as host jars
+java_device_for_host {
+ name: "robolectric-host-androidx-test-core_upstream",
+ libs: ["androidx.test.core"],
+}
+
+java_device_for_host {
+ name: "robolectric-host-androidx-test-ext-junit_upstream",
+ libs: ["androidx.test.ext.junit"],
+}
+
+java_device_for_host {
+ name: "robolectric-host-androidx-test-monitor_upstream",
+ libs: ["androidx.test.monitor"],
+}
+
+java_device_for_host {
+ name: "robolectric-host-androidx-test-runner_upstream",
+ libs: ["androidx.test.runner"],
+}
+
+java_device_for_host {
+ name: "robolectric-host-androidx_upstream",
+ libs: ["androidx.fragment_fragment"],
+}
+
+java_device_for_host {
+ name: "robolectric-host-androidx_test_espresso",
+ libs: ["androidx.test.espresso.idling-resource"],
+}
+
+//java_device_for_host {
+// name: "robolectric-host-android-support-v4_upstream",
+// libs: ["android-support-v4"],
+//}
+
+java_device_for_host {
+ name: "robolectric-host-android-support-multidex_upstream",
+ libs: [
+ "android-support-multidex",
+ "com.android.support.multidex_1.0.3",
+ ],
+}
+
+java_device_for_host {
+ name: "robolectric-host-org_apache_http_legacy_upstream",
+ libs: ["org.apache.http.legacy.stubs"],
+}
diff --git a/OWNERS b/OWNERS
index d8cb0bf40..e0564f164 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1 +1,5 @@
rexhoffman@google.com
+yuwu@google.com
+congxiliu@google.com
+ramperi@google.com
+ihcinihsdk@google.com
diff --git a/annotations/Android.bp b/annotations/Android.bp
new file mode 100644
index 000000000..b325236d0
--- /dev/null
+++ b/annotations/Android.bp
@@ -0,0 +1,22 @@
+//#############################################
+// Compile Robolectric annotations
+//#############################################
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "external_robolectric-shadows_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-MIT
+ default_applicable_licenses: ["external_robolectric_license"],
+}
+
+java_library_host {
+ name: "Robolectric_annotations_upstream",
+ static_libs: [
+ "jsr305",
+ ],
+ libs: ["robolectric-host-android_all_upstream"],
+ srcs: ["src/main/java/**/*.java"],
+ visibility: ["//visibility:public"],
+}
diff --git a/annotations/src/main/java/org/robolectric/annotation/GraphicsMode.java b/annotations/src/main/java/org/robolectric/annotation/GraphicsMode.java
new file mode 100644
index 000000000..06f785a4c
--- /dev/null
+++ b/annotations/src/main/java/org/robolectric/annotation/GraphicsMode.java
@@ -0,0 +1,27 @@
+package org.robolectric.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * A {@link org.robolectric.pluginapi.config.Configurer} annotation for controlling which graphics
+ * shadow implementation is used for the {@link android.graphics} package.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PACKAGE, ElementType.TYPE, ElementType.METHOD})
+public @interface GraphicsMode {
+
+ /** Specifies the different supported graphics modes. */
+ enum Mode {
+ /** Use legacy graphics shadows that are no-ops and fakes. */
+ LEGACY,
+ /** Use graphics shadows libraries backed by native Android graphics code. */
+ NATIVE,
+ }
+
+ Mode value();
+}
diff --git a/buildSrc/src/main/groovy/org/robolectric/gradle/GradleManagedDevicePlugin.groovy b/buildSrc/src/main/groovy/org/robolectric/gradle/GradleManagedDevicePlugin.groovy
index 7289d0c14..c170f3058 100644
--- a/buildSrc/src/main/groovy/org/robolectric/gradle/GradleManagedDevicePlugin.groovy
+++ b/buildSrc/src/main/groovy/org/robolectric/gradle/GradleManagedDevicePlugin.groovy
@@ -8,6 +8,7 @@ class GradleManagedDevicePlugin implements Plugin<Project> {
@Override
void apply(Project project) {
project.android.testOptions {
+ animationsDisabled = true
devices {
// ./gradlew -Pandroid.sdk.channel=3 nexusOneApi29DebugAndroidTest
nexusOneApi29(ManagedVirtualDevice) {
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 b5aceb4cc..06c634f1c 100644
--- a/errorprone/src/main/java/org/robolectric/errorprone/bugpatterns/RobolectricShadow.java
+++ b/errorprone/src/main/java/org/robolectric/errorprone/bugpatterns/RobolectricShadow.java
@@ -20,10 +20,12 @@ import com.sun.source.doctree.StartElementTree;
import com.sun.source.doctree.TextTree;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ClassTree;
+import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ModifiersTree;
+import com.sun.source.util.DocSourcePositions;
import com.sun.source.util.DocTreePath;
import com.sun.source.util.DocTreePathScanner;
import com.sun.source.util.TreePathScanner;
@@ -31,7 +33,6 @@ import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.tree.DCTree.DCDocComment;
import com.sun.tools.javac.tree.DCTree.DCReference;
-import com.sun.tools.javac.tree.DCTree.DCStartElement;
import com.sun.tools.javac.tree.JCTree.JCAssign;
import com.sun.tools.javac.tree.JCTree.JCIdent;
import java.util.ArrayList;
@@ -113,11 +114,12 @@ public final class RobolectricShadow extends BugChecker implements ClassTreeMatc
@Override
public Void visitStartElement(StartElementTree startElementTree, Void aVoid) {
if (startElementTree.getName().toString().equalsIgnoreCase("p")) {
- DCStartElement node = (DCStartElement) startElementTree;
-
DocTreePath path = getCurrentPath();
- int start = (int) node.getSourcePosition((DCDocComment) path.getDocComment()) + node.pos;
- int end = node.getEndPos((DCDocComment) getCurrentPath().getDocComment());
+ DCDocComment doc = (DCDocComment) path.getDocComment();
+ DocSourcePositions positions = trees.getSourcePositions();
+ CompilationUnitTree compilationUnitTree = path.getTreePath().getCompilationUnit();
+ int start = (int) positions.getStartPosition(compilationUnitTree, doc, startElementTree);
+ int end = (int) positions.getEndPosition(compilationUnitTree, doc, startElementTree);
fixes.add(Optional.of(SuggestedFix.replace(start, end, "")));
}
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 fddac6f54..319b873a8 100644
--- a/integration_tests/ctesque/src/sharedTest/java/android/graphics/BitmapTest.java
+++ b/integration_tests/ctesque/src/sharedTest/java/android/graphics/BitmapTest.java
@@ -11,6 +11,7 @@ import static android.os.Build.VERSION_CODES.Q;
import static androidx.test.InstrumentationRegistry.getTargetContext;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeFalse;
import android.content.res.Resources;
import android.graphics.Bitmap.CompressFormat;
@@ -50,6 +51,7 @@ public class BitmapTest {
@Config(minSdk = P)
@SdkSuppress(minSdkVersion = P)
@Test public void createBitmap() {
+ assumeFalse(Boolean.getBoolean("robolectric.nativeruntime.enableGraphics"));
// Bitmap.createBitmap(Picture) requires hardware-backed bitmaps
HardwareRendererCompat.setDrawingEnabled(true);
Picture picture = new Picture();
diff --git a/integration_tests/ctesque/src/sharedTest/java/android/text/format/DateFormatTest.java b/integration_tests/ctesque/src/sharedTest/java/android/text/format/DateFormatTest.java
index 651e2c469..334356bd3 100644
--- a/integration_tests/ctesque/src/sharedTest/java/android/text/format/DateFormatTest.java
+++ b/integration_tests/ctesque/src/sharedTest/java/android/text/format/DateFormatTest.java
@@ -60,13 +60,15 @@ public class DateFormatTest {
@Test
public void getTimeFormat_am() {
+ // allow both regular and thin whitespace separators
assertThat(DateFormat.getTimeFormat(getApplicationContext()).format(dateAM))
- .isEqualTo("8:24 AM");
+ .matches("8:24\\sAM");
}
@Test
public void getTimeFormat_pm() {
+ // allow both regular and thin whitespace separators
assertThat(DateFormat.getTimeFormat(getApplicationContext()).format(datePM))
- .isEqualTo("4:24 PM");
+ .matches("4:24\\sPM");
}
}
diff --git a/junit/Android.bp b/junit/Android.bp
new file mode 100644
index 000000000..3ada376d0
--- /dev/null
+++ b/junit/Android.bp
@@ -0,0 +1,30 @@
+//##########################################
+// Compile Robolectric junit
+//##########################################
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "external_robolectric-shadows_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-MIT
+ default_applicable_licenses: ["external_robolectric_license"],
+}
+
+java_library_host {
+ name: "Robolectric_junit_upstream",
+ libs: [
+ "Robolectric_annotations_upstream",
+ "Robolectric_shadowapi_upstream",
+ "Robolectric_sandbox_upstream",
+ "Robolectric_utils_upstream",
+ "asm-commons-9.2",
+ "guava",
+ "asm-tree-9.2",
+ "hamcrest",
+ "junit",
+ "asm-9.2",
+ "jsr305",
+ ],
+ srcs: ["src/main/java/**/*.java"],
+}
diff --git a/nativeruntime/Android.bp b/nativeruntime/Android.bp
new file mode 100644
index 000000000..0d5484328
--- /dev/null
+++ b/nativeruntime/Android.bp
@@ -0,0 +1,46 @@
+//#############################################
+// Compile Robolectric shadows framework
+//#############################################
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "external_robolectric_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["external_robolectric_license"],
+}
+
+java_library_host {
+ name: "Robolectric_nativeruntime_upstream",
+ libs: [
+ "Robolectric_annotations_upstream",
+ "Robolectric_shadowapi_upstream",
+ "Robolectric_sandbox_upstream",
+ "Robolectric_resources_upstream",
+ "Robolectric_pluginapi_upstream",
+ "Robolectric_utils_upstream",
+ "Robolectric_utils_reflector_upstream",
+ "robolectric-accessibility-test-framework-2.1",
+ "robolectric-javax.annotation-api-1.2",
+ "hamcrest-library",
+ "hamcrest",
+ "robolectric-sqlite4java-0.282",
+ "guava",
+ //"icu4j",
+ "jsr305",
+ "error_prone_annotations",
+ "auto_service_annotations",
+ // "jsr330",
+ "robolectric-host-android_all_upstream",
+ ],
+ static_libs: [
+ "robolectric_nativeruntime_native_prebuilt",
+ ],
+ plugins: ["auto_service_plugin"],
+ srcs: [
+ "src/main/java/**/*.java",
+ "src/main/java/**/*.kt",
+ ],
+ java_resource_dirs: ["src/main/resources"],
+}
diff --git a/nativeruntime/build.gradle b/nativeruntime/build.gradle
index ecfe56915..495bf65f4 100644
--- a/nativeruntime/build.gradle
+++ b/nativeruntime/build.gradle
@@ -183,13 +183,16 @@ processResources {
dependencies {
api project(":utils")
-
- annotationProcessor "com.google.auto.service:auto-service:$autoServiceVersion"
+ api project(":utils:reflector")
api "com.google.guava:guava:$guavaJREVersion"
+ annotationProcessor "com.google.auto.service:auto-service:$autoServiceVersion"
compileOnly "com.google.auto.service:auto-service-annotations:$autoServiceVersion"
compileOnly AndroidSdk.MAX_SDK.coordinates
+ testCompileOnly AndroidSdk.MAX_SDK.coordinates
+ testRuntimeOnly AndroidSdk.MAX_SDK.coordinates
+ testImplementation project(":robolectric")
testImplementation "junit:junit:${junitVersion}"
testImplementation "com.google.truth:truth:${truthVersion}"
}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/AnimatedImageDrawableNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/AnimatedImageDrawableNatives.java
new file mode 100644
index 000000000..a5bcfb5d9
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/AnimatedImageDrawableNatives.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2022 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.nativeruntime;
+
+import android.graphics.ImageDecoder;
+import android.graphics.Rect;
+import android.graphics.drawable.AnimatedImageDrawable;
+
+/**
+ * Native methods for AnimatedImageDrawable JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
+ */
+public final class AnimatedImageDrawableNatives {
+ public static native long nCreate(
+ long nativeImageDecoder,
+ ImageDecoder decoder,
+ int width,
+ int height,
+ long colorSpaceHandle,
+ boolean extended,
+ Rect cropRect);
+
+ public static native long nGetNativeFinalizer();
+
+ public static native long nDraw(long nativePtr, long canvasNativePtr);
+
+ public static native void nSetAlpha(long nativePtr, int alpha);
+
+ public static native int nGetAlpha(long nativePtr);
+
+ public static native void nSetColorFilter(long nativePtr, long nativeFilter);
+
+ public static native boolean nIsRunning(long nativePtr);
+
+ public static native boolean nStart(long nativePtr);
+
+ public static native boolean nStop(long nativePtr);
+
+ public static native int nGetRepeatCount(long nativePtr);
+
+ public static native void nSetRepeatCount(long nativePtr, int repeatCount);
+
+ public static native void nSetOnAnimationEndListener(
+ long nativePtr, AnimatedImageDrawable drawable);
+
+ public static native long nNativeByteSize(long nativePtr);
+
+ public static native void nSetMirrored(long nativePtr, boolean mirror);
+
+ public static native void nSetBounds(long nativePtr, Rect rect);
+
+ private AnimatedImageDrawableNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/AnimatedVectorDrawableNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/AnimatedVectorDrawableNatives.java
new file mode 100644
index 000000000..6d71e6ce4
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/AnimatedVectorDrawableNatives.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2022 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.nativeruntime;
+
+import android.graphics.drawable.AnimatedVectorDrawable.VectorDrawableAnimatorRT;
+
+/**
+ * Native methods for AnimatedVectorDrawable JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
+ */
+public final class AnimatedVectorDrawableNatives {
+
+ public static native long nCreateAnimatorSet();
+
+ public static native void nSetVectorDrawableTarget(long animatorPtr, long vectorDrawablePtr);
+
+ public static native void nAddAnimator(
+ long setPtr,
+ long propertyValuesHolder,
+ long nativeInterpolator,
+ long startDelay,
+ long duration,
+ int repeatCount,
+ int repeatMode);
+
+ public static native void nSetPropertyHolderData(long nativePtr, float[] data, int length);
+
+ public static native void nSetPropertyHolderData(long nativePtr, int[] data, int length);
+
+ public static native void nStart(long animatorSetPtr, VectorDrawableAnimatorRT set, int id);
+
+ public static native void nReverse(long animatorSetPtr, VectorDrawableAnimatorRT set, int id);
+
+ public static native long nCreateGroupPropertyHolder(
+ long nativePtr, int propertyId, float startValue, float endValue);
+
+ public static native long nCreatePathDataPropertyHolder(
+ long nativePtr, long startValuePtr, long endValuePtr);
+
+ public static native long nCreatePathColorPropertyHolder(
+ long nativePtr, int propertyId, int startValue, int endValue);
+
+ public static native long nCreatePathPropertyHolder(
+ long nativePtr, int propertyId, float startValue, float endValue);
+
+ public static native long nCreateRootAlphaPropertyHolder(
+ long nativePtr, float startValue, float endValue);
+
+ public static native void nEnd(long animatorSetPtr);
+
+ public static native void nReset(long animatorSetPtr);
+
+ private AnimatedVectorDrawableNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/BaseCanvasNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/BaseCanvasNatives.java
new file mode 100644
index 000000000..f8931eaf8
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/BaseCanvasNatives.java
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2022 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.nativeruntime;
+
+import android.annotation.ColorLong;
+
+/**
+ * Native methods for BaseCanvas JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/BaseCanvas.java
+ */
+public final class BaseCanvasNatives {
+
+ public static native void nDrawBitmap(
+ long nativeCanvas,
+ long bitmapHandle,
+ float left,
+ float top,
+ long nativePaintOrZero,
+ int canvasDensity,
+ int screenDensity,
+ int bitmapDensity);
+
+ public static native void nDrawBitmap(
+ long nativeCanvas,
+ long bitmapHandle,
+ float srcLeft,
+ float srcTop,
+ float srcRight,
+ float srcBottom,
+ float dstLeft,
+ float dstTop,
+ float dstRight,
+ float dstBottom,
+ long nativePaintOrZero,
+ int screenDensity,
+ int bitmapDensity);
+
+ public static native void nDrawBitmap(
+ long nativeCanvas,
+ int[] colors,
+ int offset,
+ int stride,
+ float x,
+ float y,
+ int width,
+ int height,
+ boolean hasAlpha,
+ long nativePaintOrZero);
+
+ public static native void nDrawColor(long nativeCanvas, int color, int mode);
+
+ public static native void nDrawColor(
+ long nativeCanvas, long nativeColorSpace, @ColorLong long color, int mode);
+
+ public static native void nDrawPaint(long nativeCanvas, long nativePaint);
+
+ public static native void nDrawPoint(long canvasHandle, float x, float y, long paintHandle);
+
+ public static native void nDrawPoints(
+ long canvasHandle, float[] pts, int offset, int count, long paintHandle);
+
+ public static native void nDrawLine(
+ long nativeCanvas, float startX, float startY, float stopX, float stopY, long nativePaint);
+
+ public static native void nDrawLines(
+ long canvasHandle, float[] pts, int offset, int count, long paintHandle);
+
+ public static native void nDrawRect(
+ long nativeCanvas, float left, float top, float right, float bottom, long nativePaint);
+
+ public static native void nDrawOval(
+ long nativeCanvas, float left, float top, float right, float bottom, long nativePaint);
+
+ public static native void nDrawCircle(
+ long nativeCanvas, float cx, float cy, float radius, long nativePaint);
+
+ public static native void nDrawArc(
+ long nativeCanvas,
+ float left,
+ float top,
+ float right,
+ float bottom,
+ float startAngle,
+ float sweep,
+ boolean useCenter,
+ long nativePaint);
+
+ public static native void nDrawRoundRect(
+ long nativeCanvas,
+ float left,
+ float top,
+ float right,
+ float bottom,
+ float rx,
+ float ry,
+ long nativePaint);
+
+ public static native void nDrawDoubleRoundRect(
+ long nativeCanvas,
+ float outerLeft,
+ float outerTop,
+ float outerRight,
+ float outerBottom,
+ float outerRx,
+ float outerRy,
+ float innerLeft,
+ float innerTop,
+ float innerRight,
+ float innerBottom,
+ float innerRx,
+ float innerRy,
+ long nativePaint);
+
+ public static native void nDrawDoubleRoundRect(
+ long nativeCanvas,
+ float outerLeft,
+ float outerTop,
+ float outerRight,
+ float outerBottom,
+ float[] outerRadii,
+ float innerLeft,
+ float innerTop,
+ float innerRight,
+ float innerBottom,
+ float[] innerRadii,
+ long nativePaint);
+
+ public static native void nDrawPath(long nativeCanvas, long nativePath, long nativePaint);
+
+ public static native void nDrawRegion(long nativeCanvas, long nativeRegion, long nativePaint);
+
+ public static native void nDrawNinePatch(
+ long nativeCanvas,
+ long nativeBitmap,
+ long ninePatch,
+ float dstLeft,
+ float dstTop,
+ float dstRight,
+ float dstBottom,
+ long nativePaintOrZero,
+ int screenDensity,
+ int bitmapDensity);
+
+ public static native void nDrawBitmapMatrix(
+ long nativeCanvas, long bitmapHandle, long nativeMatrix, long nativePaint);
+
+ public static native void nDrawBitmapMesh(
+ long nativeCanvas,
+ long bitmapHandle,
+ int meshWidth,
+ int meshHeight,
+ float[] verts,
+ int vertOffset,
+ int[] colors,
+ int colorOffset,
+ long nativePaint);
+
+ public static native void nDrawVertices(
+ long nativeCanvas,
+ int mode,
+ int n,
+ float[] verts,
+ int vertOffset,
+ float[] texs,
+ int texOffset,
+ int[] colors,
+ int colorOffset,
+ short[] indices,
+ int indexOffset,
+ int indexCount,
+ long nativePaint);
+
+ public static native void nDrawGlyphs(
+ long nativeCanvas,
+ int[] glyphIds,
+ float[] positions,
+ int glyphIdStart,
+ int positionStart,
+ int glyphCount,
+ long nativeFont,
+ long nativePaint);
+
+ public static native void nDrawText(
+ long nativeCanvas,
+ char[] text,
+ int index,
+ int count,
+ float x,
+ float y,
+ int flags,
+ long nativePaint);
+
+ public static native void nDrawText(
+ long nativeCanvas,
+ String text,
+ int start,
+ int end,
+ float x,
+ float y,
+ int flags,
+ long nativePaint);
+
+ // Variant for O..O_MR1 that includes a Typeface pointer.
+ public static native void nDrawText(
+ long nativeCanvas,
+ char[] text,
+ int index,
+ int count,
+ float x,
+ float y,
+ int flags,
+ long nativePaint,
+ long nativeTypeface);
+
+ // Variant for O..O_MR1 that includes a Typeface pointer.
+ public static native void nDrawText(
+ long nativeCanvas,
+ String text,
+ int start,
+ int end,
+ float x,
+ float y,
+ int flags,
+ long nativePaint,
+ long nativeTypeface);
+
+ public static native void nDrawTextRun(
+ long nativeCanvas,
+ String text,
+ int start,
+ int end,
+ int contextStart,
+ int contextEnd,
+ float x,
+ float y,
+ boolean isRtl,
+ long nativePaint);
+
+ public static native void nDrawTextRun(
+ long nativeCanvas,
+ char[] text,
+ int start,
+ int count,
+ int contextStart,
+ int contextCount,
+ float x,
+ float y,
+ boolean isRtl,
+ long nativePaint,
+ long nativPrecomputedText);
+
+ // Variant for O..O_MR1 that includes a Typeface pointer.
+ public static native void nDrawTextRun(
+ long nativeCanvas,
+ String text,
+ int start,
+ int end,
+ int contextStart,
+ int contextEnd,
+ float x,
+ float y,
+ boolean isRtl,
+ long nativePaint,
+ long nativeTypeface);
+
+ // Variant for O..O_MR1 that includes a Typeface pointer.
+ public static native void nDrawTextRunTypeface(
+ long nativeCanvas,
+ char[] text,
+ int start,
+ int count,
+ int contextStart,
+ int contextCount,
+ float x,
+ float y,
+ boolean isRtl,
+ long nativePaint,
+ long nativeTypeface);
+
+ public static native void nDrawTextOnPath(
+ long nativeCanvas,
+ char[] text,
+ int index,
+ int count,
+ long nativePath,
+ float hOffset,
+ float vOffset,
+ int bidiFlags,
+ long nativePaint);
+
+ public static native void nDrawTextOnPath(
+ long nativeCanvas,
+ String text,
+ long nativePath,
+ float hOffset,
+ float vOffset,
+ int flags,
+ long nativePaint);
+
+ // Variant for O..O_MR1 that includes a Typeface pointer.
+ public static native void nDrawTextOnPath(
+ long nativeCanvas,
+ char[] text,
+ int index,
+ int count,
+ long nativePath,
+ float hOffset,
+ float vOffset,
+ int bidiFlags,
+ long nativePaint,
+ long nativeTypeface);
+
+ // Variant for O..O_MR1 that includes a Typeface pointer.
+ public static native void nDrawTextOnPath(
+ long nativeCanvas,
+ String text,
+ long nativePath,
+ float hOffset,
+ float vOffset,
+ int flags,
+ long nativePaint,
+ long nativeTypeface);
+
+ public static native void nPunchHole(
+ long renderer, float left, float top, float right, float bottom, float rx, float ry);
+
+ private BaseCanvasNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/BaseRecordingCanvasNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/BaseRecordingCanvasNatives.java
new file mode 100644
index 000000000..3bd3fa937
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/BaseRecordingCanvasNatives.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright (C) 2022 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.nativeruntime;
+
+import android.annotation.ColorLong;
+
+/**
+ * Native methods for BaseRecordingCanvas JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/BaseRecordingCanvas.java
+ */
+public final class BaseRecordingCanvasNatives {
+ public static native void nDrawBitmap(
+ long nativeCanvas,
+ long bitmapHandle,
+ float left,
+ float top,
+ long nativePaintOrZero,
+ int canvasDensity,
+ int screenDensity,
+ int bitmapDensity);
+
+ public static native void nDrawBitmap(
+ long nativeCanvas,
+ long bitmapHandle,
+ float srcLeft,
+ float srcTop,
+ float srcRight,
+ float srcBottom,
+ float dstLeft,
+ float dstTop,
+ float dstRight,
+ float dstBottom,
+ long nativePaintOrZero,
+ int screenDensity,
+ int bitmapDensity);
+
+ public static native void nDrawBitmap(
+ long nativeCanvas,
+ int[] colors,
+ int offset,
+ int stride,
+ float x,
+ float y,
+ int width,
+ int height,
+ boolean hasAlpha,
+ long nativePaintOrZero);
+
+ public static native void nDrawColor(long nativeCanvas, int color, int mode);
+
+ public static native void nDrawColor(
+ long nativeCanvas, long nativeColorSpace, @ColorLong long color, int mode);
+
+ public static native void nDrawPaint(long nativeCanvas, long nativePaint);
+
+ public static native void nDrawPoint(long canvasHandle, float x, float y, long paintHandle);
+
+ public static native void nDrawPoints(
+ long canvasHandle, float[] pts, int offset, int count, long paintHandle);
+
+ public static native void nDrawLine(
+ long nativeCanvas, float startX, float startY, float stopX, float stopY, long nativePaint);
+
+ public static native void nDrawLines(
+ long canvasHandle, float[] pts, int offset, int count, long paintHandle);
+
+ public static native void nDrawRect(
+ long nativeCanvas, float left, float top, float right, float bottom, long nativePaint);
+
+ public static native void nDrawOval(
+ long nativeCanvas, float left, float top, float right, float bottom, long nativePaint);
+
+ public static native void nDrawCircle(
+ long nativeCanvas, float cx, float cy, float radius, long nativePaint);
+
+ public static native void nDrawArc(
+ long nativeCanvas,
+ float left,
+ float top,
+ float right,
+ float bottom,
+ float startAngle,
+ float sweep,
+ boolean useCenter,
+ long nativePaint);
+
+ public static native void nDrawRoundRect(
+ long nativeCanvas,
+ float left,
+ float top,
+ float right,
+ float bottom,
+ float rx,
+ float ry,
+ long nativePaint);
+
+ public static native void nDrawDoubleRoundRect(
+ long nativeCanvas,
+ float outerLeft,
+ float outerTop,
+ float outerRight,
+ float outerBottom,
+ float outerRx,
+ float outerRy,
+ float innerLeft,
+ float innerTop,
+ float innerRight,
+ float innerBottom,
+ float innerRx,
+ float innerRy,
+ long nativePaint);
+
+ public static native void nDrawDoubleRoundRect(
+ long nativeCanvas,
+ float outerLeft,
+ float outerTop,
+ float outerRight,
+ float outerBottom,
+ float[] outerRadii,
+ float innerLeft,
+ float innerTop,
+ float innerRight,
+ float innerBottom,
+ float[] innerRadii,
+ long nativePaint);
+
+ public static native void nDrawPath(long nativeCanvas, long nativePath, long nativePaint);
+
+ public static native void nDrawRegion(long nativeCanvas, long nativeRegion, long nativePaint);
+
+ public static native void nDrawNinePatch(
+ long nativeCanvas,
+ long nativeBitmap,
+ long ninePatch,
+ float dstLeft,
+ float dstTop,
+ float dstRight,
+ float dstBottom,
+ long nativePaintOrZero,
+ int screenDensity,
+ int bitmapDensity);
+
+ public static native void nDrawBitmapMatrix(
+ long nativeCanvas, long bitmapHandle, long nativeMatrix, long nativePaint);
+
+ public static native void nDrawBitmapMesh(
+ long nativeCanvas,
+ long bitmapHandle,
+ int meshWidth,
+ int meshHeight,
+ float[] verts,
+ int vertOffset,
+ int[] colors,
+ int colorOffset,
+ long nativePaint);
+
+ public static native void nDrawVertices(
+ long nativeCanvas,
+ int mode,
+ int n,
+ float[] verts,
+ int vertOffset,
+ float[] texs,
+ int texOffset,
+ int[] colors,
+ int colorOffset,
+ short[] indices,
+ int indexOffset,
+ int indexCount,
+ long nativePaint);
+
+ public static native void nDrawGlyphs(
+ long nativeCanvas,
+ int[] glyphIds,
+ float[] positions,
+ int glyphIdStart,
+ int positionStart,
+ int glyphCount,
+ long nativeFont,
+ long nativePaint);
+
+ public static native void nDrawText(
+ long nativeCanvas,
+ char[] text,
+ int index,
+ int count,
+ float x,
+ float y,
+ int flags,
+ long nativePaint);
+
+ public static native void nDrawText(
+ long nativeCanvas,
+ String text,
+ int start,
+ int end,
+ float x,
+ float y,
+ int flags,
+ long nativePaint);
+
+ // Variant for O..O_MR1 that includes a Typeface pointer.
+ public static native void nDrawText(
+ long nativeCanvas,
+ char[] text,
+ int index,
+ int count,
+ float x,
+ float y,
+ int flags,
+ long nativePaint,
+ long nativeTypeface);
+
+ // Variant for O..O_MR1 that includes a Typeface pointer.
+ public static native void nDrawText(
+ long nativeCanvas,
+ String text,
+ int start,
+ int end,
+ float x,
+ float y,
+ int flags,
+ long nativePaint,
+ long nativeTypeface);
+
+ public static native void nDrawTextRun(
+ long nativeCanvas,
+ String text,
+ int start,
+ int end,
+ int contextStart,
+ int contextEnd,
+ float x,
+ float y,
+ boolean isRtl,
+ long nativePaint);
+
+ public static native void nDrawTextRun(
+ long nativeCanvas,
+ char[] text,
+ int start,
+ int count,
+ int contextStart,
+ int contextCount,
+ float x,
+ float y,
+ boolean isRtl,
+ long nativePaint,
+ long nativePrecomputedText);
+
+ // Variant for O..O_MR1 that includes a Typeface pointer.
+ public static native void nDrawTextRun(
+ long nativeCanvas,
+ String text,
+ int start,
+ int end,
+ int contextStart,
+ int contextEnd,
+ float x,
+ float y,
+ boolean isRtl,
+ long nativePaint,
+ long nativeTypeface);
+
+ // Variant for O..O_MR1 that includes a Typeface pointer.
+ public static native void nDrawTextRunTypeface(
+ long nativeCanvas,
+ char[] text,
+ int start,
+ int count,
+ int contextStart,
+ int contextCount,
+ float x,
+ float y,
+ boolean isRtl,
+ long nativePaint,
+ long nativeTypeface);
+
+ public static native void nDrawTextOnPath(
+ long nativeCanvas,
+ char[] text,
+ int index,
+ int count,
+ long nativePath,
+ float hOffset,
+ float vOffset,
+ int bidiFlags,
+ long nativePaint);
+
+ public static native void nDrawTextOnPath(
+ long nativeCanvas,
+ String text,
+ long nativePath,
+ float hOffset,
+ float vOffset,
+ int flags,
+ long nativePaint);
+
+ // Variant for O..O_MR1 that includes a Typeface pointer.
+ public static native void nDrawTextOnPath(
+ long nativeCanvas,
+ char[] text,
+ int index,
+ int count,
+ long nativePath,
+ float hOffset,
+ float vOffset,
+ int bidiFlags,
+ long nativePaint,
+ long nativeTypeface);
+
+ // Variant for O..O_MR1 that includes a Typeface pointer.
+ public static native void nDrawTextOnPath(
+ long nativeCanvas,
+ String text,
+ long nativePath,
+ float hOffset,
+ float vOffset,
+ int flags,
+ long nativePaint,
+ long nativeTypeface);
+
+ public static native void nPunchHole(
+ long renderer, float left, float top, float right, float bottom, float rx, float ry);
+
+ private BaseRecordingCanvasNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/BitmapFactoryNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/BitmapFactoryNatives.java
new file mode 100644
index 000000000..8024f26df
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/BitmapFactoryNatives.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 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.nativeruntime;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory.Options;
+import android.graphics.Rect;
+import java.io.FileDescriptor;
+import java.io.InputStream;
+
+/**
+ * Native methods for BitmapFactory JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/BitmapFactory.java
+ */
+public final class BitmapFactoryNatives {
+ public static native Bitmap nativeDecodeStream(
+ InputStream is,
+ byte[] storage,
+ Rect padding,
+ Options opts,
+ long inBitmapHandle,
+ long colorSpaceHandle);
+
+ public static native Bitmap nativeDecodeFileDescriptor(
+ FileDescriptor fd, Rect padding, Options opts, long inBitmapHandle, long colorSpaceHandle);
+
+ public static native Bitmap nativeDecodeAsset(
+ long nativeAsset, Rect padding, Options opts, long inBitmapHandle, long colorSpaceHandle);
+
+ public static native Bitmap nativeDecodeByteArray(
+ byte[] data,
+ int offset,
+ int length,
+ Options opts,
+ long inBitmapHandle,
+ long colorSpaceHandle);
+
+ public static native boolean nativeIsSeekable(FileDescriptor fd);
+
+ private BitmapFactoryNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/BitmapNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/BitmapNatives.java
new file mode 100644
index 000000000..b273771c4
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/BitmapNatives.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2022 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.nativeruntime;
+
+import android.graphics.Bitmap;
+import android.graphics.ColorSpace;
+import android.hardware.HardwareBuffer;
+import android.os.Parcel;
+import java.io.OutputStream;
+import java.nio.Buffer;
+
+/**
+ * Native methods for Bitmap JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/Bitmap.java
+ */
+public final class BitmapNatives {
+
+ public static native Bitmap nativeCreate(
+ int[] colors,
+ int offset,
+ int stride,
+ int width,
+ int height,
+ int nativeConfig,
+ boolean mutable,
+ long nativeColorSpace);
+
+ public static native Bitmap nativeCopy(long nativeSrcBitmap, int nativeConfig, boolean isMutable);
+
+ public static native Bitmap nativeCopyAshmem(long nativeSrcBitmap);
+
+ public static native Bitmap nativeCopyAshmemConfig(long nativeSrcBitmap, int nativeConfig);
+
+ public static native long nativeGetNativeFinalizer();
+
+ public static native void nativeRecycle(long nativeBitmap);
+
+ public static native void nativeReconfigure(
+ long nativeBitmap, int width, int height, int config, boolean isPremultiplied);
+
+ public static native boolean nativeCompress(
+ long nativeBitmap, int format, int quality, OutputStream stream, byte[] tempStorage);
+
+ public static native void nativeErase(long nativeBitmap, int color);
+
+ public static native void nativeErase(long nativeBitmap, long colorSpacePtr, long color);
+
+ public static native int nativeRowBytes(long nativeBitmap);
+
+ public static native int nativeConfig(long nativeBitmap);
+
+ public static native int nativeGetPixel(long nativeBitmap, int x, int y);
+
+ public static native long nativeGetColor(long nativeBitmap, int x, int y);
+
+ public static native void nativeGetPixels(
+ long nativeBitmap, int[] pixels, int offset, int stride, int x, int y, int width, int height);
+
+ public static native void nativeSetPixel(long nativeBitmap, int x, int y, int color);
+
+ public static native void nativeSetPixels(
+ long nativeBitmap, int[] colors, int offset, int stride, int x, int y, int width, int height);
+
+ public static native void nativeCopyPixelsToBuffer(long nativeBitmap, Buffer dst);
+
+ public static native void nativeCopyPixelsFromBuffer(long nativeBitmap, Buffer src);
+
+ public static native int nativeGenerationId(long nativeBitmap);
+
+ public static native Bitmap nativeCreateFromParcel(Parcel p);
+ // returns true on success
+ public static native boolean nativeWriteToParcel(long nativeBitmap, int density, Parcel p);
+ // returns a new bitmap built from the native bitmap's alpha, and the paint
+ public static native Bitmap nativeExtractAlpha(
+ long nativeBitmap, long nativePaint, int[] offsetXY);
+
+ public static native boolean nativeHasAlpha(long nativeBitmap);
+
+ public static native boolean nativeIsPremultiplied(long nativeBitmap);
+
+ public static native void nativeSetPremultiplied(long nativeBitmap, boolean isPremul);
+
+ public static native void nativeSetHasAlpha(
+ long nativeBitmap, boolean hasAlpha, boolean requestPremul);
+
+ public static native boolean nativeHasMipMap(long nativeBitmap);
+
+ public static native void nativeSetHasMipMap(long nativeBitmap, boolean hasMipMap);
+
+ public static native boolean nativeSameAs(long nativeBitmap0, long nativeBitmap1);
+
+ public static native void nativePrepareToDraw(long nativeBitmap);
+
+ public static native int nativeGetAllocationByteCount(long nativeBitmap);
+
+ public static native Bitmap nativeCopyPreserveInternalConfig(long nativeBitmap);
+
+ public static native Bitmap nativeWrapHardwareBufferBitmap(
+ HardwareBuffer buffer, long nativeColorSpace);
+
+ public static native HardwareBuffer nativeGetHardwareBuffer(long nativeBitmap);
+
+ public static native ColorSpace nativeComputeColorSpace(long nativePtr);
+
+ public static native void nativeSetColorSpace(long nativePtr, long nativeColorSpace);
+
+ public static native boolean nativeIsSRGB(long nativePtr);
+
+ public static native boolean nativeIsSRGBLinear(long nativePtr);
+
+ public static native void nativeSetImmutable(long nativePtr);
+
+ public static native boolean nativeIsImmutable(long nativePtr);
+
+ public static native boolean nativeIsBackedByAshmem(long nativePtr);
+
+ private BitmapNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/BitmapShaderNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/BitmapShaderNatives.java
new file mode 100644
index 000000000..f0d1df102
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/BitmapShaderNatives.java
@@ -0,0 +1,19 @@
+package org.robolectric.nativeruntime;
+
+/**
+ * Native methods for BitmapShader JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/BitmapShader.java
+ */
+public final class BitmapShaderNatives {
+
+ public static native long nativeCreate(
+ long nativeMatrix,
+ long bitmapHandle,
+ int shaderTileModeX,
+ int shaderTileModeY,
+ boolean filter);
+
+ private BitmapShaderNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/BlendModeColorFilterNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/BlendModeColorFilterNatives.java
new file mode 100644
index 000000000..aa93a501b
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/BlendModeColorFilterNatives.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2006 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.nativeruntime;
+
+/**
+ * Native methods for BlendModeColorFilter JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/BlendModeColorFilter.java
+ */
+public final class BlendModeColorFilterNatives {
+
+ public static native long native_CreateBlendModeFilter(int srcColor, int blendmode);
+
+ private BlendModeColorFilterNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/BlurMaskFilterNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/BlurMaskFilterNatives.java
new file mode 100644
index 000000000..72cdebc5b
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/BlurMaskFilterNatives.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2006 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.nativeruntime;
+
+/**
+ * Native methods for BlurMaskFilter JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/BlurMaskFilter.java
+ */
+public final class BlurMaskFilterNatives {
+
+ public static native long nativeConstructor(float radius, int style);
+
+ private BlurMaskFilterNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/CanvasNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/CanvasNatives.java
new file mode 100644
index 000000000..398ce9645
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/CanvasNatives.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2022 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.nativeruntime;
+
+import android.graphics.Rect;
+
+/**
+ * Native methods for Canvas JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/Canvas.java
+ */
+public final class CanvasNatives {
+ public static native void nFreeCaches();
+
+ public static native void nFreeTextLayoutCaches();
+
+ public static native long nGetNativeFinalizer();
+
+ public static native void nSetCompatibilityVersion(int apiLevel);
+
+ public static native long nInitRaster(long bitmapHandle);
+
+ public static native void nSetBitmap(long canvasHandle, long bitmapHandle);
+
+ public static native boolean nGetClipBounds(long nativeCanvas, Rect bounds);
+
+ public static native boolean nIsOpaque(long canvasHandle);
+
+ public static native int nGetWidth(long canvasHandle);
+
+ public static native int nGetHeight(long canvasHandle);
+
+ public static native int nSave(long canvasHandle, int saveFlags);
+
+ public static native int nSaveLayer(
+ long nativeCanvas, float l, float t, float r, float b, long nativePaint);
+
+ public static native int nSaveLayerAlpha(
+ long nativeCanvas, float l, float t, float r, float b, int alpha);
+
+ public static native int nSaveUnclippedLayer(long nativeCanvas, int l, int t, int r, int b);
+
+ public static native void nRestoreUnclippedLayer(
+ long nativeCanvas, int saveCount, long nativePaint);
+
+ public static native boolean nRestore(long canvasHandle);
+
+ public static native void nRestoreToCount(long canvasHandle, int saveCount);
+
+ public static native int nGetSaveCount(long canvasHandle);
+
+ public static native void nTranslate(long canvasHandle, float dx, float dy);
+
+ public static native void nScale(long canvasHandle, float sx, float sy);
+
+ public static native void nRotate(long canvasHandle, float degrees);
+
+ public static native void nSkew(long canvasHandle, float sx, float sy);
+
+ public static native void nConcat(long nativeCanvas, long nativeMatrix);
+
+ public static native void nSetMatrix(long nativeCanvas, long nativeMatrix);
+
+ public static native boolean nClipRect(
+ long nativeCanvas, float left, float top, float right, float bottom, int regionOp);
+
+ public static native boolean nClipPath(long nativeCanvas, long nativePath, int regionOp);
+
+ public static native void nSetDrawFilter(long nativeCanvas, long nativeFilter);
+
+ public static native void nGetMatrix(long nativeCanvas, long nativeMatrix);
+
+ public static native boolean nQuickReject(long nativeCanvas, long nativePath);
+
+ public static native boolean nQuickReject(
+ long nativeCanvas, float left, float top, float right, float bottom);
+
+ private CanvasNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/CanvasPropertyNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/CanvasPropertyNatives.java
new file mode 100644
index 000000000..8e229f6ea
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/CanvasPropertyNatives.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 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.nativeruntime;
+
+/**
+ * Native methods for CanvasProperty JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/CanvasProperty.java
+ */
+public final class CanvasPropertyNatives {
+
+ public static native long nCreateFloat(float initialValue);
+
+ public static native long nCreatePaint(long initialValuePaintPtr);
+
+ private CanvasPropertyNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/ColorFilterNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/ColorFilterNatives.java
new file mode 100644
index 000000000..174fc8feb
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/ColorFilterNatives.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2006 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.nativeruntime;
+
+/**
+ * Native methods for ColorFilter JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/ColorFilter.java
+ */
+public final class ColorFilterNatives {
+
+ public static native long nativeGetFinalizer();
+
+ public static native void nSafeUnref(long nativeFinalizer);
+
+ private ColorFilterNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/ColorMatrixColorFilterNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/ColorMatrixColorFilterNatives.java
new file mode 100644
index 000000000..7b0da18e8
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/ColorMatrixColorFilterNatives.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2006 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.nativeruntime;
+
+/**
+ * Native methods for ColorMatrixColorFilter JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/ColorMatrixColorFilter.java
+ */
+public final class ColorMatrixColorFilterNatives {
+
+ public static native long nativeColorMatrixFilter(float[] array);
+
+ private ColorMatrixColorFilterNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/ColorNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/ColorNatives.java
new file mode 100644
index 000000000..1b0f0ddcf
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/ColorNatives.java
@@ -0,0 +1,16 @@
+package org.robolectric.nativeruntime;
+
+/**
+ * Native methods for Color JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/Color.java
+ */
+public final class ColorNatives {
+
+ public static native void nativeRGBToHSV(int red, int greed, int blue, float[] hsv);
+
+ public static native int nativeHSVToColor(int alpha, float[] hsv);
+
+ private ColorNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/ColorSpaceRgbNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/ColorSpaceRgbNatives.java
new file mode 100644
index 000000000..24f70a34c
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/ColorSpaceRgbNatives.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 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.nativeruntime;
+
+/**
+ * Native methods for BitmapFactory JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/ColorSpace.java
+ */
+public final class ColorSpaceRgbNatives {
+
+ public static native long nativeGetNativeFinalizer();
+
+ public static native long nativeCreate(
+ float a, float b, float c, float d, float e, float f, float g, float[] xyz);
+
+ private ColorSpaceRgbNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/ComposePathEffectNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/ComposePathEffectNatives.java
new file mode 100644
index 000000000..ed6133b98
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/ComposePathEffectNatives.java
@@ -0,0 +1,14 @@
+package org.robolectric.nativeruntime;
+
+/**
+ * Native methods for ComposePathEffect JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/ComposePathEffect.java
+ */
+public final class ComposePathEffectNatives {
+
+ public static native long nativeCreate(long nativeOuterpe, long nativeInnerpe);
+
+ private ComposePathEffectNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/ComposeShaderNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/ComposeShaderNatives.java
new file mode 100644
index 000000000..9e0982dfa
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/ComposeShaderNatives.java
@@ -0,0 +1,14 @@
+package org.robolectric.nativeruntime;
+
+/**
+ * Native methods for ComposeShader JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/ComposeShader.java
+ */
+public class ComposeShaderNatives {
+ public static native long nativeCreate(
+ long nativeMatrix, long nativeShaderA, long nativeShaderB, int porterDuffMode);
+
+ private ComposeShaderNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/CornerPathEffectNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/CornerPathEffectNatives.java
new file mode 100644
index 000000000..44c572eee
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/CornerPathEffectNatives.java
@@ -0,0 +1,13 @@
+package org.robolectric.nativeruntime;
+
+/**
+ * Native methods for CornerPathEffect JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/CornerPathEffect.java
+ */
+public final class CornerPathEffectNatives {
+ public static native long nativeCreate(float radius);
+
+ private CornerPathEffectNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/DashPathEffectNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/DashPathEffectNatives.java
new file mode 100644
index 000000000..430a74fbf
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/DashPathEffectNatives.java
@@ -0,0 +1,13 @@
+package org.robolectric.nativeruntime;
+
+/**
+ * Native methods for DashPathEffect JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/DashPathEffect.java
+ */
+public final class DashPathEffectNatives {
+ public static native long nativeCreate(float[] intervals, float phase);
+
+ private DashPathEffectNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/DiscretePathEffectNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/DiscretePathEffectNatives.java
new file mode 100644
index 000000000..f1bfa2f5e
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/DiscretePathEffectNatives.java
@@ -0,0 +1,13 @@
+package org.robolectric.nativeruntime;
+
+/**
+ * Native methods for DiscretePathEffect JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/DiscretePathEffect.java
+ */
+public final class DiscretePathEffectNatives {
+ public static native long nativeCreate(float length, float deviation);
+
+ private DiscretePathEffectNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/EmbossMaskFilterNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/EmbossMaskFilterNatives.java
new file mode 100644
index 000000000..27ef3da8f
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/EmbossMaskFilterNatives.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2006 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.nativeruntime;
+
+/**
+ * Native methods for EmbossMaskFilter JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/EmbossMaskFilter.java
+ */
+public final class EmbossMaskFilterNatives {
+
+ public static native long nativeConstructor(
+ float[] direction, float ambient, float specular, float blurRadius);
+
+ private EmbossMaskFilterNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/FontBuilderNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/FontBuilderNatives.java
new file mode 100644
index 000000000..0a02b45e0
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/FontBuilderNatives.java
@@ -0,0 +1,47 @@
+package org.robolectric.nativeruntime;
+
+/*
+ * Copyright (C) 2022 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.
+ */
+
+import java.nio.ByteBuffer;
+
+/**
+ * Native methods for android.graphics.fonts.Font$Builder JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/fonts/Font.java
+ */
+public final class FontBuilderNatives {
+ public static native long nInitBuilder();
+
+ public static native void nAddAxis(long builderPtr, int tag, float value);
+
+ public static native long nBuild(
+ long builderPtr,
+ ByteBuffer buffer,
+ String filePath,
+ String localeList,
+ int weight,
+ boolean italic,
+ int ttcIndex);
+
+ public static native long nGetReleaseNativeFont();
+
+ public static native long nClone(
+ long fontPtr, long builderPtr, int weight, boolean italic, int ttcIndex);
+
+ private FontBuilderNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/FontFamilyBuilderNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/FontFamilyBuilderNatives.java
new file mode 100644
index 000000000..4bc5e7f7f
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/FontFamilyBuilderNatives.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 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.nativeruntime;
+
+/**
+ * Native methods for android.graphics.fonts.FontFamily$Builder JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/fonts/FontFamily.java
+ */
+public final class FontFamilyBuilderNatives {
+
+ public static native long nInitBuilder();
+
+ public static native void nAddFont(long builderPtr, long fontPtr);
+
+ public static native long nBuild(
+ long builderPtr, String langTags, int variant, boolean isCustomFallback);
+
+ public static native long nGetReleaseNativeFamily();
+
+ private FontFamilyBuilderNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/FontFamilyNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/FontFamilyNatives.java
new file mode 100644
index 000000000..933baff04
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/FontFamilyNatives.java
@@ -0,0 +1,51 @@
+package org.robolectric.nativeruntime;
+
+/*
+ * Copyright (C) 2022 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.
+ */
+
+import java.nio.ByteBuffer;
+
+/**
+ * Native methods for the deprecated android.graphics.FontFamily JNI registration. Note this is
+ * different from {@link FontsFontFamilyNatives}.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/FontFamily.java
+ */
+public final class FontFamilyNatives {
+
+ public static native long nInitBuilder(String langs, int variant);
+
+ public static native void nAllowUnsupportedFont(long builderPtr);
+
+ public static native long nCreateFamily(long mBuilderPtr);
+
+ public static native long nGetBuilderReleaseFunc();
+
+ public static native long 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.
+ public static native boolean nAddFont(
+ long builderPtr, ByteBuffer font, int ttcIndex, int weight, int isItalic);
+
+ public static native boolean nAddFontWeightStyle(
+ long builderPtr, ByteBuffer font, int ttcIndex, int weight, int isItalic);
+
+ // The added axis values are only valid for the next nAddFont* method call.
+ public static native void nAddAxisValue(long builderPtr, int tag, float value);
+
+ private FontFamilyNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/FontFileUtilNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/FontFileUtilNatives.java
new file mode 100644
index 000000000..613ecb9b0
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/FontFileUtilNatives.java
@@ -0,0 +1,35 @@
+package org.robolectric.nativeruntime;
+
+/*
+ * Copyright (C) 2022 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.
+ */
+
+import java.nio.ByteBuffer;
+
+/**
+ * Native methods for android.graphics.fonts.FontFileUtil JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/fonts/FontFileUtil.java
+ */
+public final class FontFileUtilNatives {
+ public static native long nGetFontRevision(ByteBuffer buffer, int index);
+
+ public static native String nGetFontPostScriptName(ByteBuffer buffer, int index);
+
+ public static native int nIsPostScriptType1Font(ByteBuffer buffer, int index);
+
+ private FontFileUtilNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/FontNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/FontNatives.java
new file mode 100644
index 000000000..bb1994f81
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/FontNatives.java
@@ -0,0 +1,61 @@
+package org.robolectric.nativeruntime;
+
+/*
+ * Copyright (C) 2022 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.
+ */
+
+import android.graphics.Paint;
+import android.graphics.RectF;
+import java.nio.ByteBuffer;
+
+/**
+ * Native methods for android.graphics.fonts.Font JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/fonts/Font.java
+ */
+public final class FontNatives {
+ public static native long nGetMinikinFontPtr(long font);
+
+ public static native long nCloneFont(long font);
+
+ public static native ByteBuffer nNewByteBuffer(long font);
+
+ public static native long nGetBufferAddress(long font);
+
+ public static native int nGetSourceId(long font);
+
+ public static native long nGetReleaseNativeFont();
+
+ public static native float nGetGlyphBounds(long font, int glyphId, long paint, RectF rect);
+
+ public static native float nGetFontMetrics(long font, long paint, Paint.FontMetrics metrics);
+
+ public static native String nGetFontPath(long fontPtr);
+
+ public static native String nGetLocaleList(long familyPtr);
+
+ public static native int nGetPackedStyle(long fontPtr);
+
+ public static native int nGetIndex(long fontPtr);
+
+ public static native int nGetAxisCount(long fontPtr);
+
+ public static native long nGetAxisInfo(long fontPtr, int i);
+
+ public static native long[] nGetAvailableFontSet();
+
+ private FontNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/FontsFontFamilyNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/FontsFontFamilyNatives.java
new file mode 100644
index 000000000..20379d5ab
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/FontsFontFamilyNatives.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 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.nativeruntime;
+
+/**
+ * Native methods for android.graphics.fonts.FontFamily JNI registration. This is different from
+ * {@link FontFamilyNatives}.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/fonts/FontFamily.java
+ */
+public final class FontsFontFamilyNatives {
+
+ public static native int nGetFontSize(long family);
+
+ public static native long nGetFont(long family, int i);
+
+ public static native String nGetLangTags(long family);
+
+ public static native int nGetVariant(long family);
+
+ private FontsFontFamilyNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/HardwareRendererNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/HardwareRendererNatives.java
new file mode 100644
index 000000000..8553ecc48
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/HardwareRendererNatives.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2022 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.nativeruntime;
+
+import android.graphics.Bitmap;
+import android.graphics.HardwareRenderer.ASurfaceTransactionCallback;
+import android.graphics.HardwareRenderer.FrameCompleteCallback;
+import android.graphics.HardwareRenderer.FrameDrawingCallback;
+import android.graphics.HardwareRenderer.PictureCapturedCallback;
+import android.graphics.HardwareRenderer.PrepareSurfaceControlForWebviewCallback;
+import android.view.Surface;
+import java.io.FileDescriptor;
+
+/**
+ * Native methods for {@link HardwareRenderer} JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/HardwareRenderer.java
+ */
+public final class HardwareRendererNatives {
+ public static native void disableVsync();
+
+ public static native void preload();
+
+ public static native boolean isWebViewOverlaysEnabled();
+
+ public static native void setupShadersDiskCache(String cacheFile, String skiaCacheFile);
+
+ public static native void nRotateProcessStatsBuffer();
+
+ public static native void nSetProcessStatsBuffer(int fd);
+
+ public static native int nGetRenderThreadTid(long nativeProxy);
+
+ public static native long nCreateRootRenderNode();
+
+ public static native long nCreateProxy(boolean translucent, long rootRenderNode);
+
+ public static native void nDeleteProxy(long nativeProxy);
+
+ public static native boolean nLoadSystemProperties(long nativeProxy);
+
+ public static native void nSetName(long nativeProxy, String name);
+
+ public static native void nSetSurface(long nativeProxy, Surface window, boolean discardBuffer);
+
+ public static native void nSetSurfaceControl(long nativeProxy, long nativeSurfaceControl);
+
+ public static native boolean nPause(long nativeProxy);
+
+ public static native void nSetStopped(long nativeProxy, boolean stopped);
+
+ public static native void nSetLightGeometry(
+ long nativeProxy, float lightX, float lightY, float lightZ, float lightRadius);
+
+ public static native void nSetLightAlpha(
+ long nativeProxy, float ambientShadowAlpha, float spotShadowAlpha);
+
+ public static native void nSetOpaque(long nativeProxy, boolean opaque);
+
+ public static native void nSetColorMode(long nativeProxy, int colorMode);
+
+ public static native void nSetSdrWhitePoint(long nativeProxy, float whitePoint);
+
+ public static native void nSetIsHighEndGfx(boolean isHighEndGfx);
+
+ public static native int nSyncAndDrawFrame(long nativeProxy, long[] frameInfo, int size);
+
+ public static native void nDestroy(long nativeProxy, long rootRenderNode);
+
+ public static native void nRegisterAnimatingRenderNode(long rootRenderNode, long animatingNode);
+
+ public static native void nRegisterVectorDrawableAnimator(long rootRenderNode, long animator);
+
+ public static native long nCreateTextureLayer(long nativeProxy);
+
+ public static native void nBuildLayer(long nativeProxy, long node);
+
+ public static native boolean nCopyLayerInto(long nativeProxy, long layer, long bitmapHandle);
+
+ public static native void nPushLayerUpdate(long nativeProxy, long layer);
+
+ public static native void nCancelLayerUpdate(long nativeProxy, long layer);
+
+ public static native void nDetachSurfaceTexture(long nativeProxy, long layer);
+
+ public static native void nDestroyHardwareResources(long nativeProxy);
+
+ public static native void nTrimMemory(int level);
+
+ public static native void nOverrideProperty(String name, String value);
+
+ public static native void nFence(long nativeProxy);
+
+ public static native void nStopDrawing(long nativeProxy);
+
+ public static native void nNotifyFramePending(long nativeProxy);
+
+ public static native void nDumpProfileInfo(long nativeProxy, FileDescriptor fd, int dumpFlags);
+
+ public static native void nAddRenderNode(
+ long nativeProxy, long rootRenderNode, boolean placeFront);
+
+ public static native void nRemoveRenderNode(long nativeProxy, long rootRenderNode);
+
+ public static native void nDrawRenderNode(long nativeProxy, long rootRenderNode);
+
+ public static native void nSetContentDrawBounds(
+ long nativeProxy, int left, int top, int right, int bottom);
+
+ public static native void nSetPictureCaptureCallback(
+ long nativeProxy, PictureCapturedCallback callback);
+
+ public static native void nSetASurfaceTransactionCallback(
+ long nativeProxy, ASurfaceTransactionCallback callback);
+
+ public static native void nSetPrepareSurfaceControlForWebviewCallback(
+ long nativeProxy, PrepareSurfaceControlForWebviewCallback callback);
+
+ public static native void nSetFrameCallback(long nativeProxy, FrameDrawingCallback callback);
+
+ public static native void nSetFrameCompleteCallback(
+ long nativeProxy, FrameCompleteCallback callback);
+
+ public static native void nAddObserver(long nativeProxy, long nativeObserver);
+
+ public static native void nRemoveObserver(long nativeProxy, long nativeObserver);
+
+ public static native int nCopySurfaceInto(
+ Surface surface, int srcLeft, int srcTop, int srcRight, int srcBottom, long bitmapHandle);
+
+ public static native Bitmap nCreateHardwareBitmap(long renderNode, int width, int height);
+
+ public static native void nSetHighContrastText(boolean enabled);
+
+ public static native void nHackySetRTAnimationsEnabled(boolean enabled);
+
+ public static native void nSetDebuggingEnabled(boolean enabled);
+
+ public static native void nSetIsolatedProcess(boolean enabled);
+
+ public static native void nSetContextPriority(int priority);
+
+ public static native void nAllocateBuffers(long nativeProxy);
+
+ public static native void nSetForceDark(long nativeProxy, boolean enabled);
+
+ public static native void nSetDisplayDensityDpi(int densityDpi);
+
+ public static native void nInitDisplayInfo(
+ int width,
+ int height,
+ float refreshRate,
+ int wideColorDataspace,
+ long appVsyncOffsetNanos,
+ long presentationDeadlineNanos);
+
+ private HardwareRendererNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/HardwareRendererObserverNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/HardwareRendererObserverNatives.java
new file mode 100644
index 000000000..4de5d34d3
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/HardwareRendererObserverNatives.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 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.nativeruntime;
+
+/**
+ * Native methods for {@link ImageDecoder} JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/HardwareRendererObserver.java
+ */
+public class HardwareRendererObserverNatives {
+ public static native int nGetNextBuffer(long nativePtr, long[] data);
+
+ public native long nCreateObserver(boolean waitForPresentTime);
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/ImageDecoderNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/ImageDecoderNatives.java
new file mode 100644
index 000000000..36d492587
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/ImageDecoderNatives.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2022 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.nativeruntime;
+
+import android.graphics.Bitmap;
+import android.graphics.ColorSpace;
+import android.graphics.ImageDecoder;
+import android.graphics.ImageDecoder.Source;
+import android.graphics.Rect;
+import android.util.Size;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+
+/**
+ * Native methods for {@link ImageDecoder} JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/ImageDecoder.java
+ */
+public final class ImageDecoderNatives {
+
+ public static native ImageDecoder nCreate(long asset, boolean preferAnimation, Source src)
+ throws IOException;
+
+ public static native ImageDecoder nCreate(
+ ByteBuffer buffer, int position, int limit, boolean preferAnimation, Source src)
+ throws IOException;
+
+ public static native ImageDecoder nCreate(
+ byte[] data, int offset, int length, boolean preferAnimation, Source src) throws IOException;
+
+ public static native ImageDecoder nCreate(
+ InputStream is, byte[] storage, boolean preferAnimation, Source src) throws IOException;
+ // The fd must be seekable.
+ public static native ImageDecoder nCreate(
+ FileDescriptor fd, long length, boolean preferAnimation, Source src) throws IOException;
+
+ public static native Bitmap nDecodeBitmap(
+ long nativePtr,
+ ImageDecoder decoder,
+ boolean doPostProcess,
+ int width,
+ int height,
+ Rect cropRect,
+ boolean mutable,
+ int allocator,
+ boolean unpremulRequired,
+ boolean conserveMemory,
+ boolean decodeAsAlphaMask,
+ long desiredColorSpace,
+ boolean extended)
+ throws IOException;
+
+ public static native Size nGetSampledSize(long nativePtr, int sampleSize);
+
+ public static native void nGetPadding(long nativePtr, Rect outRect);
+
+ public static native void nClose(long nativePtr);
+
+ public static native String nGetMimeType(long nativePtr);
+
+ public static native ColorSpace nGetColorSpace(long nativePtr);
+
+ private ImageDecoderNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/InterpolatorNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/InterpolatorNatives.java
new file mode 100644
index 000000000..d923579b9
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/InterpolatorNatives.java
@@ -0,0 +1,25 @@
+package org.robolectric.nativeruntime;
+
+/**
+ * Native methods for Interpolator JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/Interpolator.java
+ */
+public final class InterpolatorNatives {
+ public static native long nativeConstructor(int valueCount, int frameCount);
+
+ public static native void nativeDestructor(long nativeInstance);
+
+ public static native void nativeReset(long nativeInstance, int valueCount, int frameCount);
+
+ public static native void nativeSetKeyFrame(
+ long nativeInstance, int index, int msec, float[] values, float[] blend);
+
+ public static native void nativeSetRepeatMirror(
+ long nativeInstance, float repeatCount, boolean mirror);
+
+ public static native int nativeTimeToValues(long nativeInstance, int msec, float[] values);
+
+ private InterpolatorNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/LightingColorFilterNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/LightingColorFilterNatives.java
new file mode 100644
index 000000000..264a0c39a
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/LightingColorFilterNatives.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2006 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.nativeruntime;
+
+/**
+ * Native methods for LightingColorFilter JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/LightingColorFilter.java
+ */
+public final class LightingColorFilterNatives {
+
+ public static native long native_CreateLightingFilter(int mul, int add);
+
+ private LightingColorFilterNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/LineBreakerNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/LineBreakerNatives.java
new file mode 100644
index 000000000..dbf256e2d
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/LineBreakerNatives.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2022 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.nativeruntime;
+
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+
+/**
+ * Native methods for LineBreaker JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/text/LineBreaker.java
+ */
+public final class LineBreakerNatives {
+ public static native long nInit(
+ int breakStrategy, int hyphenationFrequency, boolean isJustified, int[] indents);
+
+ public static native long nGetReleaseFunc();
+
+ public static native long nComputeLineBreaks(
+ long nativePtr,
+ char[] text,
+ long measuredTextPtr,
+ @IntRange(from = 0) int length,
+ @FloatRange(from = 0.0f) float firstWidth,
+ @IntRange(from = 0) int firstWidthLineCount,
+ @FloatRange(from = 0.0f) float restWidth,
+ float[] variableTabStops,
+ float defaultTabStop,
+ @IntRange(from = 0) int indentsOffset);
+
+ public static native int nComputeLineBreaksP(
+ /* non zero */ long nativePtr,
+
+ // Inputs
+ char[] text,
+ /* Non Zero */ long measuredTextPtr,
+ @IntRange(from = 0) int length,
+ @FloatRange(from = 0.0f) float firstWidth,
+ @IntRange(from = 0) int firstWidthLineCount,
+ @FloatRange(from = 0.0f) float restWidth,
+ float[] variableTabStops,
+ float defaultTabStop,
+ @IntRange(from = 0) int indentsOffset,
+
+ // Outputs
+ /* LineBreaks */ Object recycle,
+ @IntRange(from = 0) int recycleLength,
+ int[] recycleBreaks,
+ float[] recycleWidths,
+ float[] recycleAscents,
+ float[] recycleDescents,
+ int[] recycleFlags,
+ float[] charWidths);
+
+ public static native int nGetLineCount(long ptr);
+
+ public static native int nGetLineBreakOffset(long ptr, int idx);
+
+ public static native float nGetLineWidth(long ptr, int idx);
+
+ public static native float nGetLineAscent(long ptr, int idx);
+
+ public static native float nGetLineDescent(long ptr, int idx);
+
+ public static native int nGetLineFlag(long ptr, int idx);
+
+ public static native long nGetReleaseResultFunc();
+
+ public static native void nFinishP(long nativePtr);
+
+ private LineBreakerNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/LinearGradientNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/LinearGradientNatives.java
new file mode 100644
index 000000000..84b1bc8d3
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/LinearGradientNatives.java
@@ -0,0 +1,35 @@
+package org.robolectric.nativeruntime;
+
+/**
+ * Native methods for LinearGradient JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/LinearGradient.java
+ */
+public final class LinearGradientNatives {
+ public static native long nativeCreate(
+ long matrix,
+ float x0,
+ float y0,
+ float x1,
+ float y1,
+ long[] colors,
+ float[] positions,
+ int tileMode,
+ long colorSpaceHandle);
+
+ public static native long nativeCreate1(
+ long matrix,
+ float x0,
+ float y0,
+ float x1,
+ float y1,
+ int[] colors,
+ float[] positions,
+ int tileMode);
+
+ public static native long nativeCreate2(
+ long matrix, float x0, float y0, float x1, float y1, int color0, int color1, int tileMode);
+
+ private LinearGradientNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/MaskFilterNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/MaskFilterNatives.java
new file mode 100644
index 000000000..683035166
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/MaskFilterNatives.java
@@ -0,0 +1,14 @@
+package org.robolectric.nativeruntime;
+
+/**
+ * Native methods for MaskFilter JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/MaskFilter.java
+ */
+public final class MaskFilterNatives {
+
+ public static native void nativeDestructor(long nativeFilter);
+
+ private MaskFilterNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/MatrixNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/MatrixNatives.java
new file mode 100644
index 000000000..d3b873af6
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/MatrixNatives.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2022 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.nativeruntime;
+
+import android.graphics.RectF;
+
+/**
+ * Native methods for Matrix JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/Matrix.java
+ */
+public class MatrixNatives {
+
+ public static native long nCreate(long nSrcOrZero);
+
+ public static native long nGetNativeFinalizer();
+
+ public static native boolean nSetRectToRect(long nObject, RectF src, RectF dst, int stf);
+
+ public static native boolean nSetPolyToPoly(
+ long nObject, float[] src, int srcIndex, float[] dst, int dstIndex, int pointCount);
+
+ public static native void nMapPoints(
+ long nObject,
+ float[] dst,
+ int dstIndex,
+ float[] src,
+ int srcIndex,
+ int ptCount,
+ boolean isPts);
+
+ public static native boolean nMapRect(long nObject, RectF dst, RectF src);
+
+ public static native void nGetValues(long nObject, float[] values);
+
+ public static native void nSetValues(long nObject, float[] values);
+
+ // ------------------ Critical JNI ------------------------
+
+ public static native boolean nIsIdentity(long nObject);
+
+ public static native boolean nIsAffine(long nObject);
+
+ public static native boolean nRectStaysRect(long nObject);
+
+ public static native void nReset(long nObject);
+
+ public static native void nSet(long nObject, long nOther);
+
+ public static native void nSetTranslate(long nObject, float dx, float dy);
+
+ public static native void nSetScale(long nObject, float sx, float sy, float px, float py);
+
+ public static native void nSetScale(long nObject, float sx, float sy);
+
+ public static native void nSetRotate(long nObject, float degrees, float px, float py);
+
+ public static native void nSetRotate(long nObject, float degrees);
+
+ public static native void nSetSinCos(
+ long nObject, float sinValue, float cosValue, float px, float py);
+
+ public static native void nSetSinCos(long nObject, float sinValue, float cosValue);
+
+ public static native void nSetSkew(long nObject, float kx, float ky, float px, float py);
+
+ public static native void nSetSkew(long nObject, float kx, float ky);
+
+ public static native void nSetConcat(long nObject, long nA, long nB);
+
+ public static native void nPreTranslate(long nObject, float dx, float dy);
+
+ public static native void nPreScale(long nObject, float sx, float sy, float px, float py);
+
+ public static native void nPreScale(long nObject, float sx, float sy);
+
+ public static native void nPreRotate(long nObject, float degrees, float px, float py);
+
+ public static native void nPreRotate(long nObject, float degrees);
+
+ public static native void nPreSkew(long nObject, float kx, float ky, float px, float py);
+
+ public static native void nPreSkew(long nObject, float kx, float ky);
+
+ public static native void nPreConcat(long nObject, long nOtherMatrix);
+
+ public static native void nPostTranslate(long nObject, float dx, float dy);
+
+ public static native void nPostScale(long nObject, float sx, float sy, float px, float py);
+
+ public static native void nPostScale(long nObject, float sx, float sy);
+
+ public static native void nPostRotate(long nObject, float degrees, float px, float py);
+
+ public static native void nPostRotate(long nObject, float degrees);
+
+ public static native void nPostSkew(long nObject, float kx, float ky, float px, float py);
+
+ public static native void nPostSkew(long nObject, float kx, float ky);
+
+ public static native void nPostConcat(long nObject, long nOtherMatrix);
+
+ public static native boolean nInvert(long nObject, long nInverse);
+
+ public static native float nMapRadius(long nObject, float radius);
+
+ public static native boolean nEquals(long nA, long nB);
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/MeasuredTextBuilderNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/MeasuredTextBuilderNatives.java
new file mode 100644
index 000000000..2ea75a242
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/MeasuredTextBuilderNatives.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 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.nativeruntime;
+
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+
+/**
+ * Native methods for MeasuredText.Builder JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/text/MeasuredText.java
+ */
+public final class MeasuredTextBuilderNatives {
+
+ public static native /* Non Zero */ long nInitBuilder();
+
+ public static native void nAddStyleRun(
+ /* Non Zero */ long nativeBuilderPtr,
+ /* Non Zero */ long paintPtr,
+ @IntRange(from = 0) int start,
+ @IntRange(from = 0) int end,
+ boolean isRtl);
+
+ public static native void nAddReplacementRun(
+ /* Non Zero */ long nativeBuilderPtr,
+ /* Non Zero */ long paintPtr,
+ @IntRange(from = 0) int start,
+ @IntRange(from = 0) int end,
+ @FloatRange(from = 0) float width);
+
+ public static native long nBuildMeasuredText(
+ /* Non Zero */ long nativeBuilderPtr,
+ long hintMtPtr,
+ char[] text,
+ boolean computeHyphenation,
+ boolean computeLayout);
+
+ public static native void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr);
+
+ private MeasuredTextBuilderNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/MeasuredTextNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/MeasuredTextNatives.java
new file mode 100644
index 000000000..5f12a0d67
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/MeasuredTextNatives.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 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.nativeruntime;
+
+import android.annotation.IntRange;
+import android.graphics.Rect;
+
+/**
+ * Native methods for MeasuredText JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/text/MeasuredText.java
+ */
+public final class MeasuredTextNatives {
+
+ public static native float nGetWidth(
+ /* Non Zero */ long nativePtr, @IntRange(from = 0) int start, @IntRange(from = 0) int end);
+
+ public static native /* Non Zero */ long nGetReleaseFunc();
+
+ public static native int nGetMemoryUsage(/* Non Zero */ long nativePtr);
+
+ public static native void nGetBounds(long nativePtr, char[] buf, int start, int end, Rect rect);
+
+ public static native float nGetCharWidthAt(long nativePtr, int offset);
+
+ private MeasuredTextNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/NIOAccess.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/NIOAccess.java
new file mode 100644
index 000000000..cc20e2663
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/NIOAccess.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2022 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.nativeruntime;
+
+import static org.robolectric.util.reflector.Reflector.reflector;
+
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.DoubleBuffer;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.nio.LongBuffer;
+import java.nio.ShortBuffer;
+import org.robolectric.util.reflector.Accessor;
+import org.robolectric.util.reflector.ForType;
+
+/**
+ * Analogue to libcore's <a
+ * href="https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:libcore/luni/src/main/java/java/nio/NIOAccess.java">NIOAccess</a>,
+ * which provides access to some internal methods and properties of {@link Buffer}. These methods
+ * are designed to work on the JVM and get called from native code such as libnativehelper.
+ */
+public final class NIOAccess {
+
+ private NIOAccess() {}
+
+ /**
+ * Returns the underlying native pointer to the data of the given Buffer starting at the Buffer's
+ * current position, or 0 if the Buffer is not backed by native heap storage.
+ */
+ public static long getBasePointer(Buffer b) {
+ long address = reflector(BufferReflector.class, b).getAddress();
+
+ if (address == 0L || !b.isDirect()) {
+ return 0L;
+ }
+ return address + ((long) b.position() << elementSizeShift(b));
+ }
+
+ /**
+ * Returns the underlying Java array containing the data of the given Buffer, or null if the
+ * Buffer is not backed by a Java array.
+ */
+ static Object getBaseArray(Buffer b) {
+ return b.hasArray() ? b.array() : null;
+ }
+
+ /**
+ * Returns the offset in bytes from the start of the underlying Java array object containing the
+ * data of the given Buffer to the actual start of the data. The start of the data takes into
+ * account the Buffer's current position. This method is only meaningful if getBaseArray() returns
+ * non-null.
+ */
+ static int getBaseArrayOffset(Buffer b) {
+ return b.hasArray() ? ((b.arrayOffset() + b.position()) << elementSizeShift(b)) : 0;
+ }
+
+ /**
+ * The Android version of java.nio.Buffer has an extra final field called _elementSizeShift that
+ * only depend on the implementation of the buffer. This method can be called instead when wanting
+ * to access the value of that field on the JVM.
+ */
+ public static int elementSizeShift(Buffer buffer) {
+ if (buffer instanceof ByteBuffer) {
+ return 0;
+ }
+ if (buffer instanceof ShortBuffer || buffer instanceof CharBuffer) {
+ return 1;
+ }
+ if (buffer instanceof IntBuffer || buffer instanceof FloatBuffer) {
+ return 2;
+ }
+ if (buffer instanceof LongBuffer || buffer instanceof DoubleBuffer) {
+ return 3;
+ }
+ return 0;
+ }
+
+ @ForType(Buffer.class)
+ interface BufferReflector {
+
+ @Accessor("address")
+ long getAddress();
+ }
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/NativeAllocationRegistryNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/NativeAllocationRegistryNatives.java
new file mode 100644
index 000000000..18a2a3595
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/NativeAllocationRegistryNatives.java
@@ -0,0 +1,13 @@
+package org.robolectric.nativeruntime;
+
+/**
+ * Native methods for NativeAllocationRegistry JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:libcore/luni/src/main/java/libcore/util/NativeAllocationRegistry.java
+ */
+public final class NativeAllocationRegistryNatives {
+ public static native void applyFreeFunction(long freeFunction, long nativePtr);
+
+ private NativeAllocationRegistryNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/NativeInterpolatorFactoryNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/NativeInterpolatorFactoryNatives.java
new file mode 100644
index 000000000..728cb998f
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/NativeInterpolatorFactoryNatives.java
@@ -0,0 +1,34 @@
+package org.robolectric.nativeruntime;
+
+/**
+ * Native methods for NativeInterpolatorFactory JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/NativeInterpolatorFactory.java
+ */
+public final class NativeInterpolatorFactoryNatives {
+
+ public static native long createAccelerateDecelerateInterpolator();
+
+ public static native long createAccelerateInterpolator(float factor);
+
+ public static native long createAnticipateInterpolator(float tension);
+
+ public static native long createAnticipateOvershootInterpolator(float tension);
+
+ public static native long createBounceInterpolator();
+
+ public static native long createCycleInterpolator(float cycles);
+
+ public static native long createDecelerateInterpolator(float factor);
+
+ public static native long createLinearInterpolator();
+
+ public static native long createOvershootInterpolator(float tension);
+
+ public static native long createPathInterpolator(float[] x, float[] y);
+
+ public static native long createLutInterpolator(float[] values);
+
+ private NativeInterpolatorFactoryNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/NinePatchNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/NinePatchNatives.java
new file mode 100644
index 000000000..f76a29812
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/NinePatchNatives.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 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.nativeruntime;
+
+import android.graphics.Rect;
+
+/**
+ * Native methods for NinePatch JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/NinePatch.java
+ */
+public final class NinePatchNatives {
+
+ public static native boolean isNinePatchChunk(byte[] chunk);
+
+ public static native long validateNinePatchChunk(byte[] chunk);
+
+ public static native void nativeFinalize(long chunk);
+
+ public static native long nativeGetTransparentRegion(
+ long bitmapHandle, long chunk, Rect location);
+
+ private NinePatchNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/PaintNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/PaintNatives.java
new file mode 100644
index 000000000..b803e1c4e
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/PaintNatives.java
@@ -0,0 +1,442 @@
+/*
+ * Copyright (C) 2006 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.nativeruntime;
+
+import android.annotation.ColorInt;
+import android.annotation.ColorLong;
+import android.graphics.Paint.FontMetrics;
+import android.graphics.Paint.FontMetricsInt;
+import android.graphics.Rect;
+
+/**
+ * Native methods for Paint JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/Paint.java
+ */
+public final class PaintNatives {
+
+ public static native long nGetNativeFinalizer();
+
+ public static native long nInit();
+
+ public static native long nInitWithPaint(long paint);
+
+ public static native int nBreakText(
+ long nObject,
+ char[] text,
+ int index,
+ int count,
+ float maxWidth,
+ int bidiFlags,
+ float[] measuredWidth);
+
+ public static native int nBreakText(
+ long nObject,
+ String text,
+ boolean measureForwards,
+ float maxWidth,
+ int bidiFlags,
+ float[] measuredWidth);
+
+ public static native int nBreakText(
+ long nObject,
+ long typefacePtr,
+ char[] text,
+ int index,
+ int count,
+ float maxWidth,
+ int bidiFlags,
+ float[] measuredWidth);
+
+ public static native int nBreakText(
+ long nObject,
+ long typefacePtr,
+ String text,
+ boolean measureForwards,
+ float maxWidth,
+ int bidiFlags,
+ float[] measuredWidth);
+
+ public static native int nGetColor(long paintPtr);
+
+ public static native int nGetAlpha(long paintPtr);
+
+ public static native float nGetTextAdvances(
+ long paintPtr,
+ long typefacePtr,
+ char[] text,
+ int index,
+ int count,
+ int contextIndex,
+ int contextCount,
+ int bidiFlags,
+ float[] advances,
+ int advancesIndex);
+
+ public static native float nGetTextAdvances(
+ long paintPtr,
+ long typefacePtr,
+ String text,
+ int start,
+ int end,
+ int contextStart,
+ int contextEnd,
+ int bidiFlags,
+ float[] advances,
+ int advancesIndex);
+
+ public static native float nGetTextAdvances(
+ long paintPtr,
+ char[] text,
+ int index,
+ int count,
+ int contextIndex,
+ int contextCount,
+ int bidiFlags,
+ float[] advances,
+ int advancesIndex);
+
+ public static native float nGetTextAdvances(
+ long paintPtr,
+ String text,
+ int start,
+ int end,
+ int contextStart,
+ int contextEnd,
+ int bidiFlags,
+ float[] advances,
+ int advancesIndex);
+
+ public native int nGetTextRunCursor(
+ long paintPtr,
+ char[] text,
+ int contextStart,
+ int contextLength,
+ int dir,
+ int offset,
+ int cursorOpt);
+
+ public native int nGetTextRunCursor(
+ long paintPtr,
+ String text,
+ int contextStart,
+ int contextEnd,
+ int dir,
+ int offset,
+ int cursorOpt);
+
+ public native int nGetTextRunCursor(
+ long paintPtr,
+ long typefacePtr,
+ char[] text,
+ int contextStart,
+ int contextLength,
+ int dir,
+ int offset,
+ int cursorOpt);
+
+ public native int nGetTextRunCursor(
+ long paintPtr,
+ long typefacePtr,
+ String text,
+ int contextStart,
+ int contextEnd,
+ int dir,
+ int offset,
+ int cursorOpt);
+
+ public static native void nGetTextPath(
+ long paintPtr, int bidiFlags, char[] text, int index, int count, float x, float y, long path);
+
+ public static native void nGetTextPath(
+ long paintPtr, int bidiFlags, String text, int start, int end, float x, float y, long path);
+
+ public static native void nGetTextPath(
+ long paintPtr,
+ long typefacePtr,
+ int bidiFlags,
+ char[] text,
+ int index,
+ int count,
+ float x,
+ float y,
+ long path);
+
+ public static native void nGetTextPath(
+ long paintPtr,
+ long typefacePtr,
+ int bidiFlags,
+ String text,
+ int start,
+ int end,
+ float x,
+ float y,
+ long path);
+
+ public static native void nGetStringBounds(
+ long nativePaint, String text, int start, int end, int bidiFlags, Rect bounds);
+
+ public static native void nGetStringBounds(
+ long nativePaint,
+ long typefacePtr,
+ String text,
+ int start,
+ int end,
+ int bidiFlags,
+ Rect bounds);
+
+ public static native void nGetCharArrayBounds(
+ long nativePaint, char[] text, int index, int count, int bidiFlags, Rect bounds);
+
+ public static native void nGetCharArrayBounds(
+ long nativePaint,
+ long typefacePtr,
+ char[] text,
+ int index,
+ int count,
+ int bidiFlags,
+ Rect bounds);
+
+ public static native boolean nHasGlyph(long paintPtr, int bidiFlags, String string);
+
+ public static native boolean nHasGlyph(
+ long paintPtr, long typefacePtr, int bidiFlags, String string);
+
+ public static native float nGetRunAdvance(
+ long paintPtr,
+ char[] text,
+ int start,
+ int end,
+ int contextStart,
+ int contextEnd,
+ boolean isRtl,
+ int offset);
+
+ public static native float nGetRunAdvance(
+ long paintPtr,
+ long typefacePtr,
+ char[] text,
+ int start,
+ int end,
+ int contextStart,
+ int contextEnd,
+ boolean isRtl,
+ int offset);
+
+ public static native int nGetOffsetForAdvance(
+ long paintPtr,
+ char[] text,
+ int start,
+ int end,
+ int contextStart,
+ int contextEnd,
+ boolean isRtl,
+ float advance);
+
+ public static native int nGetOffsetForAdvance(
+ long paintPtr,
+ long typefacePtr,
+ char[] text,
+ int start,
+ int end,
+ int contextStart,
+ int contextEnd,
+ boolean isRtl,
+ float advance);
+
+ public static native int nSetTextLocales(long paintPtr, String locales);
+
+ public static native void nSetFontFeatureSettings(long paintPtr, String settings);
+
+ public static native float nGetFontMetrics(long paintPtr, FontMetrics metrics);
+
+ public static native float nGetFontMetrics(long paintPtr, long typefacePtr, FontMetrics metrics);
+
+ public static native int nGetFontMetricsInt(long paintPtr, FontMetricsInt fmi);
+
+ public static native int nGetFontMetricsInt(long paintPtr, long typefacePtr, FontMetricsInt fmi);
+
+ public static native void nReset(long paintPtr);
+
+ public static native void nSet(long paintPtrDest, long paintPtrSrc);
+
+ public static native int nGetStyle(long paintPtr);
+
+ public static native void nSetStyle(long paintPtr, int style);
+
+ public static native int nGetStrokeCap(long paintPtr);
+
+ public static native void nSetStrokeCap(long paintPtr, int cap);
+
+ public static native int nGetStrokeJoin(long paintPtr);
+
+ public static native void nSetStrokeJoin(long paintPtr, int join);
+
+ public static native boolean nGetFillPath(long paintPtr, long src, long dst);
+
+ public static native long nSetShader(long paintPtr, long shader);
+
+ public static native long nSetColorFilter(long paintPtr, long filter);
+
+ public static native void nSetXfermode(long paintPtr, int xfermode);
+
+ public static native long nSetPathEffect(long paintPtr, long effect);
+
+ public static native long nSetMaskFilter(long paintPtr, long maskfilter);
+
+ public static native void nSetTypeface(long paintPtr, long typeface);
+
+ public static native int nGetTextAlign(long paintPtr);
+
+ public static native void nSetTextAlign(long paintPtr, int align);
+
+ public static native void nSetTextLocalesByMinikinLocaleListId(
+ long paintPtr, int mMinikinLocaleListId);
+
+ public static native void nSetShadowLayer(
+ long paintPtr,
+ float radius,
+ float dx,
+ float dy,
+ long colorSpaceHandle,
+ @ColorLong long shadowColor);
+
+ public static native void nSetShadowLayer(
+ long paintPtr, float radius, float dx, float dy, @ColorInt int shadowColor);
+
+ public static native boolean nHasShadowLayer(long paintPtr);
+
+ public static native float nGetLetterSpacing(long paintPtr);
+
+ public static native void nSetLetterSpacing(long paintPtr, float letterSpacing);
+
+ public static native float nGetWordSpacing(long paintPtr);
+
+ public static native void nSetWordSpacing(long paintPtr, float wordSpacing);
+
+ public static native int nGetStartHyphenEdit(long paintPtr);
+
+ public static native int nGetEndHyphenEdit(long paintPtr);
+
+ public static native void nSetStartHyphenEdit(long paintPtr, int hyphen);
+
+ public static native void nSetEndHyphenEdit(long paintPtr, int hyphen);
+
+ public static native void nSetStrokeMiter(long paintPtr, float miter);
+
+ public static native float nGetStrokeMiter(long paintPtr);
+
+ public static native void nSetStrokeWidth(long paintPtr, float width);
+
+ public static native float nGetStrokeWidth(long paintPtr);
+
+ public static native void nSetAlpha(long paintPtr, int a);
+
+ public static native void nSetDither(long paintPtr, boolean dither);
+
+ public static native int nGetFlags(long paintPtr);
+
+ public static native void nSetFlags(long paintPtr, int flags);
+
+ public static native int nGetHinting(long paintPtr);
+
+ public static native void nSetHinting(long paintPtr, int mode);
+
+ public static native void nSetAntiAlias(long paintPtr, boolean aa);
+
+ public static native void nSetLinearText(long paintPtr, boolean linearText);
+
+ public static native void nSetSubpixelText(long paintPtr, boolean subpixelText);
+
+ public static native void nSetUnderlineText(long paintPtr, boolean underlineText);
+
+ public static native void nSetFakeBoldText(long paintPtr, boolean fakeBoldText);
+
+ public static native void nSetFilterBitmap(long paintPtr, boolean filter);
+
+ public static native void nSetColor(long paintPtr, long colorSpaceHandle, @ColorLong long color);
+
+ public static native void nSetColor(long paintPtr, @ColorInt int color);
+
+ public static native void nSetStrikeThruText(long paintPtr, boolean strikeThruText);
+
+ public static native boolean nIsElegantTextHeight(long paintPtr);
+
+ public static native void nSetElegantTextHeight(long paintPtr, boolean elegant);
+
+ public static native float nGetTextSize(long paintPtr);
+
+ public static native float nGetTextScaleX(long paintPtr);
+
+ public static native void nSetTextScaleX(long paintPtr, float scaleX);
+
+ public static native float nGetTextSkewX(long paintPtr);
+
+ public static native void nSetTextSkewX(long paintPtr, float skewX);
+
+ public static native float nAscent(long paintPtr);
+
+ public static native float nAscent(long paintPtr, long typefacePtr);
+
+ public static native float nDescent(long paintPtr);
+
+ public static native float nDescent(long paintPtr, long typefacePtr);
+
+ public static native float nGetUnderlinePosition(long paintPtr);
+
+ public static native float nGetUnderlineThickness(long paintPtr);
+
+ public static native float nGetStrikeThruPosition(long paintPtr);
+
+ public static native float nGetStrikeThruThickness(long paintPtr);
+
+ public static native void nSetTextSize(long paintPtr, float textSize);
+
+ public static native boolean nEqualsForTextMeasurement(long leftPaintPtr, long rightPaintPtr);
+
+ public static native void nGetFontMetricsIntForText(
+ long paintPtr,
+ char[] text,
+ int start,
+ int count,
+ int ctxStart,
+ int ctxCount,
+ boolean isRtl,
+ FontMetricsInt outMetrics);
+
+ public static native void nGetFontMetricsIntForText(
+ long paintPtr,
+ String text,
+ int start,
+ int count,
+ int ctxStart,
+ int ctxCount,
+ boolean isRtl,
+ FontMetricsInt outMetrics);
+
+ public static native float nGetRunCharacterAdvance(
+ long paintPtr,
+ char[] text,
+ int start,
+ int end,
+ int contextStart,
+ int contextEnd,
+ boolean isRtl,
+ int offset,
+ float[] advances,
+ int advancesIndex);
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/PathDashPathEffectNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/PathDashPathEffectNatives.java
new file mode 100644
index 000000000..5c508fb4e
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/PathDashPathEffectNatives.java
@@ -0,0 +1,15 @@
+package org.robolectric.nativeruntime;
+
+/**
+ * Native methods for PathDashPathEffect JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/PathDashPathEffect.java
+ */
+public final class PathDashPathEffectNatives {
+
+ public static native long nativeCreate(
+ long nativePath, float advance, float phase, int nativeStyle);
+
+ private PathDashPathEffectNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/PathEffectNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/PathEffectNatives.java
new file mode 100644
index 000000000..33215a4e6
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/PathEffectNatives.java
@@ -0,0 +1,14 @@
+package org.robolectric.nativeruntime;
+
+/**
+ * Native methods for PathEffect JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/PathEffect.java
+ */
+public final class PathEffectNatives {
+
+ public static native void nativeDestructor(long nativePatheffect);
+
+ private PathEffectNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/PathMeasureNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/PathMeasureNatives.java
new file mode 100644
index 000000000..e1b8d8385
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/PathMeasureNatives.java
@@ -0,0 +1,34 @@
+package org.robolectric.nativeruntime;
+
+/**
+ * Native methods for PathMeasure JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/PathMeasure.java
+ */
+public final class PathMeasureNatives {
+
+ public static native long native_create(long nativePath, boolean forceClosed);
+
+ public static native void native_setPath(
+ long nativeInstance, long nativePath, boolean forceClosed);
+
+ public static native float native_getLength(long nativeInstance);
+
+ public static native boolean native_getPosTan(
+ long nativeInstance, float distance, float[] pos, float[] tan);
+
+ public static native boolean native_getMatrix(
+ long nativeInstance, float distance, long nativeMatrix, int flags);
+
+ public static native boolean native_getSegment(
+ long nativeInstance, float startD, float stopD, long nativePath, boolean startWithMoveTo);
+
+ public static native boolean native_isClosed(long nativeInstance);
+
+ public static native boolean native_nextContour(long nativeInstance);
+
+ public static native void native_destroy(long nativeInstance);
+
+ private PathMeasureNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/PathNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/PathNatives.java
new file mode 100644
index 000000000..0870f6bf7
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/PathNatives.java
@@ -0,0 +1,111 @@
+package org.robolectric.nativeruntime;
+
+import android.graphics.RectF;
+
+/**
+ * Native methods for Path JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/Path.java
+ */
+public final class PathNatives {
+
+ public static native long nInit();
+
+ public static native long nInit(long nPath);
+
+ public static native long nGetFinalizer();
+
+ public static native void nSet(long nativeDst, long nSrc);
+
+ public static native void nComputeBounds(long nPath, RectF bounds);
+
+ public static native void nIncReserve(long nPath, int extraPtCount);
+
+ public static native void nMoveTo(long nPath, float x, float y);
+
+ public static native void nRMoveTo(long nPath, float dx, float dy);
+
+ public static native void nLineTo(long nPath, float x, float y);
+
+ public static native void nRLineTo(long nPath, float dx, float dy);
+
+ public static native void nQuadTo(long nPath, float x1, float y1, float x2, float y2);
+
+ public static native void nRQuadTo(long nPath, float dx1, float dy1, float dx2, float dy2);
+
+ public static native void nCubicTo(
+ long nPath, float x1, float y1, float x2, float y2, float x3, float y3);
+
+ public static native void nRCubicTo(
+ long nPath, float x1, float y1, float x2, float y2, float x3, float y3);
+
+ public static native void nArcTo(
+ long nPath,
+ float left,
+ float top,
+ float right,
+ float bottom,
+ float startAngle,
+ float sweepAngle,
+ boolean forceMoveTo);
+
+ public static native void nClose(long nPath);
+
+ public static native void nAddRect(
+ long nPath, float left, float top, float right, float bottom, int dir);
+
+ public static native void nAddOval(
+ long nPath, float left, float top, float right, float bottom, int dir);
+
+ public static native void nAddCircle(long nPath, float x, float y, float radius, int dir);
+
+ public static native void nAddArc(
+ long nPath,
+ float left,
+ float top,
+ float right,
+ float bottom,
+ float startAngle,
+ float sweepAngle);
+
+ public static native void nAddRoundRect(
+ long nPath, float left, float top, float right, float bottom, float rx, float ry, int dir);
+
+ public static native void nAddRoundRect(
+ long nPath, float left, float top, float right, float bottom, float[] radii, int dir);
+
+ public static native void nAddPath(long nPath, long src, float dx, float dy);
+
+ public static native void nAddPath(long nPath, long src);
+
+ public static native void nAddPath(long nPath, long src, long matrix);
+
+ public static native void nOffset(long nPath, float dx, float dy);
+
+ public static native void nSetLastPoint(long nPath, float dx, float dy);
+
+ public static native void nTransform(long nPath, long matrix, long dstPath);
+
+ public static native void nTransform(long nPath, long matrix);
+
+ public static native boolean nOp(long path1, long path2, int op, long result);
+
+ public static native boolean nIsRect(long nPath, RectF rect);
+
+ public static native void nReset(long nPath);
+
+ public static native void nRewind(long nPath);
+
+ public static native boolean nIsEmpty(long nPath);
+
+ public static native boolean nIsConvex(long nPath);
+
+ public static native int nGetFillType(long nPath);
+
+ public static native void nSetFillType(long nPath, int ft);
+
+ public static native float[] nApproximate(long nPath, float error);
+
+ private PathNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/PathParserNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/PathParserNatives.java
new file mode 100644
index 000000000..fb1d5f2c8
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/PathParserNatives.java
@@ -0,0 +1,31 @@
+package org.robolectric.nativeruntime;
+
+/**
+ * Native methods for PathParser JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/PathParser.java
+ */
+public final class PathParserNatives {
+
+ public static native void nParseStringForPath(long pathPtr, String pathString, int stringLength);
+
+ public static native long nCreatePathDataFromString(String pathString, int stringLength);
+
+ public static native void nCreatePathFromPathData(long outPathPtr, long pathData);
+
+ public static native long nCreateEmptyPathData();
+
+ public static native long nCreatePathData(long nativePtr);
+
+ public static native boolean nInterpolatePathData(
+ long outDataPtr, long fromDataPtr, long toDataPtr, float fraction);
+
+ public static native void nFinalize(long nativePtr);
+
+ public static native boolean nCanMorph(long fromDataPtr, long toDataPtr);
+
+ public static native void nSetPathData(long outDataPtr, long fromDataPtr);
+
+ private PathParserNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/PictureNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/PictureNatives.java
new file mode 100644
index 000000000..c2bdba6a2
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/PictureNatives.java
@@ -0,0 +1,32 @@
+package org.robolectric.nativeruntime;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Native methods for Picture JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/Picture.java
+ */
+public class PictureNatives {
+
+ public static native long nativeConstructor(long nativeSrcOr0);
+
+ public static native long nativeCreateFromStream(InputStream stream, byte[] storage);
+
+ public static native int nativeGetWidth(long nativePicture);
+
+ public static native int nativeGetHeight(long nativePicture);
+
+ public static native long nativeBeginRecording(long nativeCanvas, int w, int h);
+
+ public static native void nativeEndRecording(long nativeCanvas);
+
+ public static native void nativeDraw(long nativeCanvas, long nativePicture);
+
+ public static native boolean nativeWriteToStream(
+ long nativePicture, OutputStream stream, byte[] storage);
+
+ public static native void nativeDestructor(long nativePicture);
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/PorterDuffColorFilterNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/PorterDuffColorFilterNatives.java
new file mode 100644
index 000000000..8071bfd51
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/PorterDuffColorFilterNatives.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2006 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.nativeruntime;
+
+/**
+ * Native methods for PorterDuffColorFilter JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/PorterDuffColorFilter.java
+ */
+public final class PorterDuffColorFilterNatives {
+
+ public static native long native_CreateBlendModeFilter(int srcColor, int blendmode);
+
+ private PorterDuffColorFilterNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/PropertyValuesHolderNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/PropertyValuesHolderNatives.java
new file mode 100644
index 000000000..7cc6e010a
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/PropertyValuesHolderNatives.java
@@ -0,0 +1,41 @@
+package org.robolectric.nativeruntime;
+
+/**
+ * Native methods for PropertyValuesHolder JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/PropertyValuesHolder.java
+ */
+public final class PropertyValuesHolderNatives {
+
+ public static native long nGetIntMethod(Class<?> targetClass, String methodName);
+
+ public static native long nGetFloatMethod(Class<?> targetClass, String methodName);
+
+ public static native long nGetMultipleIntMethod(
+ Class<?> targetClass, String methodName, int numParams);
+
+ public static native long nGetMultipleFloatMethod(
+ Class<?> targetClass, String methodName, int numParams);
+
+ public static native void nCallIntMethod(Object target, long methodID, int arg);
+
+ public static native void nCallFloatMethod(Object target, long methodID, float arg);
+
+ public static native void nCallTwoIntMethod(Object target, long methodID, int arg1, int arg2);
+
+ public static native void nCallFourIntMethod(
+ Object target, long methodID, int arg1, int arg2, int arg3, int arg4);
+
+ public static native void nCallMultipleIntMethod(Object target, long methodID, int[] args);
+
+ public static native void nCallTwoFloatMethod(
+ Object target, long methodID, float arg1, float arg2);
+
+ public static native void nCallFourFloatMethod(
+ Object target, long methodID, float arg1, float arg2, float arg3, float arg4);
+
+ public static native void nCallMultipleFloatMethod(Object target, long methodID, float[] args);
+
+ private PropertyValuesHolderNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/RadialGradientNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/RadialGradientNatives.java
new file mode 100644
index 000000000..6c21a81b8
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/RadialGradientNatives.java
@@ -0,0 +1,33 @@
+package org.robolectric.nativeruntime;
+
+import android.annotation.ColorLong;
+
+/**
+ * Native methods for RadialGradient JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/RadialGradient.java
+ */
+public class RadialGradientNatives {
+
+ public static native long nativeCreate(
+ long matrix,
+ float startX,
+ float startY,
+ float startRadius,
+ float endX,
+ float endY,
+ float endRadius,
+ @ColorLong long[] colors,
+ float[] positions,
+ int tileMode,
+ long colorSpaceHandle);
+
+ public static native long nativeCreate1(
+ long matrix, float x, float y, float radius, int[] colors, float[] positions, int tileMode);
+
+ public static native long nativeCreate2(
+ long matrix, float x, float y, float radius, int color0, int color1, int tileMode);
+
+ RadialGradientNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/RecordingCanvasNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/RecordingCanvasNatives.java
new file mode 100644
index 000000000..da67153f5
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/RecordingCanvasNatives.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2022 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.nativeruntime;
+
+/**
+ * Native methods for RecordingCanvas JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/RecordingCanvas.java
+ */
+public final class RecordingCanvasNatives {
+
+ public static native long nCreateDisplayListCanvas(long node, int width, int height);
+
+ public static native void nResetDisplayListCanvas(long canvas, long node, int width, int height);
+
+ public static native int nGetMaximumTextureWidth();
+
+ public static native int nGetMaximumTextureHeight();
+
+ public static native void nEnableZ(long renderer, boolean enableZ);
+
+ public static native void nFinishRecording(long renderer, long renderNode);
+
+ public static native void nDrawRenderNode(long renderer, long renderNode);
+
+ public static native void nDrawTextureLayer(long renderer, long layer);
+
+ public static native void nDrawCircle(
+ long renderer, long propCx, long propCy, long propRadius, long propPaint);
+
+ public static native void nDrawRipple(
+ long renderer,
+ long propCx,
+ long propCy,
+ long propRadius,
+ long propPaint,
+ long propProgress,
+ long turbulencePhase,
+ int color,
+ long runtimeEffect);
+
+ public static native void nDrawRoundRect(
+ long renderer,
+ long propLeft,
+ long propTop,
+ long propRight,
+ long propBottom,
+ long propRx,
+ long propRy,
+ long propPaint);
+
+ public static native void nDrawWebViewFunctor(long canvas, int functor);
+
+ private RecordingCanvasNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/RegionIteratorNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/RegionIteratorNatives.java
new file mode 100644
index 000000000..ea2f17d44
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/RegionIteratorNatives.java
@@ -0,0 +1,20 @@
+package org.robolectric.nativeruntime;
+
+import android.graphics.Rect;
+
+/**
+ * Native methods for RegionIterator JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/RegionIterator.java
+ */
+public final class RegionIteratorNatives {
+
+ public static native long nativeConstructor(long nativeRegion);
+
+ public static native void nativeDestructor(long nativeIter);
+
+ public static native boolean nativeNext(long nativeIter, Rect r);
+
+ private RegionIteratorNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/RegionNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/RegionNatives.java
new file mode 100644
index 000000000..c6d1bae52
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/RegionNatives.java
@@ -0,0 +1,66 @@
+package org.robolectric.nativeruntime;
+
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.Parcel;
+
+/**
+ * Native methods for Region JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/Region.java
+ */
+public final class RegionNatives {
+
+ // Must be this style to match AOSP branch
+ public long mNativeRegion;
+
+ public static native boolean nativeEquals(long nativeR1, long nativeR2);
+
+ public static native long nativeConstructor();
+
+ public static native void nativeDestructor(long nativeRegion);
+
+ public static native void nativeSetRegion(long nativeDst, long nativeSrc);
+
+ public static native boolean nativeSetRect(
+ long nativeDst, int left, int top, int right, int bottom);
+
+ public static native boolean nativeSetPath(long nativeDst, long nativePath, long nativeClip);
+
+ public static native boolean nativeGetBounds(long nativeRegion, Rect rect);
+
+ public static native boolean nativeGetBoundaryPath(long nativeRegion, long nativePath);
+
+ public static native boolean nativeOp(
+ long nativeDst, int left, int top, int right, int bottom, int op);
+
+ public static native boolean nativeOp(long nativeDst, Rect rect, long nativeRegion, int op);
+
+ public static native boolean nativeOp(
+ long nativeDst, long nativeRegion1, long nativeRegion2, int op);
+
+ public static native long nativeCreateFromParcel(Parcel p);
+
+ public static native boolean nativeWriteToParcel(long nativeRegion, Parcel p);
+
+ public static native String nativeToString(long nativeRegion);
+
+ public native boolean isEmpty();
+
+ public native boolean isRect();
+
+ public native boolean isComplex();
+
+ public native boolean contains(int x, int y);
+
+ public native boolean quickContains(int left, int top, int right, int bottom);
+
+ public native boolean quickReject(int left, int top, int right, int bottom);
+
+ public native boolean quickReject(Region rgn);
+
+ public native void translate(int dx, int dy, Region dst);
+
+ public native void scale(float scale, Region dst);
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/RenderEffectNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/RenderEffectNatives.java
new file mode 100644
index 000000000..dcf82d1b4
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/RenderEffectNatives.java
@@ -0,0 +1,39 @@
+package org.robolectric.nativeruntime;
+
+/**
+ * Native methods for RenderEffect JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/RenderEffect.java
+ */
+public final class RenderEffectNatives {
+
+ public static native long nativeCreateOffsetEffect(
+ float offsetX, float offsetY, long nativeInput);
+
+ public static native long nativeCreateBlurEffect(
+ float radiusX, float radiusY, long nativeInput, int edgeTreatment);
+
+ public static native long nativeCreateBitmapEffect(
+ long bitmapHandle,
+ float srcLeft,
+ float srcTop,
+ float srcRight,
+ float srcBottom,
+ float dstLeft,
+ float dstTop,
+ float dstRight,
+ float dstBottom);
+
+ public static native long nativeCreateColorFilterEffect(long colorFilter, long nativeInput);
+
+ public static native long nativeCreateBlendModeEffect(long dst, long src, int blendmode);
+
+ public static native long nativeCreateChainEffect(long outer, long inner);
+
+ public static native long nativeCreateShaderEffect(long shader);
+
+ public static native long nativeGetFinalizer();
+
+ private RenderEffectNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/RenderNodeAnimatorNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/RenderNodeAnimatorNatives.java
new file mode 100644
index 000000000..3d7de6e8d
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/RenderNodeAnimatorNatives.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 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.nativeruntime;
+
+/**
+ * Native methods for RenderNodeAnimator JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/RenderNodeAnimator.java
+ */
+public final class RenderNodeAnimatorNatives {
+
+ public static native long nCreateAnimator(int property, float finalValue);
+
+ public static native long nCreateCanvasPropertyFloatAnimator(
+ long canvasProperty, float finalValue);
+
+ public static native long nCreateCanvasPropertyPaintAnimator(
+ long canvasProperty, int paintField, float finalValue);
+
+ public static native long nCreateRevealAnimator(int x, int y, float startRadius, float endRadius);
+
+ public static native void nSetStartValue(long nativePtr, float startValue);
+
+ public static native void nSetDuration(long nativePtr, long duration);
+
+ public static native long nGetDuration(long nativePtr);
+
+ public static native void nSetStartDelay(long nativePtr, long startDelay);
+
+ public static native void nSetInterpolator(long animPtr, long interpolatorPtr);
+
+ public static native void nSetAllowRunningAsync(long animPtr, boolean mayRunAsync);
+
+ public static native void nSetListener(long animPtr, Object listener);
+
+ public static native void nStart(long animPtr);
+
+ public static native void nEnd(long animPtr);
+
+ private RenderNodeAnimatorNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/RenderNodeNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/RenderNodeNatives.java
new file mode 100644
index 000000000..adda69e61
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/RenderNodeNatives.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2022 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.nativeruntime;
+
+import android.graphics.RenderNode.PositionUpdateListener;
+
+/**
+ * Native methods for RenderNode JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/RenderNode.java
+ */
+public final class RenderNodeNatives {
+
+ public static native long nCreate(String name);
+
+ public static native long nGetNativeFinalizer();
+
+ public static native void nOutput(long renderNode);
+
+ public static native int nGetUsageSize(long renderNode);
+
+ public static native int nGetAllocatedSize(long renderNode);
+
+ public static native void nRequestPositionUpdates(
+ long renderNode, PositionUpdateListener callback);
+
+ public static native void nAddAnimator(long renderNode, long animatorPtr);
+
+ public static native void nEndAllAnimators(long renderNode);
+
+ public static native void nDiscardDisplayList(long renderNode);
+
+ public static native boolean nIsValid(long renderNode);
+
+ public static native void nGetTransformMatrix(long renderNode, long nativeMatrix);
+
+ public static native void nGetInverseTransformMatrix(long renderNode, long nativeMatrix);
+
+ public static native boolean nHasIdentityMatrix(long renderNode);
+
+ public static native boolean nOffsetTopAndBottom(long renderNode, int offset);
+
+ public static native boolean nOffsetLeftAndRight(long renderNode, int offset);
+
+ public static native boolean nSetLeftTopRightBottom(
+ long renderNode, int left, int top, int right, int bottom);
+
+ public static native boolean nSetLeft(long renderNode, int left);
+
+ public static native boolean nSetTop(long renderNode, int top);
+
+ public static native boolean nSetRight(long renderNode, int right);
+
+ public static native boolean nSetBottom(long renderNode, int bottom);
+
+ public static native int nGetLeft(long renderNode);
+
+ public static native int nGetTop(long renderNode);
+
+ public static native int nGetRight(long renderNode);
+
+ public static native int nGetBottom(long renderNode);
+
+ public static native boolean nSetCameraDistance(long renderNode, float distance);
+
+ public static native boolean nSetPivotY(long renderNode, float pivotY);
+
+ public static native boolean nSetPivotX(long renderNode, float pivotX);
+
+ public static native boolean nResetPivot(long renderNode);
+
+ public static native boolean nSetLayerType(long renderNode, int layerType);
+
+ public static native int nGetLayerType(long renderNode);
+
+ public static native boolean nSetLayerPaint(long renderNode, long paint);
+
+ public static native boolean nSetClipToBounds(long renderNode, boolean clipToBounds);
+
+ public static native boolean nGetClipToBounds(long renderNode);
+
+ public static native boolean nSetClipBounds(
+ long renderNode, int left, int top, int right, int bottom);
+
+ public static native boolean nSetClipBoundsEmpty(long renderNode);
+
+ public static native boolean nSetProjectBackwards(long renderNode, boolean shouldProject);
+
+ public static native boolean nSetProjectionReceiver(long renderNode, boolean shouldReceive);
+
+ public static native boolean nSetOutlineRoundRect(
+ long renderNode, int left, int top, int right, int bottom, float radius, float alpha);
+
+ public static native boolean nSetOutlinePath(long renderNode, long nativePath, float alpha);
+
+ public static native boolean nSetOutlineEmpty(long renderNode);
+
+ public static native boolean nSetOutlineNone(long renderNode);
+
+ public static native boolean nClearStretch(long renderNode);
+
+ public static native boolean nStretch(
+ long renderNode, float vecX, float vecY, float maxStretchX, float maxStretchY);
+
+ public static native boolean nHasShadow(long renderNode);
+
+ public static native boolean nSetSpotShadowColor(long renderNode, int color);
+
+ public static native boolean nSetAmbientShadowColor(long renderNode, int color);
+
+ public static native int nGetSpotShadowColor(long renderNode);
+
+ public static native int nGetAmbientShadowColor(long renderNode);
+
+ public static native boolean nSetClipToOutline(long renderNode, boolean clipToOutline);
+
+ public static native boolean nSetRevealClip(
+ long renderNode, boolean shouldClip, float x, float y, float radius);
+
+ public static native boolean nSetAlpha(long renderNode, float alpha);
+
+ public static native boolean nSetRenderEffect(long renderNode, long renderEffect);
+
+ public static native boolean nSetHasOverlappingRendering(
+ long renderNode, boolean hasOverlappingRendering);
+
+ public static native void nSetUsageHint(long renderNode, int usageHint);
+
+ public static native boolean nSetElevation(long renderNode, float lift);
+
+ public static native boolean nSetTranslationX(long renderNode, float translationX);
+
+ public static native boolean nSetTranslationY(long renderNode, float translationY);
+
+ public static native boolean nSetTranslationZ(long renderNode, float translationZ);
+
+ public static native boolean nSetRotation(long renderNode, float rotation);
+
+ public static native boolean nSetRotationX(long renderNode, float rotationX);
+
+ public static native boolean nSetRotationY(long renderNode, float rotationY);
+
+ public static native boolean nSetScaleX(long renderNode, float scaleX);
+
+ public static native boolean nSetScaleY(long renderNode, float scaleY);
+
+ public static native boolean nSetStaticMatrix(long renderNode, long nativeMatrix);
+
+ public static native boolean nSetAnimationMatrix(long renderNode, long animationMatrix);
+
+ public static native boolean nHasOverlappingRendering(long renderNode);
+
+ public static native boolean nGetAnimationMatrix(long renderNode, long animationMatrix);
+
+ public static native boolean nGetClipToOutline(long renderNode);
+
+ public static native float nGetAlpha(long renderNode);
+
+ public static native float nGetCameraDistance(long renderNode);
+
+ public static native float nGetScaleX(long renderNode);
+
+ public static native float nGetScaleY(long renderNode);
+
+ public static native float nGetElevation(long renderNode);
+
+ public static native float nGetTranslationX(long renderNode);
+
+ public static native float nGetTranslationY(long renderNode);
+
+ public static native float nGetTranslationZ(long renderNode);
+
+ public static native float nGetRotation(long renderNode);
+
+ public static native float nGetRotationX(long renderNode);
+
+ public static native float nGetRotationY(long renderNode);
+
+ public static native boolean nIsPivotExplicitlySet(long renderNode);
+
+ public static native float nGetPivotX(long renderNode);
+
+ public static native float nGetPivotY(long renderNode);
+
+ public static native int nGetWidth(long renderNode);
+
+ public static native int nGetHeight(long renderNode);
+
+ public static native boolean nSetAllowForceDark(long renderNode, boolean allowForceDark);
+
+ public static native boolean nGetAllowForceDark(long renderNode);
+
+ public static native long nGetUniqueId(long renderNode);
+
+ private RenderNodeNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/RuntimeShaderNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/RuntimeShaderNatives.java
new file mode 100644
index 000000000..6d4e49f23
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/RuntimeShaderNatives.java
@@ -0,0 +1,23 @@
+package org.robolectric.nativeruntime;
+
+/**
+ * Native methods for RuntimeShader JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/RuntimeShader.java
+ */
+public class RuntimeShaderNatives {
+
+ public static native long nativeGetFinalizer();
+
+ public static native long nativeCreateBuilder(String sksl);
+
+ public static native long nativeCreateShader(long shaderBuilder, long matrix, boolean isOpaque);
+
+ public static native void nativeUpdateUniforms(
+ long shaderBuilder, String uniformName, float[] uniforms);
+
+ public static native void nativeUpdateShader(long shaderBuilder, String shaderName, long shader);
+
+ private RuntimeShaderNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/ShaderNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/ShaderNatives.java
new file mode 100644
index 000000000..b50fa5fc2
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/ShaderNatives.java
@@ -0,0 +1,14 @@
+package org.robolectric.nativeruntime;
+
+/**
+ * Native methods for Shader JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/Shader.java
+ */
+public final class ShaderNatives {
+
+ public static native long nativeGetFinalizer();
+
+ private ShaderNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/SumPathEffectNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/SumPathEffectNatives.java
new file mode 100644
index 000000000..d7edf0e56
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/SumPathEffectNatives.java
@@ -0,0 +1,14 @@
+package org.robolectric.nativeruntime;
+
+/**
+ * Native methods for SumPathEffect JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/SumPathEffect.java
+ */
+public final class SumPathEffectNatives {
+
+ public static native long nativeCreate(long first, long second);
+
+ private SumPathEffectNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/SurfaceNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/SurfaceNatives.java
new file mode 100644
index 000000000..882d811a5
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/SurfaceNatives.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2022 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.nativeruntime;
+
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.SurfaceTexture;
+import android.hardware.HardwareBuffer;
+import android.os.Parcel;
+
+/**
+ * Native methods for Surface JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/view/Surface.java
+ */
+public final class SurfaceNatives {
+
+ public static native long nativeCreateFromSurfaceTexture(SurfaceTexture surfaceTexture);
+
+ public static native long nativeCreateFromSurfaceControl(long surfaceControlNativeObject);
+
+ public static native long nativeGetFromSurfaceControl(
+ long surfaceObject, long surfaceControlNativeObject);
+
+ public static native long nativeGetFromBlastBufferQueue(
+ long surfaceObject, long blastBufferQueueNativeObject);
+
+ public static native long nativeLockCanvas(long nativeObject, Canvas canvas, Rect dirty);
+
+ public static native void nativeUnlockCanvasAndPost(long nativeObject, Canvas canvas);
+
+ public static native void nativeRelease(long nativeObject);
+
+ public static native boolean nativeIsValid(long nativeObject);
+
+ public static native boolean nativeIsConsumerRunningBehind(long nativeObject);
+
+ public static native long nativeReadFromParcel(long nativeObject, Parcel source);
+
+ public static native void nativeWriteToParcel(long nativeObject, Parcel dest);
+
+ public static native void nativeAllocateBuffers(long nativeObject);
+
+ public static native int nativeGetWidth(long nativeObject);
+
+ public static native int nativeGetHeight(long nativeObject);
+
+ public static native long nativeGetNextFrameNumber(long nativeObject);
+
+ public static native int nativeSetScalingMode(long nativeObject, int scalingMode);
+
+ public static native int nativeForceScopedDisconnect(long nativeObject);
+
+ public static native int nativeAttachAndQueueBufferWithColorSpace(
+ long nativeObject, HardwareBuffer buffer, int colorSpaceId);
+
+ public static native int nativeSetSharedBufferModeEnabled(long nativeObject, boolean enabled);
+
+ public static native int nativeSetAutoRefreshEnabled(long nativeObject, boolean enabled);
+
+ public static native int nativeSetFrameRate(
+ long nativeObject, float frameRate, int compatibility, int changeFrameRateStrategy);
+
+ private SurfaceNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/SweepGradientNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/SweepGradientNatives.java
new file mode 100644
index 000000000..85d5a2d82
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/SweepGradientNatives.java
@@ -0,0 +1,20 @@
+package org.robolectric.nativeruntime;
+
+/**
+ * Native methods for SweepGradient JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/SweepGradient.java
+ */
+public class SweepGradientNatives {
+
+ public static native long nativeCreate(
+ long matrix, float x, float y, long[] colors, float[] positions, long colorSpaceHandle);
+
+ public static native long nativeCreate1(
+ long matrix, float x, float y, int[] colors, float[] positions);
+
+ public static native long nativeCreate2(long matrix, float x, float y, int color0, int color1);
+
+ private SweepGradientNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/TableMaskFilterNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/TableMaskFilterNatives.java
new file mode 100644
index 000000000..ca7f4f096
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/TableMaskFilterNatives.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2006 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.nativeruntime;
+
+/**
+ * Native methods for TableMaskFilter JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/TableMaskFilter.java
+ */
+public final class TableMaskFilterNatives {
+
+ public static native long nativeNewTable(byte[] table);
+
+ public static native long nativeNewClip(int min, int max);
+
+ public static native long nativeNewGamma(float gamma);
+
+ private TableMaskFilterNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/TypefaceNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/TypefaceNatives.java
new file mode 100644
index 000000000..204d89a62
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/TypefaceNatives.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2022 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.nativeruntime;
+
+import android.graphics.Typeface;
+import android.graphics.fonts.FontVariationAxis;
+import java.nio.ByteBuffer;
+import java.util.List;
+
+/**
+ * Native methods for Typeface JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/Typeface.java
+ */
+public final class TypefaceNatives {
+
+ public static native long nativeCreateFromTypeface(long nativeInstance, int style);
+
+ public static native long nativeCreateFromTypefaceWithExactStyle(
+ long nativeInstance, int weight, boolean italic);
+
+ public static native long nativeCreateFromTypefaceWithVariation(
+ long nativeInstance, List<FontVariationAxis> axes);
+
+ public static native long nativeCreateWeightAlias(long nativeInstance, int weight);
+
+ public static native long nativeCreateFromArray(
+ long[] familyArray, long fallbackTypeface, int weight, int italic);
+
+ public static native int[] nativeGetSupportedAxes(long nativeInstance);
+
+ public static native void nativeSetDefault(long nativePtr);
+
+ public static native int nativeGetStyle(long nativePtr);
+
+ public static native int nativeGetWeight(long nativePtr);
+
+ public static native long nativeGetReleaseFunc();
+
+ public static native int nativeGetFamilySize(long naitvePtr);
+
+ public static native long nativeGetFamily(long nativePtr, int index);
+
+ public static native void nativeRegisterGenericFamily(String str, long nativePtr);
+
+ public static native int nativeWriteTypefaces(ByteBuffer buffer, long[] nativePtrs);
+
+ public static native long[] nativeReadTypefaces(ByteBuffer buffer);
+
+ public static native void nativeForceSetStaticFinalField(String fieldName, Typeface typeface);
+
+ public static native void nativeAddFontCollections(long nativePtr);
+
+ public static native void nativeWarmUpCache(String fileName);
+
+ private TypefaceNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/VectorDrawableNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/VectorDrawableNatives.java
new file mode 100644
index 000000000..39c054244
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/VectorDrawableNatives.java
@@ -0,0 +1,150 @@
+package org.robolectric.nativeruntime;
+
+import android.graphics.Rect;
+
+/**
+ * Native methods for VectorDrawable JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/VectorDrawable.java
+ */
+public final class VectorDrawableNatives {
+
+ public static native int nDraw(
+ long rendererPtr,
+ long canvasWrapperPtr,
+ long colorFilterPtr,
+ Rect bounds,
+ boolean needsMirroring,
+ boolean canReuseCache);
+
+ public static native boolean nGetFullPathProperties(long pathPtr, byte[] properties, int length);
+
+ public static native void nSetName(long nodePtr, String name);
+
+ public static native boolean nGetGroupProperties(long groupPtr, float[] properties, int length);
+
+ public static native void nSetPathString(long pathPtr, String pathString, int length);
+
+ public static native long nCreateTree(long rootGroupPtr);
+
+ public static native long nCreateTreeFromCopy(long treeToCopy, long rootGroupPtr);
+
+ public static native void nSetRendererViewportSize(
+ long rendererPtr, float viewportWidth, float viewportHeight);
+
+ public static native boolean nSetRootAlpha(long rendererPtr, float alpha);
+
+ public static native float nGetRootAlpha(long rendererPtr);
+
+ public static native void nSetAntiAlias(long rendererPtr, boolean aa);
+
+ public static native void nSetAllowCaching(long rendererPtr, boolean allowCaching);
+
+ public static native long nCreateFullPath();
+
+ public static native long nCreateFullPath(long nativeFullPathPtr);
+
+ public static native void nUpdateFullPathProperties(
+ long pathPtr,
+ float strokeWidth,
+ int strokeColor,
+ float strokeAlpha,
+ int fillColor,
+ float fillAlpha,
+ float trimPathStart,
+ float trimPathEnd,
+ float trimPathOffset,
+ float strokeMiterLimit,
+ int strokeLineCap,
+ int strokeLineJoin,
+ int fillType);
+
+ public static native void nUpdateFullPathFillGradient(long pathPtr, long fillGradientPtr);
+
+ public static native void nUpdateFullPathStrokeGradient(long pathPtr, long strokeGradientPtr);
+
+ public static native long nCreateClipPath();
+
+ public static native long nCreateClipPath(long clipPathPtr);
+
+ public static native long nCreateGroup();
+
+ public static native long nCreateGroup(long groupPtr);
+
+ public static native void nUpdateGroupProperties(
+ long groupPtr,
+ float rotate,
+ float pivotX,
+ float pivotY,
+ float scaleX,
+ float scaleY,
+ float translateX,
+ float translateY);
+
+ public static native void nAddChild(long groupPtr, long nodePtr);
+
+ public static native float nGetRotation(long groupPtr);
+
+ public static native void nSetRotation(long groupPtr, float rotation);
+
+ public static native float nGetPivotX(long groupPtr);
+
+ public static native void nSetPivotX(long groupPtr, float pivotX);
+
+ public static native float nGetPivotY(long groupPtr);
+
+ public static native void nSetPivotY(long groupPtr, float pivotY);
+
+ public static native float nGetScaleX(long groupPtr);
+
+ public static native void nSetScaleX(long groupPtr, float scaleX);
+
+ public static native float nGetScaleY(long groupPtr);
+
+ public static native void nSetScaleY(long groupPtr, float scaleY);
+
+ public static native float nGetTranslateX(long groupPtr);
+
+ public static native void nSetTranslateX(long groupPtr, float translateX);
+
+ public static native float nGetTranslateY(long groupPtr);
+
+ public static native void nSetTranslateY(long groupPtr, float translateY);
+
+ public static native void nSetPathData(long pathPtr, long pathDataPtr);
+
+ public static native float nGetStrokeWidth(long pathPtr);
+
+ public static native void nSetStrokeWidth(long pathPtr, float width);
+
+ public static native int nGetStrokeColor(long pathPtr);
+
+ public static native void nSetStrokeColor(long pathPtr, int strokeColor);
+
+ public static native float nGetStrokeAlpha(long pathPtr);
+
+ public static native void nSetStrokeAlpha(long pathPtr, float alpha);
+
+ public static native int nGetFillColor(long pathPtr);
+
+ public static native void nSetFillColor(long pathPtr, int fillColor);
+
+ public static native float nGetFillAlpha(long pathPtr);
+
+ public static native void nSetFillAlpha(long pathPtr, float fillAlpha);
+
+ public static native float nGetTrimPathStart(long pathPtr);
+
+ public static native void nSetTrimPathStart(long pathPtr, float trimPathStart);
+
+ public static native float nGetTrimPathEnd(long pathPtr);
+
+ public static native void nSetTrimPathEnd(long pathPtr, float trimPathEnd);
+
+ public static native float nGetTrimPathOffset(long pathPtr);
+
+ public static native void nSetTrimPathOffset(long pathPtr, float trimPathOffset);
+
+ private VectorDrawableNatives() {}
+}
diff --git a/nativeruntime/src/main/java/org/robolectric/nativeruntime/VirtualRefBasePtrNatives.java b/nativeruntime/src/main/java/org/robolectric/nativeruntime/VirtualRefBasePtrNatives.java
new file mode 100644
index 000000000..0c96f08e3
--- /dev/null
+++ b/nativeruntime/src/main/java/org/robolectric/nativeruntime/VirtualRefBasePtrNatives.java
@@ -0,0 +1,16 @@
+package org.robolectric.nativeruntime;
+
+/**
+ * Native methods for VirtualRefBasePtr JNI registration.
+ *
+ * <p>Native method signatures are derived from
+ * https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/graphics/java/android/graphics/VirtualRefBasePtr.java
+ */
+public final class VirtualRefBasePtrNatives {
+
+ public static native void nIncStrong(long ptr);
+
+ public static native void nDecStrong(long ptr);
+
+ private VirtualRefBasePtrNatives() {}
+}
diff --git a/nativeruntime/src/main/resources/fonts/AndroidClock.ttf b/nativeruntime/src/main/resources/fonts/AndroidClock.ttf
new file mode 100644
index 000000000..a955442ba
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/AndroidClock.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/CarroisGothicSC-Regular.ttf b/nativeruntime/src/main/resources/fonts/CarroisGothicSC-Regular.ttf
new file mode 100644
index 000000000..d0281c739
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/CarroisGothicSC-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/ComingSoon.ttf b/nativeruntime/src/main/resources/fonts/ComingSoon.ttf
new file mode 100644
index 000000000..62a5a0de7
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/ComingSoon.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/CutiveMono.ttf b/nativeruntime/src/main/resources/fonts/CutiveMono.ttf
new file mode 100644
index 000000000..efe0f334e
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/CutiveMono.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/DancingScript-Bold.ttf b/nativeruntime/src/main/resources/fonts/DancingScript-Bold.ttf
new file mode 100644
index 000000000..d502ef871
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/DancingScript-Bold.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/DancingScript-Regular.ttf b/nativeruntime/src/main/resources/fonts/DancingScript-Regular.ttf
new file mode 100644
index 000000000..3fa27af34
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/DancingScript-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/DroidSans-Bold.ttf b/nativeruntime/src/main/resources/fonts/DroidSans-Bold.ttf
new file mode 100644
index 000000000..adf2aede6
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/DroidSans-Bold.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/DroidSans.ttf b/nativeruntime/src/main/resources/fonts/DroidSans.ttf
new file mode 100644
index 000000000..adf2aede6
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/DroidSans.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/DroidSansMono.ttf b/nativeruntime/src/main/resources/fonts/DroidSansMono.ttf
new file mode 100644
index 000000000..b7bf5b4aa
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/DroidSansMono.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoColorEmoji.ttf b/nativeruntime/src/main/resources/fonts/NotoColorEmoji.ttf
new file mode 100644
index 000000000..a746ca0d4
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoColorEmoji.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoColorEmojiFlags.ttf b/nativeruntime/src/main/resources/fonts/NotoColorEmojiFlags.ttf
new file mode 100644
index 000000000..b5120cdd1
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoColorEmojiFlags.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoColorEmojiLegacy.ttf b/nativeruntime/src/main/resources/fonts/NotoColorEmojiLegacy.ttf
new file mode 100644
index 000000000..e037903ae
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoColorEmojiLegacy.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoNaskhArabic-Bold.ttf b/nativeruntime/src/main/resources/fonts/NotoNaskhArabic-Bold.ttf
new file mode 100644
index 000000000..ca75a3e48
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoNaskhArabic-Bold.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoNaskhArabic-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoNaskhArabic-Regular.ttf
new file mode 100644
index 000000000..cac2af239
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoNaskhArabic-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoNaskhArabicUI-Bold.ttf b/nativeruntime/src/main/resources/fonts/NotoNaskhArabicUI-Bold.ttf
new file mode 100644
index 000000000..f51061077
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoNaskhArabicUI-Bold.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoNaskhArabicUI-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoNaskhArabicUI-Regular.ttf
new file mode 100644
index 000000000..138b3751a
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoNaskhArabicUI-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansAdlam-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansAdlam-VF.ttf
new file mode 100644
index 000000000..11207b096
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansAdlam-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansAhom-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansAhom-Regular.otf
new file mode 100644
index 000000000..2edf45869
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansAhom-Regular.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansAnatolianHieroglyphs-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansAnatolianHieroglyphs-Regular.otf
new file mode 100644
index 000000000..7c1e87aee
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansAnatolianHieroglyphs-Regular.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansArmenian-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansArmenian-VF.ttf
new file mode 100644
index 000000000..769660a8c
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansArmenian-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansAvestan-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansAvestan-Regular.ttf
new file mode 100644
index 000000000..31eeb5aa7
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansAvestan-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansBalinese-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansBalinese-Regular.ttf
new file mode 100644
index 000000000..a15509bb9
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansBalinese-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansBamum-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansBamum-Regular.ttf
new file mode 100644
index 000000000..f2de18714
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansBamum-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansBassaVah-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansBassaVah-Regular.otf
new file mode 100644
index 000000000..0b7b7b820
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansBassaVah-Regular.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansBatak-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansBatak-Regular.ttf
new file mode 100644
index 000000000..882ad961b
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansBatak-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansBengali-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansBengali-VF.ttf
new file mode 100644
index 000000000..a03477940
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansBengali-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansBengaliUI-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansBengaliUI-VF.ttf
new file mode 100644
index 000000000..a2bd0c344
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansBengaliUI-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansBhaiksuki-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansBhaiksuki-Regular.otf
new file mode 100644
index 000000000..dcf386217
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansBhaiksuki-Regular.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansBrahmi-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansBrahmi-Regular.ttf
new file mode 100644
index 000000000..eb98a6557
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansBrahmi-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansBuginese-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansBuginese-Regular.ttf
new file mode 100644
index 000000000..fd59da691
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansBuginese-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansBuhid-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansBuhid-Regular.ttf
new file mode 100644
index 000000000..756218ffa
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansBuhid-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansCJK-Regular.ttc b/nativeruntime/src/main/resources/fonts/NotoSansCJK-Regular.ttc
new file mode 100644
index 000000000..31ab08455
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansCJK-Regular.ttc
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansCanadianAboriginal-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansCanadianAboriginal-Regular.ttf
new file mode 100644
index 000000000..c036d94eb
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansCanadianAboriginal-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansCarian-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansCarian-Regular.ttf
new file mode 100644
index 000000000..8abada192
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansCarian-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansChakma-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansChakma-Regular.otf
new file mode 100644
index 000000000..d9690ca6b
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansChakma-Regular.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansCham-Bold.ttf b/nativeruntime/src/main/resources/fonts/NotoSansCham-Bold.ttf
new file mode 100644
index 000000000..5f98099b1
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansCham-Bold.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansCham-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansCham-Regular.ttf
new file mode 100644
index 000000000..be897d445
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansCham-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansCherokee-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansCherokee-Regular.ttf
new file mode 100644
index 000000000..1dfef8bb7
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansCherokee-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansCoptic-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansCoptic-Regular.ttf
new file mode 100644
index 000000000..d327c7964
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansCoptic-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansCuneiform-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansCuneiform-Regular.ttf
new file mode 100644
index 000000000..37a4e9e1a
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansCuneiform-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansCypriot-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansCypriot-Regular.ttf
new file mode 100644
index 000000000..bc4083e07
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansCypriot-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansDeseret-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansDeseret-Regular.ttf
new file mode 100644
index 000000000..c043a5434
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansDeseret-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansDevanagari-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansDevanagari-VF.ttf
new file mode 100644
index 000000000..f871a48a2
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansDevanagari-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansDevanagariUI-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansDevanagariUI-VF.ttf
new file mode 100644
index 000000000..1c023cf5f
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansDevanagariUI-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansEgyptianHieroglyphs-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansEgyptianHieroglyphs-Regular.ttf
new file mode 100644
index 000000000..62ee89793
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansEgyptianHieroglyphs-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansElbasan-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansElbasan-Regular.otf
new file mode 100644
index 000000000..1c28397c9
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansElbasan-Regular.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansEthiopic-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansEthiopic-VF.ttf
new file mode 100644
index 000000000..e2f46a97c
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansEthiopic-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansGeorgian-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansGeorgian-VF.ttf
new file mode 100644
index 000000000..235d6af87
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansGeorgian-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansGlagolitic-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansGlagolitic-Regular.ttf
new file mode 100644
index 000000000..86237aa8a
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansGlagolitic-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansGothic-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansGothic-Regular.ttf
new file mode 100644
index 000000000..d653ee107
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansGothic-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansGrantha-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansGrantha-Regular.ttf
new file mode 100644
index 000000000..3039d7383
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansGrantha-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansGujarati-Bold.ttf b/nativeruntime/src/main/resources/fonts/NotoSansGujarati-Bold.ttf
new file mode 100644
index 000000000..7b6f05f2e
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansGujarati-Bold.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansGujarati-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansGujarati-Regular.ttf
new file mode 100644
index 000000000..7af1329de
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansGujarati-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansGujaratiUI-Bold.ttf b/nativeruntime/src/main/resources/fonts/NotoSansGujaratiUI-Bold.ttf
new file mode 100644
index 000000000..d60a3483d
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansGujaratiUI-Bold.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansGujaratiUI-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansGujaratiUI-Regular.ttf
new file mode 100644
index 000000000..1bb407d86
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansGujaratiUI-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansGunjalaGondi-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansGunjalaGondi-Regular.otf
new file mode 100644
index 000000000..4cc0fa3af
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansGunjalaGondi-Regular.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansGurmukhi-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansGurmukhi-VF.ttf
new file mode 100644
index 000000000..ba48e40e2
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansGurmukhi-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansGurmukhiUI-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansGurmukhiUI-VF.ttf
new file mode 100644
index 000000000..930289fed
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansGurmukhiUI-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansHanifiRohingya-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansHanifiRohingya-Regular.otf
new file mode 100644
index 000000000..f3dc231c3
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansHanifiRohingya-Regular.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansHanunoo-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansHanunoo-Regular.ttf
new file mode 100644
index 000000000..480af3494
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansHanunoo-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansHatran-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansHatran-Regular.otf
new file mode 100644
index 000000000..125909571
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansHatran-Regular.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansHebrew-Bold.ttf b/nativeruntime/src/main/resources/fonts/NotoSansHebrew-Bold.ttf
new file mode 100644
index 000000000..64844fec2
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansHebrew-Bold.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansHebrew-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansHebrew-Regular.ttf
new file mode 100644
index 000000000..c161ce529
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansHebrew-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansImperialAramaic-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansImperialAramaic-Regular.ttf
new file mode 100644
index 000000000..10802e324
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansImperialAramaic-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansInscriptionalPahlavi-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansInscriptionalPahlavi-Regular.ttf
new file mode 100644
index 000000000..44d529650
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansInscriptionalPahlavi-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansInscriptionalParthian-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansInscriptionalParthian-Regular.ttf
new file mode 100644
index 000000000..bca05aa7d
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansInscriptionalParthian-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansJavanese-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansJavanese-Regular.otf
new file mode 100644
index 000000000..e1aa66580
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansJavanese-Regular.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansKaithi-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansKaithi-Regular.ttf
new file mode 100644
index 000000000..22b8c4761
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansKaithi-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansKannada-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansKannada-VF.ttf
new file mode 100644
index 000000000..7447a8965
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansKannada-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansKannadaUI-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansKannadaUI-VF.ttf
new file mode 100644
index 000000000..30b730136
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansKannadaUI-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansKayahLi-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansKayahLi-Regular.ttf
new file mode 100644
index 000000000..75b6b8013
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansKayahLi-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansKharoshthi-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansKharoshthi-Regular.ttf
new file mode 100644
index 000000000..1f4a9a667
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansKharoshthi-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansKhmer-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansKhmer-VF.ttf
new file mode 100644
index 000000000..1ba1f1d78
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansKhmer-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansKhmerUI-Bold.ttf b/nativeruntime/src/main/resources/fonts/NotoSansKhmerUI-Bold.ttf
new file mode 100644
index 000000000..592414c3f
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansKhmerUI-Bold.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansKhmerUI-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansKhmerUI-Regular.ttf
new file mode 100644
index 000000000..7a11a8153
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansKhmerUI-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansKhojki-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansKhojki-Regular.otf
new file mode 100644
index 000000000..99146ac5f
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansKhojki-Regular.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansLao-Bold.ttf b/nativeruntime/src/main/resources/fonts/NotoSansLao-Bold.ttf
new file mode 100644
index 000000000..4f50a70e6
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansLao-Bold.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansLao-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansLao-Regular.ttf
new file mode 100644
index 000000000..ff6c6a443
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansLao-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansLaoUI-Bold.ttf b/nativeruntime/src/main/resources/fonts/NotoSansLaoUI-Bold.ttf
new file mode 100644
index 000000000..4ca61cf23
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansLaoUI-Bold.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansLaoUI-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansLaoUI-Regular.ttf
new file mode 100644
index 000000000..61d755df3
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansLaoUI-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansLepcha-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansLepcha-Regular.ttf
new file mode 100644
index 000000000..b86dc5982
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansLepcha-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansLimbu-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansLimbu-Regular.ttf
new file mode 100644
index 000000000..b9eca0839
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansLimbu-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansLinearA-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansLinearA-Regular.otf
new file mode 100644
index 000000000..0f791af41
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansLinearA-Regular.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansLinearB-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansLinearB-Regular.ttf
new file mode 100644
index 000000000..b7415078e
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansLinearB-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansLisu-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansLisu-Regular.ttf
new file mode 100644
index 000000000..12405b4a5
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansLisu-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansLycian-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansLycian-Regular.ttf
new file mode 100644
index 000000000..30310c20e
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansLycian-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansLydian-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansLydian-Regular.ttf
new file mode 100644
index 000000000..fc5bf5fd1
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansLydian-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansMalayalam-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansMalayalam-VF.ttf
new file mode 100644
index 000000000..8221617a5
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansMalayalam-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansMalayalamUI-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansMalayalamUI-VF.ttf
new file mode 100644
index 000000000..cf38b96f7
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansMalayalamUI-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansMandaic-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansMandaic-Regular.ttf
new file mode 100644
index 000000000..1a533e1b3
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansMandaic-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansManichaean-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansManichaean-Regular.otf
new file mode 100644
index 000000000..8065acb03
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansManichaean-Regular.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansMarchen-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansMarchen-Regular.otf
new file mode 100644
index 000000000..983e62281
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansMarchen-Regular.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansMasaramGondi-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansMasaramGondi-Regular.otf
new file mode 100644
index 000000000..8833d019d
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansMasaramGondi-Regular.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansMedefaidrin-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansMedefaidrin-VF.ttf
new file mode 100644
index 000000000..7dda52d3b
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansMedefaidrin-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansMeeteiMayek-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansMeeteiMayek-Regular.ttf
new file mode 100644
index 000000000..3059a6c3a
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansMeeteiMayek-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansMeroitic-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansMeroitic-Regular.otf
new file mode 100644
index 000000000..e0f70d55f
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansMeroitic-Regular.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansMiao-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansMiao-Regular.otf
new file mode 100644
index 000000000..2facdee8b
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansMiao-Regular.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansModi-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansModi-Regular.ttf
new file mode 100644
index 000000000..dd4e340b6
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansModi-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansMongolian-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansMongolian-Regular.ttf
new file mode 100644
index 000000000..760d7e0d3
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansMongolian-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansMro-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansMro-Regular.otf
new file mode 100644
index 000000000..78f715b51
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansMro-Regular.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansMultani-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansMultani-Regular.otf
new file mode 100644
index 000000000..7804a00e7
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansMultani-Regular.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansMyanmar-Bold.otf b/nativeruntime/src/main/resources/fonts/NotoSansMyanmar-Bold.otf
new file mode 100644
index 000000000..ddec24133
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansMyanmar-Bold.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansMyanmar-Medium.otf b/nativeruntime/src/main/resources/fonts/NotoSansMyanmar-Medium.otf
new file mode 100644
index 000000000..c6c03db26
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansMyanmar-Medium.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansMyanmar-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansMyanmar-Regular.otf
new file mode 100644
index 000000000..f2f7a6764
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansMyanmar-Regular.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansMyanmarUI-Bold.otf b/nativeruntime/src/main/resources/fonts/NotoSansMyanmarUI-Bold.otf
new file mode 100644
index 000000000..6742efd9a
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansMyanmarUI-Bold.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansMyanmarUI-Medium.otf b/nativeruntime/src/main/resources/fonts/NotoSansMyanmarUI-Medium.otf
new file mode 100644
index 000000000..af579deaa
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansMyanmarUI-Medium.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansMyanmarUI-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansMyanmarUI-Regular.otf
new file mode 100644
index 000000000..7d912b84b
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansMyanmarUI-Regular.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansNKo-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansNKo-Regular.ttf
new file mode 100644
index 000000000..091416be4
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansNKo-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansNabataean-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansNabataean-Regular.otf
new file mode 100644
index 000000000..a757da5a0
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansNabataean-Regular.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansNewTaiLue-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansNewTaiLue-Regular.ttf
new file mode 100644
index 000000000..bc79a7930
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansNewTaiLue-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansNewa-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansNewa-Regular.otf
new file mode 100644
index 000000000..4728d2e04
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansNewa-Regular.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansOgham-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansOgham-Regular.ttf
new file mode 100644
index 000000000..8943189c5
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansOgham-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansOlChiki-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansOlChiki-Regular.ttf
new file mode 100644
index 000000000..332664043
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansOlChiki-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansOldItalic-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansOldItalic-Regular.ttf
new file mode 100644
index 000000000..4a63a5e72
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansOldItalic-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansOldNorthArabian-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansOldNorthArabian-Regular.otf
new file mode 100644
index 000000000..f884ae06a
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansOldNorthArabian-Regular.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansOldPermic-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansOldPermic-Regular.otf
new file mode 100644
index 000000000..5a0e71e62
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansOldPermic-Regular.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansOldPersian-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansOldPersian-Regular.ttf
new file mode 100644
index 000000000..21166f443
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansOldPersian-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansOldSouthArabian-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansOldSouthArabian-Regular.ttf
new file mode 100644
index 000000000..5037bd996
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansOldSouthArabian-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansOldTurkic-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansOldTurkic-Regular.ttf
new file mode 100644
index 000000000..1652ad157
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansOldTurkic-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansOriya-Bold.ttf b/nativeruntime/src/main/resources/fonts/NotoSansOriya-Bold.ttf
new file mode 100644
index 000000000..ab6e759a5
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansOriya-Bold.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansOriya-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansOriya-Regular.ttf
new file mode 100644
index 000000000..92abe640c
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansOriya-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansOriyaUI-Bold.ttf b/nativeruntime/src/main/resources/fonts/NotoSansOriyaUI-Bold.ttf
new file mode 100644
index 000000000..1b32205eb
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansOriyaUI-Bold.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansOriyaUI-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansOriyaUI-Regular.ttf
new file mode 100644
index 000000000..5738e1d55
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansOriyaUI-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansOsage-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansOsage-Regular.ttf
new file mode 100644
index 000000000..28f1d9403
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansOsage-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansOsmanya-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansOsmanya-Regular.ttf
new file mode 100644
index 000000000..dd0b982db
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansOsmanya-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansPahawhHmong-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansPahawhHmong-Regular.otf
new file mode 100644
index 000000000..26a7440ee
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansPahawhHmong-Regular.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansPalmyrene-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansPalmyrene-Regular.otf
new file mode 100644
index 000000000..3ccbf6f35
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansPalmyrene-Regular.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansPauCinHau-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansPauCinHau-Regular.otf
new file mode 100644
index 000000000..a5c7d921d
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansPauCinHau-Regular.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansPhagsPa-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansPhagsPa-Regular.ttf
new file mode 100644
index 000000000..bd59e328f
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansPhagsPa-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansPhoenician-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansPhoenician-Regular.ttf
new file mode 100644
index 000000000..345a9770a
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansPhoenician-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansRejang-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansRejang-Regular.ttf
new file mode 100644
index 000000000..3d95fd3d9
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansRejang-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansRunic-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansRunic-Regular.ttf
new file mode 100644
index 000000000..64a18b17d
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansRunic-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansSamaritan-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansSamaritan-Regular.ttf
new file mode 100644
index 000000000..9fe427c84
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansSamaritan-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansSaurashtra-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansSaurashtra-Regular.ttf
new file mode 100644
index 000000000..6ca7590ae
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansSaurashtra-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansSharada-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansSharada-Regular.otf
new file mode 100644
index 000000000..2c865a21f
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansSharada-Regular.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansShavian-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansShavian-Regular.ttf
new file mode 100644
index 000000000..265c5231b
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansShavian-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansSinhala-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansSinhala-VF.ttf
new file mode 100644
index 000000000..cee6ff2e6
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansSinhala-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansSinhalaUI-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansSinhalaUI-VF.ttf
new file mode 100644
index 000000000..681e12104
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansSinhalaUI-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansSoraSompeng-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansSoraSompeng-Regular.otf
new file mode 100644
index 000000000..1240506da
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansSoraSompeng-Regular.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansSoyombo-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansSoyombo-VF.ttf
new file mode 100644
index 000000000..cba7dc0dc
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansSoyombo-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansSundanese-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansSundanese-Regular.ttf
new file mode 100644
index 000000000..5c19cd703
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansSundanese-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansSylotiNagri-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansSylotiNagri-Regular.ttf
new file mode 100644
index 000000000..164c217fa
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansSylotiNagri-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansSymbols-Regular-Subsetted.ttf b/nativeruntime/src/main/resources/fonts/NotoSansSymbols-Regular-Subsetted.ttf
new file mode 100644
index 000000000..b98dcccf4
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansSymbols-Regular-Subsetted.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansSymbols-Regular-Subsetted2.ttf b/nativeruntime/src/main/resources/fonts/NotoSansSymbols-Regular-Subsetted2.ttf
new file mode 100644
index 000000000..1b5d2a36a
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansSymbols-Regular-Subsetted2.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansSyriacEastern-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansSyriacEastern-Regular.ttf
new file mode 100644
index 000000000..25ee338b9
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansSyriacEastern-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansSyriacEstrangela-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansSyriacEstrangela-Regular.ttf
new file mode 100644
index 000000000..07a2e043e
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansSyriacEstrangela-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansSyriacWestern-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansSyriacWestern-Regular.ttf
new file mode 100644
index 000000000..f0f9de146
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansSyriacWestern-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansTagalog-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansTagalog-Regular.ttf
new file mode 100644
index 000000000..0aff41104
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansTagalog-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansTagbanwa-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansTagbanwa-Regular.ttf
new file mode 100644
index 000000000..286cf4c19
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansTagbanwa-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansTaiLe-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansTaiLe-Regular.ttf
new file mode 100644
index 000000000..88ea3fd76
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansTaiLe-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansTaiTham-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansTaiTham-Regular.ttf
new file mode 100644
index 000000000..faa8ddf6c
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansTaiTham-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansTaiViet-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansTaiViet-Regular.ttf
new file mode 100644
index 000000000..9208fc776
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansTaiViet-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansTakri-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansTakri-VF.ttf
new file mode 100644
index 000000000..73f5fbeb8
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansTakri-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansTamil-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansTamil-VF.ttf
new file mode 100644
index 000000000..da4ee1c78
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansTamil-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansTamilUI-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansTamilUI-VF.ttf
new file mode 100644
index 000000000..3cd3c8ecb
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansTamilUI-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansTelugu-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansTelugu-VF.ttf
new file mode 100644
index 000000000..591b80db0
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansTelugu-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansTeluguUI-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansTeluguUI-VF.ttf
new file mode 100644
index 000000000..b3385e70d
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansTeluguUI-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansThaana-Bold.ttf b/nativeruntime/src/main/resources/fonts/NotoSansThaana-Bold.ttf
new file mode 100644
index 000000000..986195695
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansThaana-Bold.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansThaana-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansThaana-Regular.ttf
new file mode 100644
index 000000000..8fcf561a5
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansThaana-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansThai-Bold.ttf b/nativeruntime/src/main/resources/fonts/NotoSansThai-Bold.ttf
new file mode 100644
index 000000000..3b5ed2875
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansThai-Bold.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansThai-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansThai-Regular.ttf
new file mode 100644
index 000000000..552c35617
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansThai-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansThaiUI-Bold.ttf b/nativeruntime/src/main/resources/fonts/NotoSansThaiUI-Bold.ttf
new file mode 100644
index 000000000..3bc9a7051
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansThaiUI-Bold.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansThaiUI-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansThaiUI-Regular.ttf
new file mode 100644
index 000000000..9fe222d4e
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansThaiUI-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansTifinagh-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansTifinagh-Regular.otf
new file mode 100644
index 000000000..c8b87c8ee
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansTifinagh-Regular.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansUgaritic-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansUgaritic-Regular.ttf
new file mode 100644
index 000000000..dec137d5e
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansUgaritic-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansVai-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansVai-Regular.ttf
new file mode 100644
index 000000000..3ec5fccfa
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansVai-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansWancho-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansWancho-Regular.otf
new file mode 100644
index 000000000..af941de19
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansWancho-Regular.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansWarangCiti-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansWarangCiti-Regular.otf
new file mode 100644
index 000000000..2a3b78a99
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansWarangCiti-Regular.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSansYi-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansYi-Regular.ttf
new file mode 100644
index 000000000..5bd81f9cb
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSansYi-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSerif-Bold.ttf b/nativeruntime/src/main/resources/fonts/NotoSerif-Bold.ttf
new file mode 100644
index 000000000..2ffdf8c1c
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSerif-Bold.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSerif-BoldItalic.ttf b/nativeruntime/src/main/resources/fonts/NotoSerif-BoldItalic.ttf
new file mode 100644
index 000000000..4a317f818
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSerif-BoldItalic.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSerif-Italic.ttf b/nativeruntime/src/main/resources/fonts/NotoSerif-Italic.ttf
new file mode 100644
index 000000000..968b46bca
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSerif-Italic.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSerif-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSerif-Regular.ttf
new file mode 100644
index 000000000..9d9a347f6
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSerif-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifArmenian-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifArmenian-VF.ttf
new file mode 100644
index 000000000..5525d52d7
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSerifArmenian-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifBengali-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifBengali-VF.ttf
new file mode 100644
index 000000000..24c0774ba
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSerifBengali-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifCJK-Regular.ttc b/nativeruntime/src/main/resources/fonts/NotoSerifCJK-Regular.ttc
new file mode 100644
index 000000000..2820fc99b
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSerifCJK-Regular.ttc
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifDevanagari-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifDevanagari-VF.ttf
new file mode 100644
index 000000000..bf013a7d6
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSerifDevanagari-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifDogra-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifDogra-Regular.ttf
new file mode 100644
index 000000000..ca5eba924
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSerifDogra-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifEthiopic-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifEthiopic-VF.ttf
new file mode 100644
index 000000000..10b5e2698
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSerifEthiopic-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifGeorgian-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifGeorgian-VF.ttf
new file mode 100644
index 000000000..afc8e9c1f
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSerifGeorgian-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifGujarati-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifGujarati-VF.ttf
new file mode 100644
index 000000000..586150185
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSerifGujarati-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifGurmukhi-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifGurmukhi-VF.ttf
new file mode 100644
index 000000000..6c104cc09
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSerifGurmukhi-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifHebrew-Bold.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifHebrew-Bold.ttf
new file mode 100644
index 000000000..cdec7467b
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSerifHebrew-Bold.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifHebrew-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifHebrew-Regular.ttf
new file mode 100644
index 000000000..8b69dde90
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSerifHebrew-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifKannada-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifKannada-VF.ttf
new file mode 100644
index 000000000..c523ad723
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSerifKannada-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifKhmer-Bold.otf b/nativeruntime/src/main/resources/fonts/NotoSerifKhmer-Bold.otf
new file mode 100644
index 000000000..47294159e
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSerifKhmer-Bold.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifKhmer-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSerifKhmer-Regular.otf
new file mode 100644
index 000000000..204e3432a
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSerifKhmer-Regular.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifLao-Bold.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifLao-Bold.ttf
new file mode 100644
index 000000000..1a2b775af
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSerifLao-Bold.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifLao-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifLao-Regular.ttf
new file mode 100644
index 000000000..d6a4ae25d
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSerifLao-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifMalayalam-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifMalayalam-VF.ttf
new file mode 100644
index 000000000..e270f5d73
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSerifMalayalam-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifMyanmar-Bold.otf b/nativeruntime/src/main/resources/fonts/NotoSerifMyanmar-Bold.otf
new file mode 100644
index 000000000..ba21a2798
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSerifMyanmar-Bold.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifMyanmar-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSerifMyanmar-Regular.otf
new file mode 100644
index 000000000..ec4ed4438
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSerifMyanmar-Regular.otf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifNyiakengPuachueHmong-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifNyiakengPuachueHmong-VF.ttf
new file mode 100644
index 000000000..e87a20286
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSerifNyiakengPuachueHmong-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifSinhala-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifSinhala-VF.ttf
new file mode 100644
index 000000000..9109b833e
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSerifSinhala-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifTamil-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifTamil-VF.ttf
new file mode 100644
index 000000000..91acc2b7f
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSerifTamil-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifTelugu-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifTelugu-VF.ttf
new file mode 100644
index 000000000..16a187b10
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSerifTelugu-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifThai-Bold.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifThai-Bold.ttf
new file mode 100644
index 000000000..10d8afd45
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSerifThai-Bold.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifThai-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifThai-Regular.ttf
new file mode 100644
index 000000000..e4b1c14a9
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSerifThai-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifTibetan-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifTibetan-VF.ttf
new file mode 100644
index 000000000..60903833d
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSerifTibetan-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifYezidi-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifYezidi-VF.ttf
new file mode 100644
index 000000000..fabcbbb0f
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/NotoSerifYezidi-VF.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/Roboto-Regular.ttf b/nativeruntime/src/main/resources/fonts/Roboto-Regular.ttf
new file mode 100644
index 000000000..adf2aede6
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/Roboto-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/RobotoStatic-Regular.ttf b/nativeruntime/src/main/resources/fonts/RobotoStatic-Regular.ttf
new file mode 100644
index 000000000..b624812dc
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/RobotoStatic-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/SourceSansPro-Bold.ttf b/nativeruntime/src/main/resources/fonts/SourceSansPro-Bold.ttf
new file mode 100644
index 000000000..f6986468b
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/SourceSansPro-Bold.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/SourceSansPro-BoldItalic.ttf b/nativeruntime/src/main/resources/fonts/SourceSansPro-BoldItalic.ttf
new file mode 100644
index 000000000..5c00b64fa
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/SourceSansPro-BoldItalic.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/SourceSansPro-Italic.ttf b/nativeruntime/src/main/resources/fonts/SourceSansPro-Italic.ttf
new file mode 100644
index 000000000..82e876201
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/SourceSansPro-Italic.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/SourceSansPro-Regular.ttf b/nativeruntime/src/main/resources/fonts/SourceSansPro-Regular.ttf
new file mode 100644
index 000000000..278ad8aa0
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/SourceSansPro-Regular.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/SourceSansPro-SemiBold.ttf b/nativeruntime/src/main/resources/fonts/SourceSansPro-SemiBold.ttf
new file mode 100644
index 000000000..ac3e0d19a
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/SourceSansPro-SemiBold.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/SourceSansPro-SemiBoldItalic.ttf b/nativeruntime/src/main/resources/fonts/SourceSansPro-SemiBoldItalic.ttf
new file mode 100644
index 000000000..b0737bb31
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/SourceSansPro-SemiBoldItalic.ttf
Binary files differ
diff --git a/nativeruntime/src/main/resources/fonts/fonts.xml b/nativeruntime/src/main/resources/fonts/fonts.xml
new file mode 100644
index 000000000..8e2a59cd8
--- /dev/null
+++ b/nativeruntime/src/main/resources/fonts/fonts.xml
@@ -0,0 +1,1545 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ WARNING: Parsing of this file by third-party apps is not supported. The
+ file, and the font files it refers to, will be renamed and/or moved out
+ from their respective location in the next Android release, and/or the
+ format or syntax of the file may change significantly. If you parse this
+ file for information about system fonts, do it at your own risk. Your
+ application will almost certainly break with the next major Android
+ release.
+
+ In this file, all fonts without names are added to the default list.
+ Fonts are chosen based on a match: full BCP-47 language tag including
+ script, then just language, and finally order (the first font containing
+ the glyph).
+
+ Order of appearance is also the tiebreaker for weight matching. This is
+ the reason why the 900 weights of Roboto precede the 700 weights - we
+ prefer the former when an 800 weight is requested. Since bold spans
+ effectively add 300 to the weight, this ensures that 900 is the bold
+ paired with the 500 weight, ensuring adequate contrast.
+
+ TODO(rsheeter) update comment; ordering to match 800 to 900 is no longer required
+-->
+<familyset version="23">
+ <!-- first font is default -->
+ <family name="sans-serif">
+ <font weight="100" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="100" />
+ </font>
+ <font weight="200" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="200" />
+ </font>
+ <font weight="300" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="300" />
+ </font>
+ <font weight="400" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="400" />
+ </font>
+ <font weight="500" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="500" />
+ </font>
+ <font weight="600" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="600" />
+ </font>
+ <font weight="700" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="700" />
+ </font>
+ <font weight="800" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="800" />
+ </font>
+ <font weight="900" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="900" />
+ </font>
+ <font weight="100" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="100" />
+ </font>
+ <font weight="200" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="200" />
+ </font>
+ <font weight="300" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="300" />
+ </font>
+ <font weight="400" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="400" />
+ </font>
+ <font weight="500" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="500" />
+ </font>
+ <font weight="600" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="600" />
+ </font>
+ <font weight="700" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="700" />
+ </font>
+ <font weight="800" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="800" />
+ </font>
+ <font weight="900" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="900" />
+ </font>
+ </family>
+
+
+ <!-- Note that aliases must come after the fonts they reference. -->
+ <alias name="sans-serif-thin" to="sans-serif" weight="100" />
+ <alias name="sans-serif-light" to="sans-serif" weight="300" />
+ <alias name="sans-serif-medium" to="sans-serif" weight="500" />
+ <alias name="sans-serif-black" to="sans-serif" weight="900" />
+ <alias name="arial" to="sans-serif" />
+ <alias name="helvetica" to="sans-serif" />
+ <alias name="tahoma" to="sans-serif" />
+ <alias name="verdana" to="sans-serif" />
+
+ <family name="sans-serif-condensed">
+ <font weight="100" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="100" />
+ </font>
+ <font weight="200" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="200" />
+ </font>
+ <font weight="300" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="300" />
+ </font>
+ <font weight="400" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="400" />
+ </font>
+ <font weight="500" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="500" />
+ </font>
+ <font weight="600" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="600" />
+ </font>
+ <font weight="700" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="700" />
+ </font>
+ <font weight="800" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="800" />
+ </font>
+ <font weight="900" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="900" />
+ </font>
+ <font weight="100" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="100" />
+ </font>
+ <font weight="200" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="200" />
+ </font>
+ <font weight="300" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="300" />
+ </font>
+ <font weight="400" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="400" />
+ </font>
+ <font weight="500" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="500" />
+ </font>
+ <font weight="600" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="600" />
+ </font>
+ <font weight="700" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="700" />
+ </font>
+ <font weight="800" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="800" />
+ </font>
+ <font weight="900" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="900" />
+ </font>
+ </family>
+ <alias name="sans-serif-condensed-light" to="sans-serif-condensed" weight="300" />
+ <alias name="sans-serif-condensed-medium" to="sans-serif-condensed" weight="500" />
+
+ <family name="serif">
+ <font weight="400" style="normal" postScriptName="NotoSerif">NotoSerif-Regular.ttf</font>
+ <font weight="700" style="normal">NotoSerif-Bold.ttf</font>
+ <font weight="400" style="italic">NotoSerif-Italic.ttf</font>
+ <font weight="700" style="italic">NotoSerif-BoldItalic.ttf</font>
+ </family>
+ <alias name="serif-bold" to="serif" weight="700" />
+ <alias name="times" to="serif" />
+ <alias name="times new roman" to="serif" />
+ <alias name="palatino" to="serif" />
+ <alias name="georgia" to="serif" />
+ <alias name="baskerville" to="serif" />
+ <alias name="goudy" to="serif" />
+ <alias name="fantasy" to="serif" />
+ <alias name="ITC Stone Serif" to="serif" />
+
+ <family name="monospace">
+ <font weight="400" style="normal">DroidSansMono.ttf</font>
+ </family>
+ <alias name="sans-serif-monospace" to="monospace" />
+ <alias name="monaco" to="monospace" />
+
+ <family name="serif-monospace">
+ <font weight="400" style="normal" postScriptName="CutiveMono-Regular">CutiveMono.ttf</font>
+ </family>
+ <alias name="courier" to="serif-monospace" />
+ <alias name="courier new" to="serif-monospace" />
+
+ <family name="casual">
+ <font weight="400" style="normal">ComingSoon.ttf</font>
+ </family>
+
+ <family name="cursive">
+ <font weight="400" style="normal" postScriptName="DancingScript">DancingScript-Regular.ttf
+ </font>
+ <font weight="700" style="normal">DancingScript-Bold.ttf</font>
+ </family>
+
+ <family name="sans-serif-smallcaps">
+ <font weight="400" style="normal">CarroisGothicSC-Regular.ttf</font>
+ </family>
+
+ <family name="source-sans-pro">
+ <font weight="400" style="normal">SourceSansPro-Regular.ttf</font>
+ <font weight="400" style="italic">SourceSansPro-Italic.ttf</font>
+ <font weight="600" style="normal">SourceSansPro-SemiBold.ttf</font>
+ <font weight="600" style="italic">SourceSansPro-SemiBoldItalic.ttf</font>
+ <font weight="700" style="normal">SourceSansPro-Bold.ttf</font>
+ <font weight="700" style="italic">SourceSansPro-BoldItalic.ttf</font>
+ </family>
+ <alias name="source-sans-pro-semi-bold" to="source-sans-pro" weight="600"/>
+
+ <!-- fallback fonts -->
+ <family lang="und-Arab" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoNaskhArabic">
+ NotoNaskhArabic-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoNaskhArabic-Bold.ttf</font>
+ </family>
+ <family lang="und-Arab" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoNaskhArabicUI">
+ NotoNaskhArabicUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Ethi">
+ <font weight="400" style="normal" postScriptName="NotoSansEthiopic-Regular">
+ NotoSansEthiopic-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansEthiopic-Regular">
+ NotoSansEthiopic-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansEthiopic-Regular">
+ NotoSansEthiopic-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansEthiopic-Regular">
+ NotoSansEthiopic-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Hebr">
+ <font weight="400" style="normal" postScriptName="NotoSansHebrew">
+ NotoSansHebrew-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansHebrew-Bold.ttf</font>
+ <font weight="400" style="normal" fallbackFor="serif">NotoSerifHebrew-Regular.ttf</font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifHebrew-Bold.ttf</font>
+ </family>
+ <family lang="und-Thai" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansThai">NotoSansThai-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansThai-Bold.ttf</font>
+ <font weight="400" style="normal" fallbackFor="serif">
+ NotoSerifThai-Regular.ttf
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifThai-Bold.ttf</font>
+ </family>
+ <family lang="und-Thai" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansThaiUI">
+ NotoSansThaiUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansThaiUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Armn">
+ <font weight="400" style="normal" postScriptName="NotoSansArmenian-Regular">
+ NotoSansArmenian-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansArmenian-Regular">
+ NotoSansArmenian-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansArmenian-Regular">
+ NotoSansArmenian-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansArmenian-Regular">
+ NotoSansArmenian-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Geor,und-Geok">
+ <font weight="400" style="normal" postScriptName="NotoSansGeorgian-Regular">
+ NotoSansGeorgian-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansGeorgian-Regular">
+ NotoSansGeorgian-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansGeorgian-Regular">
+ NotoSansGeorgian-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansGeorgian-Regular">
+ NotoSansGeorgian-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Deva" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansDevanagari-Regular">
+ NotoSansDevanagari-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansDevanagari-Regular">
+ NotoSansDevanagari-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansDevanagari-Regular">
+ NotoSansDevanagari-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansDevanagari-Regular">
+ NotoSansDevanagari-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Deva" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansDevanagariUI-Regular">
+ NotoSansDevanagariUI-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansDevanagariUI-Regular">
+ NotoSansDevanagariUI-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansDevanagariUI-Regular">
+ NotoSansDevanagariUI-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansDevanagariUI-Regular">
+ NotoSansDevanagariUI-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+
+ <!-- All scripts of India should come after Devanagari, due to shared
+ danda characters.
+ -->
+ <family lang="und-Gujr" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansGujarati">
+ NotoSansGujarati-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansGujarati-Bold.ttf</font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Gujr" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansGujaratiUI">
+ NotoSansGujaratiUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansGujaratiUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Guru" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansGurmukhi-Regular">
+ NotoSansGurmukhi-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansGurmukhi-Regular">
+ NotoSansGurmukhi-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansGurmukhi-Regular">
+ NotoSansGurmukhi-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansGurmukhi-Regular">
+ NotoSansGurmukhi-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Guru" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansGurmukhiUI-Regular">
+ NotoSansGurmukhiUI-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansGurmukhiUI-Regular">
+ NotoSansGurmukhiUI-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansGurmukhiUI-Regular">
+ NotoSansGurmukhiUI-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansGurmukhiUI-Regular">
+ NotoSansGurmukhiUI-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Taml" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansTamil-Regular">
+ NotoSansTamil-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansTamil-Regular">
+ NotoSansTamil-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansTamil-Regular">
+ NotoSansTamil-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansTamil-Regular">
+ NotoSansTamil-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Taml" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansTamilUI-Regular">
+ NotoSansTamilUI-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansTamilUI-Regular">
+ NotoSansTamilUI-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansTamilUI-Regular">
+ NotoSansTamilUI-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansTamilUI-Regular">
+ NotoSansTamilUI-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Mlym" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansMalayalam-Regular">
+ NotoSansMalayalam-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansMalayalam-Regular">
+ NotoSansMalayalam-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansMalayalam-Regular">
+ NotoSansMalayalam-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansMalayalam-Regular">
+ NotoSansMalayalam-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Mlym" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansMalayalamUI-Regular">
+ NotoSansMalayalamUI-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansMalayalamUI-Regular">
+ NotoSansMalayalamUI-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansMalayalamUI-Regular">
+ NotoSansMalayalamUI-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansMalayalamUI-Regular">
+ NotoSansMalayalamUI-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Beng" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansBengali-Regular">
+ NotoSansBengali-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansBengali-Regular">
+ NotoSansBengali-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansBengali-Regular">
+ NotoSansBengali-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansBengali-Regular">
+ NotoSansBengali-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Beng" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansBengaliUI-Regular">
+ NotoSansBengaliUI-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansBengaliUI-Regular">
+ NotoSansBengaliUI-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansBengaliUI-Regular">
+ NotoSansBengaliUI-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansBengaliUI-Regular">
+ NotoSansBengaliUI-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Telu" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansTelugu-Regular">
+ NotoSansTelugu-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansTelugu-Regular">
+ NotoSansTelugu-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansTelugu-Regular">
+ NotoSansTelugu-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansTelugu-Regular">
+ NotoSansTelugu-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Telu" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansTeluguUI-Regular">
+ NotoSansTeluguUI-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansTeluguUI-Regular">
+ NotoSansTeluguUI-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansTeluguUI-Regular">
+ NotoSansTeluguUI-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansTeluguUI-Regular">
+ NotoSansTeluguUI-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Knda" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansKannada-Regular">
+ NotoSansKannada-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansKannada-Regular">
+ NotoSansKannada-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansKannada-Regular">
+ NotoSansKannada-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansKannada-Regular">
+ NotoSansKannada-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Knda" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansKannadaUI-Regular">
+ NotoSansKannadaUI-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansKannadaUI-Regular">
+ NotoSansKannadaUI-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansKannadaUI-Regular">
+ NotoSansKannadaUI-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansKannadaUI-Regular">
+ NotoSansKannadaUI-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Orya" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansOriya">NotoSansOriya-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansOriya-Bold.ttf</font>
+ </family>
+ <family lang="und-Orya" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansOriyaUI">
+ NotoSansOriyaUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansOriyaUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Sinh" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansSinhala-Regular">
+ NotoSansSinhala-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansSinhala-Regular">
+ NotoSansSinhala-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansSinhala-Regular">
+ NotoSansSinhala-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansSinhala-Regular">
+ NotoSansSinhala-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Sinh" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansSinhalaUI-Regular">
+ NotoSansSinhalaUI-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansSinhalaUI-Regular">
+ NotoSansSinhalaUI-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansSinhalaUI-Regular">
+ NotoSansSinhalaUI-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansSinhalaUI-Regular">
+ NotoSansSinhalaUI-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Khmr" variant="elegant">
+ <font weight="100" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="26.0"/>
+ </font>
+ <font weight="200" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="39.0"/>
+ </font>
+ <font weight="300" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="58.0"/>
+ </font>
+ <font weight="400" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="90.0"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="108.0"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="128.0"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="151.0"/>
+ </font>
+ <font weight="800" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="169.0"/>
+ </font>
+ <font weight="900" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="190.0"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif">NotoSerifKhmer-Regular.otf</font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifKhmer-Bold.otf</font>
+ </family>
+ <family lang="und-Khmr" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansKhmerUI">
+ NotoSansKhmerUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansKhmerUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Laoo" variant="elegant">
+ <font weight="400" style="normal">NotoSansLao-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansLao-Bold.ttf</font>
+ <font weight="400" style="normal" fallbackFor="serif">
+ NotoSerifLao-Regular.ttf
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifLao-Bold.ttf</font>
+ </family>
+ <family lang="und-Laoo" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansLaoUI">NotoSansLaoUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansLaoUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Mymr" variant="elegant">
+ <font weight="400" style="normal">NotoSansMyanmar-Regular.otf</font>
+ <font weight="500" style="normal">NotoSansMyanmar-Medium.otf</font>
+ <font weight="700" style="normal">NotoSansMyanmar-Bold.otf</font>
+ <font weight="400" style="normal" fallbackFor="serif">NotoSerifMyanmar-Regular.otf</font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifMyanmar-Bold.otf</font>
+ </family>
+ <family lang="und-Mymr" variant="compact">
+ <font weight="400" style="normal">NotoSansMyanmarUI-Regular.otf</font>
+ <font weight="500" style="normal">NotoSansMyanmarUI-Medium.otf</font>
+ <font weight="700" style="normal">NotoSansMyanmarUI-Bold.otf</font>
+ </family>
+ <family lang="und-Thaa">
+ <font weight="400" style="normal" postScriptName="NotoSansThaana">
+ NotoSansThaana-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansThaana-Bold.ttf</font>
+ </family>
+ <family lang="und-Cham">
+ <font weight="400" style="normal" postScriptName="NotoSansCham">NotoSansCham-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansCham-Bold.ttf</font>
+ </family>
+ <family lang="und-Ahom">
+ <font weight="400" style="normal">NotoSansAhom-Regular.otf</font>
+ </family>
+ <family lang="und-Adlm">
+ <font weight="400" style="normal" postScriptName="NotoSansAdlam-Regular">
+ NotoSansAdlam-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansAdlam-Regular">
+ NotoSansAdlam-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansAdlam-Regular">
+ NotoSansAdlam-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansAdlam-Regular">
+ NotoSansAdlam-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Avst">
+ <font weight="400" style="normal" postScriptName="NotoSansAvestan">
+ NotoSansAvestan-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Bali">
+ <font weight="400" style="normal" postScriptName="NotoSansBalinese">
+ NotoSansBalinese-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Bamu">
+ <font weight="400" style="normal" postScriptName="NotoSansBamum">NotoSansBamum-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Batk">
+ <font weight="400" style="normal" postScriptName="NotoSansBatak">NotoSansBatak-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Brah">
+ <font weight="400" style="normal" postScriptName="NotoSansBrahmi">
+ NotoSansBrahmi-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Bugi">
+ <font weight="400" style="normal" postScriptName="NotoSansBuginese">
+ NotoSansBuginese-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Buhd">
+ <font weight="400" style="normal" postScriptName="NotoSansBuhid">NotoSansBuhid-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Cans">
+ <font weight="400" style="normal">
+ NotoSansCanadianAboriginal-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Cari">
+ <font weight="400" style="normal" postScriptName="NotoSansCarian">
+ NotoSansCarian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Cakm">
+ <font weight="400" style="normal">NotoSansChakma-Regular.otf</font>
+ </family>
+ <family lang="und-Cher">
+ <font weight="400" style="normal">NotoSansCherokee-Regular.ttf</font>
+ </family>
+ <family lang="und-Copt">
+ <font weight="400" style="normal" postScriptName="NotoSansCoptic">
+ NotoSansCoptic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Xsux">
+ <font weight="400" style="normal" postScriptName="NotoSansCuneiform">
+ NotoSansCuneiform-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Cprt">
+ <font weight="400" style="normal" postScriptName="NotoSansCypriot">
+ NotoSansCypriot-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Dsrt">
+ <font weight="400" style="normal" postScriptName="NotoSansDeseret">
+ NotoSansDeseret-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Egyp">
+ <font weight="400" style="normal" postScriptName="NotoSansEgyptianHieroglyphs">
+ NotoSansEgyptianHieroglyphs-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Elba">
+ <font weight="400" style="normal">NotoSansElbasan-Regular.otf</font>
+ </family>
+ <family lang="und-Glag">
+ <font weight="400" style="normal" postScriptName="NotoSansGlagolitic">
+ NotoSansGlagolitic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Goth">
+ <font weight="400" style="normal" postScriptName="NotoSansGothic">
+ NotoSansGothic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Hano">
+ <font weight="400" style="normal" postScriptName="NotoSansHanunoo">
+ NotoSansHanunoo-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Armi">
+ <font weight="400" style="normal" postScriptName="NotoSansImperialAramaic">
+ NotoSansImperialAramaic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Phli">
+ <font weight="400" style="normal" postScriptName="NotoSansInscriptionalPahlavi">
+ NotoSansInscriptionalPahlavi-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Prti">
+ <font weight="400" style="normal" postScriptName="NotoSansInscriptionalParthian">
+ NotoSansInscriptionalParthian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Java">
+ <font weight="400" style="normal">NotoSansJavanese-Regular.otf</font>
+ </family>
+ <family lang="und-Kthi">
+ <font weight="400" style="normal" postScriptName="NotoSansKaithi">
+ NotoSansKaithi-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Kali">
+ <font weight="400" style="normal" postScriptName="NotoSansKayahLi">
+ NotoSansKayahLi-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Khar">
+ <font weight="400" style="normal" postScriptName="NotoSansKharoshthi">
+ NotoSansKharoshthi-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Lepc">
+ <font weight="400" style="normal" postScriptName="NotoSansLepcha">
+ NotoSansLepcha-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Limb">
+ <font weight="400" style="normal" postScriptName="NotoSansLimbu">NotoSansLimbu-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Linb">
+ <font weight="400" style="normal" postScriptName="NotoSansLinearB">
+ NotoSansLinearB-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Lisu">
+ <font weight="400" style="normal" postScriptName="NotoSansLisu">NotoSansLisu-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Lyci">
+ <font weight="400" style="normal" postScriptName="NotoSansLycian">
+ NotoSansLycian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Lydi">
+ <font weight="400" style="normal" postScriptName="NotoSansLydian">
+ NotoSansLydian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Mand">
+ <font weight="400" style="normal" postScriptName="NotoSansMandaic">
+ NotoSansMandaic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Mtei">
+ <font weight="400" style="normal" postScriptName="NotoSansMeeteiMayek">
+ NotoSansMeeteiMayek-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Talu">
+ <font weight="400" style="normal" postScriptName="NotoSansNewTaiLue">
+ NotoSansNewTaiLue-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Nkoo">
+ <font weight="400" style="normal" postScriptName="NotoSansNKo">NotoSansNKo-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Ogam">
+ <font weight="400" style="normal" postScriptName="NotoSansOgham">NotoSansOgham-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Olck">
+ <font weight="400" style="normal" postScriptName="NotoSansOlChiki">
+ NotoSansOlChiki-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Ital">
+ <font weight="400" style="normal" postScriptName="NotoSansOldItalic">
+ NotoSansOldItalic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Xpeo">
+ <font weight="400" style="normal" postScriptName="NotoSansOldPersian">
+ NotoSansOldPersian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Sarb">
+ <font weight="400" style="normal" postScriptName="NotoSansOldSouthArabian">
+ NotoSansOldSouthArabian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Orkh">
+ <font weight="400" style="normal" postScriptName="NotoSansOldTurkic">
+ NotoSansOldTurkic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Osge">
+ <font weight="400" style="normal">NotoSansOsage-Regular.ttf</font>
+ </family>
+ <family lang="und-Osma">
+ <font weight="400" style="normal" postScriptName="NotoSansOsmanya">
+ NotoSansOsmanya-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Phnx">
+ <font weight="400" style="normal" postScriptName="NotoSansPhoenician">
+ NotoSansPhoenician-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Rjng">
+ <font weight="400" style="normal" postScriptName="NotoSansRejang">
+ NotoSansRejang-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Runr">
+ <font weight="400" style="normal" postScriptName="NotoSansRunic">NotoSansRunic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Samr">
+ <font weight="400" style="normal" postScriptName="NotoSansSamaritan">
+ NotoSansSamaritan-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Saur">
+ <font weight="400" style="normal" postScriptName="NotoSansSaurashtra">
+ NotoSansSaurashtra-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Shaw">
+ <font weight="400" style="normal" postScriptName="NotoSansShavian">
+ NotoSansShavian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Sund">
+ <font weight="400" style="normal" postScriptName="NotoSansSundanese">
+ NotoSansSundanese-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Sylo">
+ <font weight="400" style="normal" postScriptName="NotoSansSylotiNagri">
+ NotoSansSylotiNagri-Regular.ttf
+ </font>
+ </family>
+ <!-- Esrangela should precede Eastern and Western Syriac, since it's our default form. -->
+ <family lang="und-Syre">
+ <font weight="400" style="normal" postScriptName="NotoSansSyriacEstrangela">
+ NotoSansSyriacEstrangela-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Syrn">
+ <font weight="400" style="normal" postScriptName="NotoSansSyriacEastern">
+ NotoSansSyriacEastern-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Syrj">
+ <font weight="400" style="normal" postScriptName="NotoSansSyriacWestern">
+ NotoSansSyriacWestern-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Tglg">
+ <font weight="400" style="normal" postScriptName="NotoSansTagalog">
+ NotoSansTagalog-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Tagb">
+ <font weight="400" style="normal" postScriptName="NotoSansTagbanwa">
+ NotoSansTagbanwa-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Lana">
+ <font weight="400" style="normal" postScriptName="NotoSansTaiTham">
+ NotoSansTaiTham-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Tavt">
+ <font weight="400" style="normal" postScriptName="NotoSansTaiViet">
+ NotoSansTaiViet-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Tibt">
+ <font weight="400" style="normal" postScriptName="NotoSerifTibetan-Regular">
+ NotoSerifTibetan-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSerifTibetan-Regular">
+ NotoSerifTibetan-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSerifTibetan-Regular">
+ NotoSerifTibetan-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSerifTibetan-Regular">
+ NotoSerifTibetan-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Tfng">
+ <font weight="400" style="normal">NotoSansTifinagh-Regular.otf</font>
+ </family>
+ <family lang="und-Ugar">
+ <font weight="400" style="normal" postScriptName="NotoSansUgaritic">
+ NotoSansUgaritic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Vaii">
+ <font weight="400" style="normal" postScriptName="NotoSansVai">NotoSansVai-Regular.ttf
+ </font>
+ </family>
+ <family>
+ <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font>
+ </family>
+ <family lang="zh-Hans">
+ <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKjp-Regular">
+ NotoSansCJK-Regular.ttc
+ </font>
+ <font weight="400" style="normal" index="2" fallbackFor="serif"
+ postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+ </font>
+ </family>
+ <family lang="zh-Hant,zh-Bopo">
+ <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKjp-Regular">
+ NotoSansCJK-Regular.ttc
+ </font>
+ <font weight="400" style="normal" index="3" fallbackFor="serif"
+ postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+ </font>
+ </family>
+ <family lang="ja">
+ <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKjp-Regular">
+ NotoSansCJK-Regular.ttc
+ </font>
+ <font weight="400" style="normal" index="0" fallbackFor="serif"
+ postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+ </font>
+ </family>
+ <family lang="ko">
+ <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Regular">
+ NotoSansCJK-Regular.ttc
+ </font>
+ <font weight="400" style="normal" index="1" fallbackFor="serif"
+ postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+ </font>
+ </family>
+ <family lang="und-Zsye" ignore="true">
+ <font weight="400" style="normal">NotoColorEmojiLegacy.ttf</font>
+ </family>
+ <family lang="und-Zsye">
+ <font weight="400" style="normal">NotoColorEmoji.ttf</font>
+ </family>
+ <family lang="und-Zsye">
+ <font weight="400" style="normal">NotoColorEmojiFlags.ttf</font>
+ </family>
+ <family lang="und-Zsym">
+ <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted2.ttf</font>
+ </family>
+ <!--
+ Tai Le, Yi, Mongolian, and Phags-pa are intentionally kept last, to make sure they don't
+ override the East Asian punctuation for Chinese.
+ -->
+ <family lang="und-Tale">
+ <font weight="400" style="normal" postScriptName="NotoSansTaiLe">NotoSansTaiLe-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Yiii">
+ <font weight="400" style="normal" postScriptName="NotoSansYi">NotoSansYi-Regular.ttf</font>
+ </family>
+ <family lang="und-Mong">
+ <font weight="400" style="normal" postScriptName="NotoSansMongolian">
+ NotoSansMongolian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Phag">
+ <font weight="400" style="normal" postScriptName="NotoSansPhagsPa">
+ NotoSansPhagsPa-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Hluw">
+ <font weight="400" style="normal">NotoSansAnatolianHieroglyphs-Regular.otf</font>
+ </family>
+ <family lang="und-Bass">
+ <font weight="400" style="normal">NotoSansBassaVah-Regular.otf</font>
+ </family>
+ <family lang="und-Bhks">
+ <font weight="400" style="normal">NotoSansBhaiksuki-Regular.otf</font>
+ </family>
+ <family lang="und-Hatr">
+ <font weight="400" style="normal">NotoSansHatran-Regular.otf</font>
+ </family>
+ <family lang="und-Lina">
+ <font weight="400" style="normal">NotoSansLinearA-Regular.otf</font>
+ </family>
+ <family lang="und-Mani">
+ <font weight="400" style="normal">NotoSansManichaean-Regular.otf</font>
+ </family>
+ <family lang="und-Marc">
+ <font weight="400" style="normal">NotoSansMarchen-Regular.otf</font>
+ </family>
+ <family lang="und-Merc">
+ <font weight="400" style="normal">NotoSansMeroitic-Regular.otf</font>
+ </family>
+ <family lang="und-Plrd">
+ <font weight="400" style="normal">NotoSansMiao-Regular.otf</font>
+ </family>
+ <family lang="und-Mroo">
+ <font weight="400" style="normal">NotoSansMro-Regular.otf</font>
+ </family>
+ <family lang="und-Mult">
+ <font weight="400" style="normal">NotoSansMultani-Regular.otf</font>
+ </family>
+ <family lang="und-Nbat">
+ <font weight="400" style="normal">NotoSansNabataean-Regular.otf</font>
+ </family>
+ <family lang="und-Newa">
+ <font weight="400" style="normal">NotoSansNewa-Regular.otf</font>
+ </family>
+ <family lang="und-Narb">
+ <font weight="400" style="normal">NotoSansOldNorthArabian-Regular.otf</font>
+ </family>
+ <family lang="und-Perm">
+ <font weight="400" style="normal">NotoSansOldPermic-Regular.otf</font>
+ </family>
+ <family lang="und-Hmng">
+ <font weight="400" style="normal">NotoSansPahawhHmong-Regular.otf</font>
+ </family>
+ <family lang="und-Palm">
+ <font weight="400" style="normal">NotoSansPalmyrene-Regular.otf</font>
+ </family>
+ <family lang="und-Pauc">
+ <font weight="400" style="normal">NotoSansPauCinHau-Regular.otf</font>
+ </family>
+ <family lang="und-Shrd">
+ <font weight="400" style="normal">NotoSansSharada-Regular.otf</font>
+ </family>
+ <family lang="und-Sora">
+ <font weight="400" style="normal">NotoSansSoraSompeng-Regular.otf</font>
+ </family>
+ <family lang="und-Gong">
+ <font weight="400" style="normal">NotoSansGunjalaGondi-Regular.otf</font>
+ </family>
+ <family lang="und-Rohg">
+ <font weight="400" style="normal">NotoSansHanifiRohingya-Regular.otf</font>
+ </family>
+ <family lang="und-Khoj">
+ <font weight="400" style="normal">NotoSansKhojki-Regular.otf</font>
+ </family>
+ <family lang="und-Gonm">
+ <font weight="400" style="normal">NotoSansMasaramGondi-Regular.otf</font>
+ </family>
+ <family lang="und-Wcho">
+ <font weight="400" style="normal">NotoSansWancho-Regular.otf</font>
+ </family>
+ <family lang="und-Wara">
+ <font weight="400" style="normal">NotoSansWarangCiti-Regular.otf</font>
+ </family>
+ <family lang="und-Gran">
+ <font weight="400" style="normal">NotoSansGrantha-Regular.ttf</font>
+ </family>
+ <family lang="und-Modi">
+ <font weight="400" style="normal">NotoSansModi-Regular.ttf</font>
+ </family>
+ <family lang="und-Dogr">
+ <font weight="400" style="normal">NotoSerifDogra-Regular.ttf</font>
+ </family>
+ <family lang="und-Medf">
+ <font weight="400" style="normal" postScriptName="NotoSansMedefaidrin-Regular">
+ NotoSansMedefaidrin-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansMedefaidrin-Regular">
+ NotoSansMedefaidrin-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansMedefaidrin-Regular">
+ NotoSansMedefaidrin-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansMedefaidrin-Regular">
+ NotoSansMedefaidrin-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Soyo">
+ <font weight="400" style="normal" postScriptName="NotoSansSoyombo-Regular">
+ NotoSansSoyombo-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansSoyombo-Regular">
+ NotoSansSoyombo-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansSoyombo-Regular">
+ NotoSansSoyombo-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansSoyombo-Regular">
+ NotoSansSoyombo-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Takr">
+ <font weight="400" style="normal" postScriptName="NotoSansTakri-Regular">
+ NotoSansTakri-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansTakri-Regular">
+ NotoSansTakri-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansTakri-Regular">
+ NotoSansTakri-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansTakri-Regular">
+ NotoSansTakri-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Hmnp">
+ <font weight="400" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular">
+ NotoSerifNyiakengPuachueHmong-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular">
+ NotoSerifNyiakengPuachueHmong-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular">
+ NotoSerifNyiakengPuachueHmong-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular">
+ NotoSerifNyiakengPuachueHmong-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Yezi">
+ <font weight="400" style="normal" postScriptName="NotoSerifYezidi-Regular">
+ NotoSerifYezidi-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSerifYezidi-Regular">
+ NotoSerifYezidi-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSerifYezidi-Regular">
+ NotoSerifYezidi-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSerifYezidi-Regular">
+ NotoSerifYezidi-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+</familyset>
diff --git a/nativeruntime/src/main/resources/icu/icudt68l.dat b/nativeruntime/src/main/resources/icu/icudt68l.dat
new file mode 100644
index 000000000..5b805c5bd
--- /dev/null
+++ b/nativeruntime/src/main/resources/icu/icudt68l.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
new file mode 100644
index 000000000..ec86818f1
--- /dev/null
+++ b/nativeruntime/src/test/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLazyLoadTest.java
@@ -0,0 +1,29 @@
+package org.robolectric.nativeruntime;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Application;
+import android.database.CursorWindow;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+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.
+ */
+ @SuppressWarnings("UnusedVariable")
+ @Test
+ public void lazyLoad() throws Exception {
+ Application application = RuntimeEnvironment.getApplication();
+ assertThat(DefaultNativeRuntimeLoader.isLoaded()).isFalse();
+ CursorWindow cursorWindow = new CursorWindow("hi");
+ cursorWindow.close();
+ assertThat(DefaultNativeRuntimeLoader.isLoaded()).isTrue();
+ }
+}
diff --git a/nativeruntime/src/test/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLoaderTest.java b/nativeruntime/src/test/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLoaderTest.java
new file mode 100644
index 000000000..cbb9cf1f5
--- /dev/null
+++ b/nativeruntime/src/test/java/org/robolectric/nativeruntime/DefaultNativeRuntimeLoaderTest.java
@@ -0,0 +1,21 @@
+package org.robolectric.nativeruntime;
+
+import android.database.CursorWindow;
+import android.database.sqlite.SQLiteDatabase;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public final class DefaultNativeRuntimeLoaderTest {
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+
+ @Test
+ public void concurrentLoad() throws Exception {
+ executor.execute(() -> SQLiteDatabase.create(null));
+ CursorWindow cursorWindow = new CursorWindow("sdfsdf");
+ cursorWindow.close();
+ }
+}
diff --git a/pluginapi/Android.bp b/pluginapi/Android.bp
new file mode 100644
index 000000000..085c62bf8
--- /dev/null
+++ b/pluginapi/Android.bp
@@ -0,0 +1,41 @@
+//#############################################
+// Compile Robolectric utils
+//#############################################
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "external_robolectric_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["external_robolectric_license"],
+}
+
+java_library_host {
+ name: "Robolectric_pluginapi_upstream",
+ srcs: ["src/main/java/**/*.java"],
+ static_libs: [
+ "robolectric-javax.annotation-api-1.2",
+ "Robolectric_annotations_upstream",
+ "guava",
+ "jsr330",
+ "jsr305",
+ ],
+}
+
+//#############################################
+// Compile Robolectric utils tests
+//#############################################
+
+java_test_host {
+ name: "Robolectric_pluginapi_tests_upstream",
+ srcs: ["src/test/java/**/*.java"],
+ static_libs: [
+ "Robolectric_pluginapi_upstream",
+ "hamcrest",
+ "guava",
+ "junit",
+ "truth-prebuilt",
+ ],
+ test_suites: ["general-tests"],
+}
diff --git a/plugins/maven-dependency-resolver/src/main/java/org/robolectric/MavenRoboSettings.java b/plugins/maven-dependency-resolver/src/main/java/org/robolectric/MavenRoboSettings.java
index 527ee33a9..0c8d0697c 100644
--- a/plugins/maven-dependency-resolver/src/main/java/org/robolectric/MavenRoboSettings.java
+++ b/plugins/maven-dependency-resolver/src/main/java/org/robolectric/MavenRoboSettings.java
@@ -7,11 +7,13 @@ package org.robolectric;
*/
@Deprecated
public class MavenRoboSettings {
-
+ private static final int DEFAULT_PROXY_PORT = 0;
private static String mavenRepositoryId;
private static String mavenRepositoryUrl;
private static String mavenRepositoryUserName;
private static String mavenRepositoryPassword;
+ private static String mavenProxyHost = "";
+ private static int mavenProxyPort = DEFAULT_PROXY_PORT;
static {
mavenRepositoryId = System.getProperty("robolectric.dependency.repo.id", "mavenCentral");
@@ -19,6 +21,20 @@ public class MavenRoboSettings {
System.getProperty("robolectric.dependency.repo.url", "https://repo1.maven.org/maven2");
mavenRepositoryUserName = System.getProperty("robolectric.dependency.repo.username");
mavenRepositoryPassword = System.getProperty("robolectric.dependency.repo.password");
+
+ String proxyHost = System.getProperty("robolectric.dependency.proxy.host");
+ if (proxyHost != null && !proxyHost.isEmpty()) {
+ mavenProxyHost = proxyHost;
+ }
+
+ String proxyPort = System.getProperty("robolectric.dependency.proxy.port");
+ if (proxyPort != null && !proxyPort.isEmpty()) {
+ try {
+ mavenProxyPort = Integer.parseInt(proxyPort);
+ } catch (NumberFormatException numberFormatException) {
+ mavenProxyPort = DEFAULT_PROXY_PORT;
+ }
+ }
}
public static String getMavenRepositoryId() {
@@ -52,4 +68,20 @@ public class MavenRoboSettings {
public static void setMavenRepositoryPassword(String mavenRepositoryPassword) {
MavenRoboSettings.mavenRepositoryPassword = mavenRepositoryPassword;
}
+
+ public static String getMavenProxyHost() {
+ return mavenProxyHost;
+ }
+
+ public static void setMavenProxyHost(String mavenProxyHost) {
+ MavenRoboSettings.mavenProxyHost = mavenProxyHost;
+ }
+
+ public static int getMavenProxyPort() {
+ return mavenProxyPort;
+ }
+
+ public static void setMavenProxyPort(int mavenProxyPort) {
+ MavenRoboSettings.mavenProxyPort = mavenProxyPort;
+ }
}
diff --git a/plugins/maven-dependency-resolver/src/main/java/org/robolectric/internal/dependency/MavenArtifactFetcher.java b/plugins/maven-dependency-resolver/src/main/java/org/robolectric/internal/dependency/MavenArtifactFetcher.java
index 9b3a28a32..60f852dbc 100644
--- a/plugins/maven-dependency-resolver/src/main/java/org/robolectric/internal/dependency/MavenArtifactFetcher.java
+++ b/plugins/maven-dependency-resolver/src/main/java/org/robolectric/internal/dependency/MavenArtifactFetcher.java
@@ -14,7 +14,9 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.net.InetSocketAddress;
import java.net.MalformedURLException;
+import java.net.Proxy;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
@@ -34,6 +36,8 @@ public class MavenArtifactFetcher {
private final String repositoryUrl;
private final String repositoryUserName;
private final String repositoryPassword;
+ private final String proxyHost;
+ private final int proxyPort;
private final File localRepositoryDir;
private final ExecutorService executorService;
private File stagingRepositoryDir;
@@ -42,11 +46,15 @@ public class MavenArtifactFetcher {
String repositoryUrl,
String repositoryUserName,
String repositoryPassword,
+ String proxyHost,
+ int proxyPort,
File localRepositoryDir,
ExecutorService executorService) {
this.repositoryUrl = repositoryUrl;
this.repositoryUserName = repositoryUserName;
this.repositoryPassword = repositoryPassword;
+ this.proxyHost = proxyHost;
+ this.proxyPort = proxyPort;
this.localRepositoryDir = localRepositoryDir;
this.executorService = executorService;
}
@@ -152,7 +160,8 @@ public class MavenArtifactFetcher {
protected ListenableFuture<Void> createFetchToFileTask(URL remoteUrl, File tempFile) {
return Futures.submitAsync(
- new FetchToFileTask(remoteUrl, tempFile, repositoryUserName, repositoryPassword),
+ new FetchToFileTask(
+ remoteUrl, tempFile, repositoryUserName, repositoryPassword, proxyHost, proxyPort),
this.executorService);
}
@@ -168,18 +177,34 @@ public class MavenArtifactFetcher {
private final File localFile;
private String repositoryUserName;
private String repositoryPassword;
+ private String proxyHost;
+ private int proxyPort;
public FetchToFileTask(
- URL remoteURL, File localFile, String repositoryUserName, String repositoryPassword) {
+ URL remoteURL,
+ File localFile,
+ String repositoryUserName,
+ String repositoryPassword,
+ String proxyHost,
+ int proxyPort) {
this.remoteURL = remoteURL;
this.localFile = localFile;
this.repositoryUserName = repositoryUserName;
this.repositoryPassword = repositoryPassword;
+ this.proxyHost = proxyHost;
+ this.proxyPort = proxyPort;
}
@Override
public ListenableFuture<Void> call() throws Exception {
- URLConnection connection = remoteURL.openConnection();
+ URLConnection connection;
+ if (this.proxyHost != null && !this.proxyHost.isEmpty() && this.proxyPort > 0) {
+ Proxy proxy =
+ new Proxy(Proxy.Type.HTTP, new InetSocketAddress(this.proxyHost, this.proxyPort));
+ connection = remoteURL.openConnection(proxy);
+ } else {
+ connection = remoteURL.openConnection();
+ }
// Add authorization header if applicable.
if (!Strings.isNullOrEmpty(this.repositoryUserName)) {
String encoded =
diff --git a/plugins/maven-dependency-resolver/src/main/java/org/robolectric/internal/dependency/MavenDependencyResolver.java b/plugins/maven-dependency-resolver/src/main/java/org/robolectric/internal/dependency/MavenDependencyResolver.java
index 22adfaeb3..bb5604d80 100755
--- a/plugins/maven-dependency-resolver/src/main/java/org/robolectric/internal/dependency/MavenDependencyResolver.java
+++ b/plugins/maven-dependency-resolver/src/main/java/org/robolectric/internal/dependency/MavenDependencyResolver.java
@@ -44,11 +44,22 @@ public class MavenDependencyResolver implements DependencyResolver {
private final File localRepositoryDir;
public MavenDependencyResolver() {
- this(MavenRoboSettings.getMavenRepositoryUrl(), MavenRoboSettings.getMavenRepositoryId(), MavenRoboSettings
- .getMavenRepositoryUserName(), MavenRoboSettings.getMavenRepositoryPassword());
+ this(
+ MavenRoboSettings.getMavenRepositoryUrl(),
+ MavenRoboSettings.getMavenRepositoryId(),
+ MavenRoboSettings.getMavenRepositoryUserName(),
+ MavenRoboSettings.getMavenRepositoryPassword(),
+ MavenRoboSettings.getMavenProxyHost(),
+ MavenRoboSettings.getMavenProxyPort());
}
- public MavenDependencyResolver(String repositoryUrl, String repositoryId, String repositoryUserName, String repositoryPassword) {
+ public MavenDependencyResolver(
+ String repositoryUrl,
+ String repositoryId,
+ String repositoryUserName,
+ String repositoryPassword,
+ String proxyHost,
+ int proxyPort) {
this.executorService = createExecutorService();
this.localRepositoryDir = getLocalRepositoryDir();
this.mavenArtifactFetcher =
@@ -56,6 +67,8 @@ public class MavenDependencyResolver implements DependencyResolver {
repositoryUrl,
repositoryUserName,
repositoryPassword,
+ proxyHost,
+ proxyPort,
localRepositoryDir,
this.executorService);
}
@@ -163,10 +176,18 @@ public class MavenDependencyResolver implements DependencyResolver {
String repositoryUrl,
String repositoryUserName,
String repositoryPassword,
+ String proxyHost,
+ int proxyPort,
File localRepositoryDir,
ExecutorService executorService) {
return new MavenArtifactFetcher(
- repositoryUrl, repositoryUserName, repositoryPassword, localRepositoryDir, executorService);
+ repositoryUrl,
+ repositoryUserName,
+ repositoryPassword,
+ proxyHost,
+ proxyPort,
+ localRepositoryDir,
+ executorService);
}
protected ExecutorService createExecutorService() {
diff --git a/plugins/maven-dependency-resolver/src/test/java/org/robolectric/MavenRoboSettingsTest.java b/plugins/maven-dependency-resolver/src/test/java/org/robolectric/MavenRoboSettingsTest.java
index 164203b9e..8924257d1 100644
--- a/plugins/maven-dependency-resolver/src/test/java/org/robolectric/MavenRoboSettingsTest.java
+++ b/plugins/maven-dependency-resolver/src/test/java/org/robolectric/MavenRoboSettingsTest.java
@@ -15,6 +15,8 @@ public class MavenRoboSettingsTest {
private String originalMavenRepositoryUrl;
private String originalMavenRepositoryUserName;
private String originalMavenRepositoryPassword;
+ private String originalMavenRepositoryProxyHost;
+ private int originalMavenProxyPort;
@Before
public void setUp() {
@@ -22,6 +24,8 @@ public class MavenRoboSettingsTest {
originalMavenRepositoryUrl = MavenRoboSettings.getMavenRepositoryUrl();
originalMavenRepositoryUserName = MavenRoboSettings.getMavenRepositoryUserName();
originalMavenRepositoryPassword = MavenRoboSettings.getMavenRepositoryPassword();
+ originalMavenRepositoryProxyHost = MavenRoboSettings.getMavenProxyHost();
+ originalMavenProxyPort = MavenRoboSettings.getMavenProxyPort();
}
@After
@@ -30,6 +34,8 @@ public class MavenRoboSettingsTest {
MavenRoboSettings.setMavenRepositoryUrl(originalMavenRepositoryUrl);
MavenRoboSettings.setMavenRepositoryUserName(originalMavenRepositoryUserName);
MavenRoboSettings.setMavenRepositoryPassword(originalMavenRepositoryPassword);
+ MavenRoboSettings.setMavenProxyHost(originalMavenRepositoryProxyHost);
+ MavenRoboSettings.setMavenProxyPort(originalMavenProxyPort);
}
@Test
@@ -65,4 +71,16 @@ public class MavenRoboSettingsTest {
MavenRoboSettings.setMavenRepositoryPassword("password");
assertEquals("password", MavenRoboSettings.getMavenRepositoryPassword());
}
+
+ @Test
+ public void setMavenProxyHost() {
+ MavenRoboSettings.setMavenProxyHost("123.4.5.678");
+ assertEquals("123.4.5.678", MavenRoboSettings.getMavenProxyHost());
+ }
+
+ @Test
+ public void setMavenProxyPort() {
+ MavenRoboSettings.setMavenProxyPort(9000);
+ assertEquals(9000, MavenRoboSettings.getMavenProxyPort());
+ }
}
diff --git a/plugins/maven-dependency-resolver/src/test/java/org/robolectric/internal/dependency/MavenDependencyResolverTest.java b/plugins/maven-dependency-resolver/src/test/java/org/robolectric/internal/dependency/MavenDependencyResolverTest.java
index 3849c03e9..f438414d3 100644
--- a/plugins/maven-dependency-resolver/src/test/java/org/robolectric/internal/dependency/MavenDependencyResolverTest.java
+++ b/plugins/maven-dependency-resolver/src/test/java/org/robolectric/internal/dependency/MavenDependencyResolverTest.java
@@ -27,6 +27,8 @@ public class MavenDependencyResolverTest {
private static final String REPOSITORY_URL;
private static final String REPOSITORY_USERNAME = "username";
private static final String REPOSITORY_PASSWORD = "password";
+ private static final String PROXY_HOST = "123.4.5.678";
+ private static final int PROXY_PORT = 9000;
private static final HashFunction SHA512 = Hashing.sha512();
private static DependencyJar[] successCases =
@@ -65,6 +67,8 @@ public class MavenDependencyResolverTest {
REPOSITORY_URL,
REPOSITORY_USERNAME,
REPOSITORY_PASSWORD,
+ PROXY_HOST,
+ PROXY_PORT,
localRepositoryDir,
executorService);
mavenDependencyResolver = new TestMavenDependencyResolver();
@@ -167,6 +171,8 @@ public class MavenDependencyResolverTest {
String repositoryUrl,
String repositoryUserName,
String repositoryPassword,
+ String proxyHost,
+ int proxyPort,
File localRepositoryDir,
ExecutorService executorService) {
return mavenArtifactFetcher;
@@ -200,12 +206,16 @@ public class MavenDependencyResolverTest {
String repositoryUrl,
String repositoryUserName,
String repositoryPassword,
+ String proxyHost,
+ int proxyPort,
File localRepositoryDir,
ExecutorService executorService) {
super(
repositoryUrl,
repositoryUserName,
repositoryPassword,
+ proxyHost,
+ proxyPort,
localRepositoryDir,
executorService);
this.executorService = executorService;
@@ -214,7 +224,7 @@ public class MavenDependencyResolverTest {
@Override
protected ListenableFuture<Void> createFetchToFileTask(URL remoteUrl, File tempFile) {
return Futures.submitAsync(
- new FetchToFileTask(remoteUrl, tempFile, null, null) {
+ new FetchToFileTask(remoteUrl, tempFile, null, null, null, 0) {
@Override
public ListenableFuture<Void> call() throws Exception {
numRequests += 1;
diff --git a/preinstrumented/src/main/java/org/robolectric/preinstrumented/JarInstrumentor.java b/preinstrumented/src/main/java/org/robolectric/preinstrumented/JarInstrumentor.java
index ed5769215..a9c5aceab 100644
--- a/preinstrumented/src/main/java/org/robolectric/preinstrumented/JarInstrumentor.java
+++ b/preinstrumented/src/main/java/org/robolectric/preinstrumented/JarInstrumentor.java
@@ -8,6 +8,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.Locale;
+import java.util.Properties;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
@@ -64,6 +65,13 @@ public class JarInstrumentor {
int nonClassCount = 0;
int classCount = 0;
+ // get the jar's SDK version
+ try {
+ classInstrumentor.setAndroidJarSDKVersion(getJarAndroidSDKVersion(jarFile));
+ } catch (Exception e) {
+ throw new AssertionError("Unable to get Android SDK version from Jar File", e);
+ }
+
try (JarOutputStream jarOut =
new JarOutputStream(new BufferedOutputStream(new FileOutputStream(destFile), ONE_MB))) {
Enumeration<JarEntry> entries = jarFile.entries();
@@ -136,4 +144,16 @@ public class JarInstrumentor {
entry.setTime(original.getTime());
return entry;
}
+
+ private int getJarAndroidSDKVersion(JarFile jarFile) throws IOException {
+ ZipEntry buildProp = jarFile.getEntry("build.prop");
+ Properties buildProps = new Properties();
+ buildProps.load(jarFile.getInputStream(buildProp));
+ String codename = buildProps.getProperty("ro.build.version.codename");
+ // Check for a prerelease SDK.
+ if (!"REL".equals(codename)) {
+ return 10000;
+ }
+ return Integer.parseInt(buildProps.getProperty("ro.build.version.sdk"));
+ }
}
diff --git a/processor/Android.bp b/processor/Android.bp
new file mode 100644
index 000000000..46a8dc3b8
--- /dev/null
+++ b/processor/Android.bp
@@ -0,0 +1,93 @@
+//#############################################
+// Compile Robolectric processor
+//#############################################
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "external_robolectric_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ // SPDX-license-identifier-MIT
+ default_applicable_licenses: ["external_robolectric_license"],
+}
+
+java_library_host {
+ name: "libRobolectric_processor_upstream",
+ srcs: ["src/main/java/**/*.java"],
+ java_resource_dirs: ["src/main/resources"],
+ java_resources: ["sdks.txt"],
+ use_tools_jar: true,
+ plugins: [
+ "auto_service_plugin",
+ ],
+ static_libs: [
+ "Robolectric_annotations_upstream",
+ "Robolectric_shadowapi_upstream",
+ "auto_service_annotations",
+ "asm-commons-9.2",
+ "guava",
+ "asm-tree-9.2",
+ "gson-prebuilt-jar-2.9.1",
+ "asm-9.2",
+ "jsr305",
+ "auto-common-1.1.2",
+ ],
+
+ openjdk9: {
+ javacflags: [
+ "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED",
+ "--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
+ "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
+ ],
+ },
+}
+
+java_plugin {
+ name: "Robolectric_processor_upstream",
+ processor_class: "org.robolectric.annotation.processing.RobolectricProcessor",
+ static_libs: ["libRobolectric_processor_upstream"],
+}
+
+//#############################################
+// Compile Robolectric processor tests
+//#############################################
+java_test_host {
+ name: "Robolectric_processor_tests_upstream",
+ srcs: ["src/test/java/**/*.java"],
+ java_resource_dirs: ["src/test/resources"],
+ java_resources: [":Robolectric_processor_tests_resources_upstream"],
+ static_libs: [
+ "Robolectric_annotations_upstream",
+ "libRobolectric_processor_upstream",
+ "Robolectric_shadowapi_upstream",
+ "robolectric-javax.annotation-api-1.2",
+ "robolectric-compile-testing-0.19",
+ "mockito",
+ "hamcrest",
+ "guava",
+ "objenesis",
+ "junit",
+ "truth-prebuilt",
+ "gson-prebuilt-jar-2.9.1",
+ "jsr305",
+ ],
+
+ test_suites: ["general-tests"],
+
+ // Disable annotation processing while compiling tests to avoid executing RobolectricProcessor.
+ javacflags: ["-proc:none"],
+
+ // Disabling tests as they fail in the bramble build.
+ test_options: {
+ unit_test: false,
+ },
+
+}
+
+// Workaround: java_resource_dirs ignores *.java files
+filegroup {
+ name: "Robolectric_processor_tests_resources_upstream",
+ path: "src/test/resources",
+ srcs: ["src/test/resources/**/*.java"],
+}
diff --git a/processor/sdks.txt b/processor/sdks.txt
new file mode 100644
index 000000000..b9931ca3d
--- /dev/null
+++ b/processor/sdks.txt
@@ -0,0 +1,15 @@
+prebuilts/misc/common/robolectric/android-all/android-all-4.1.2_r1-robolectric-r1.jar
+prebuilts/misc/common/robolectric/android-all/android-all-4.2.2_r1.2-robolectric-r1.jar
+prebuilts/misc/common/robolectric/android-all/android-all-4.3_r2-robolectric-r1.jar
+prebuilts/misc/common/robolectric/android-all/android-all-4.4_r1-robolectric-r2.jar
+prebuilts/misc/common/robolectric/android-all/android-all-5.0.2_r3-robolectric-r0.jar
+prebuilts/misc/common/robolectric/android-all/android-all-5.1.1_r9-robolectric-r2.jar
+prebuilts/misc/common/robolectric/android-all/android-all-6.0.1_r3-robolectric-r1.jar
+prebuilts/misc/common/robolectric/android-all/android-all-7.0.0_r1-robolectric-r1.jar
+prebuilts/misc/common/robolectric/android-all/android-all-7.1.0_r7-robolectric-r1.jar
+prebuilts/misc/common/robolectric/android-all/android-all-8.0.0_r4-robolectric-r1.jar
+prebuilts/misc/common/robolectric/android-all/android-all-8.1.0-robolectric-4611349.jar
+prebuilts/misc/common/robolectric/android-all/android-all-9-robolectric-4913185-2.jar
+prebuilts/misc/common/robolectric/android-all/android-all-9plus-robolectric-5616371.jar
+prebuilts/misc/common/robolectric/android-all/android-all-10-robolectric-5803371.jar
+prebuilts/misc/common/robolectric/android-all/android-all-R-beta2-robolectric-6625208.jar
diff --git a/processor/src/main/java/org/robolectric/annotation/processing/RobolectricModel.java b/processor/src/main/java/org/robolectric/annotation/processing/RobolectricModel.java
index 79c246132..fd5e77dd2 100644
--- a/processor/src/main/java/org/robolectric/annotation/processing/RobolectricModel.java
+++ b/processor/src/main/java/org/robolectric/annotation/processing/RobolectricModel.java
@@ -4,6 +4,7 @@ import static com.google.common.collect.Maps.newHashMap;
import static com.google.common.collect.Maps.newTreeMap;
import static com.google.common.collect.Sets.newTreeSet;
+import com.google.auto.common.MoreTypes;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Multimaps;
@@ -99,10 +100,9 @@ public class RobolectricModel {
TypeElement shadowBaseType = null;
if (shadowPickerType != null) {
TypeMirror iface = helpers.findInterface(shadowPickerType, ShadowPicker.class);
- if (iface != null) {
- com.sun.tools.javac.code.Type type = ((com.sun.tools.javac.code.Type.ClassType) iface)
- .allparams().get(0);
- String baseClassName = type.asElement().getQualifiedName().toString();
+ if (iface instanceof DeclaredType) {
+ TypeMirror first = MoreTypes.asDeclared(iface).getTypeArguments().get(0);
+ String baseClassName = first.toString();
shadowBaseType = helpers.getTypeElement(baseClassName);
}
}
diff --git a/resources/Android.bp b/resources/Android.bp
new file mode 100644
index 000000000..cfdc377ce
--- /dev/null
+++ b/resources/Android.bp
@@ -0,0 +1,46 @@
+//#############################################
+// Compile Robolectric resources
+//#############################################
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "external_robolectric-shadows_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["external_robolectric_license"],
+}
+
+java_library_host {
+ name: "Robolectric_resources_upstream",
+ srcs: ["src/main/java/**/*.java"],
+ libs: [
+ "Robolectric_annotations_upstream",
+ "Robolectric_utils_upstream",
+ "guava",
+ "jsr305",
+ ],
+}
+
+//#############################################
+// Compile Robolectric resources tests
+//#############################################
+
+java_test_host {
+ name: "Robolectric_resources_tests_upstream",
+ srcs: ["src/test/java/**/*.java"],
+ static_libs: [
+ "Robolectric_resources_upstream",
+ "Robolectric_annotations_upstream",
+ "Robolectric_utils_upstream",
+ "mockito",
+ "hamcrest",
+ "guava",
+ "objenesis",
+ "junit",
+ "truth-prebuilt",
+ "jsr305",
+ ],
+ java_resource_dirs: ["src/test/resources"],
+ test_suites: ["general-tests"],
+}
diff --git a/resources/src/main/java/org/robolectric/manifest/AndroidManifest.java b/resources/src/main/java/org/robolectric/manifest/AndroidManifest.java
index 82de00f79..cfabcbb2b 100644
--- a/resources/src/main/java/org/robolectric/manifest/AndroidManifest.java
+++ b/resources/src/main/java/org/robolectric/manifest/AndroidManifest.java
@@ -13,6 +13,7 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.xml.parsers.DocumentBuilder;
@@ -21,6 +22,7 @@ import org.robolectric.pluginapi.UsesSdk;
import org.robolectric.res.Fs;
import org.robolectric.res.ResourcePath;
import org.robolectric.res.ResourceTable;
+import org.robolectric.util.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
@@ -165,6 +167,8 @@ public class AndroidManifest implements UsesSdk {
return;
}
+ Logger.debug("Manifest file location: " + androidManifestFile);
+
if (androidManifestFile != null && Files.exists(androidManifestFile)) {
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
@@ -174,6 +178,9 @@ public class AndroidManifest implements UsesSdk {
Document manifestDocument = db.parse(inputStream);
inputStream.close();
+ Logger.debug("Manifest doc:\n" + Files.readAllLines(androidManifestFile).stream().collect(
+ Collectors.joining("\n")));
+
if (!packageNameIsOverridden()) {
packageName = getTagAttributeText(manifestDocument, "manifest", "package");
}
diff --git a/resources/src/main/java/org/robolectric/res/android/FileMap.java b/resources/src/main/java/org/robolectric/res/android/FileMap.java
index f12726865..0672bbde4 100644
--- a/resources/src/main/java/org/robolectric/res/android/FileMap.java
+++ b/resources/src/main/java/org/robolectric/res/android/FileMap.java
@@ -7,6 +7,7 @@ import static org.robolectric.res.android.Util.ALOGV;
import com.google.common.collect.ImmutableMap;
import com.google.common.primitives.Ints;
+import com.google.common.primitives.Longs;
import com.google.common.primitives.Shorts;
import java.io.File;
import java.io.FileInputStream;
@@ -23,11 +24,20 @@ public class FileMap {
/** ZIP archive central directory end header signature. */
private static final int ENDSIG = 0x6054b50;
- private static final int ENDHDR = 22;
+ private static final int EOCD_SIZE = 22;
+
+ private static final int ZIP64_EOCD_SIZE = 56;
+
+ private static final int ZIP64_EOCD_LOCATOR_SIZE = 20;
+
/** ZIP64 archive central directory end header signature. */
private static final int ENDSIG64 = 0x6064b50;
- /** the maximum size of the end of central directory section in bytes */
- private static final int MAXIMUM_ZIP_EOCD_SIZE = 64 * 1024 + ENDHDR;
+
+ private static final int MAX_COMMENT_SIZE = 64 * 1024; // 64k
+
+ /** the maximum size of the end of central directory sections in bytes */
+ private static final int MAXIMUM_ZIP_EOCD_SIZE =
+ MAX_COMMENT_SIZE + EOCD_SIZE + ZIP64_EOCD_SIZE + ZIP64_EOCD_LOCATOR_SIZE;
private ZipFile zipFile;
private ZipEntry zipEntry;
@@ -209,7 +219,6 @@ public class FileMap {
// First read the 'end of central directory record' in order to find the start of the central
// directory
- // The end of central directory record (EOCD) is max comment length (64K) + 22 bytes
int endOfCdSize = Math.min(MAXIMUM_ZIP_EOCD_SIZE, length);
int endofCdOffset = length - endOfCdSize;
randomAccessFile.seek(endofCdOffset);
@@ -217,7 +226,11 @@ public class FileMap {
randomAccessFile.readFully(buffer);
int centralDirOffset = findCentralDir(buffer);
-
+ if (centralDirOffset == -1) {
+ // If the zip file contains > 2^16 entries, a Zip64 EOCD is written, and the central
+ // dir offset in the regular EOCD may be -1.
+ centralDirOffset = findCentralDir64(buffer);
+ }
int offset = centralDirOffset - endofCdOffset;
if (offset < 0) {
// read the entire central directory record into memory
@@ -284,7 +297,7 @@ public class FileMap {
private static int findCentralDir(byte[] buffer) throws IOException {
// find start of central directory by scanning backwards
- int scanOffset = buffer.length - ENDHDR;
+ int scanOffset = buffer.length - EOCD_SIZE;
while (true) {
int val = readInt(buffer, scanOffset);
@@ -305,12 +318,48 @@ public class FileMap {
return offsetToCentralDir;
}
+ private static int findCentralDir64(byte[] buffer) throws IOException {
+ // find start of central directory by scanning backwards
+ int scanOffset = buffer.length - EOCD_SIZE - ZIP64_EOCD_LOCATOR_SIZE - ZIP64_EOCD_SIZE;
+
+ while (true) {
+ int val = readInt(buffer, scanOffset);
+ if (val == ENDSIG64) {
+ break;
+ }
+
+ // Ok, keep backing up looking for the ZIP end central directory
+ // signature.
+ --scanOffset;
+ if (scanOffset < 0) {
+ throw new ZipException("ZIP directory not found, not a ZIP archive.");
+ }
+ }
+ // scanOffset is now start of end of central directory record
+ // the 'offset to central dir' data is at position 16 in the record
+ long offsetToCentralDir = readLong(buffer, scanOffset + 48);
+ return (int) offsetToCentralDir;
+ }
+
/** Read a 32-bit integer from a bytebuffer in little-endian order. */
private static int readInt(byte[] buffer, int offset) {
return Ints.fromBytes(
buffer[offset + 3], buffer[offset + 2], buffer[offset + 1], buffer[offset]);
}
+ /** Read a 64-bit integer from a bytebuffer in little-endian order. */
+ private static long readLong(byte[] buffer, int offset) {
+ return Longs.fromBytes(
+ buffer[offset + 7],
+ buffer[offset + 6],
+ buffer[offset + 5],
+ buffer[offset + 4],
+ buffer[offset + 3],
+ buffer[offset + 2],
+ buffer[offset + 1],
+ buffer[offset]);
+ }
+
/** Read a 16-bit short from a bytebuffer in little-endian order. */
private static short readShort(byte[] buffer, int offset) {
return Shorts.fromBytes(buffer[offset + 1], buffer[offset]);
diff --git a/resources/src/test/java/org/robolectric/res/android/ZipFileROTest.java b/resources/src/test/java/org/robolectric/res/android/ZipFileROTest.java
index eebf3654b..cf48b2109 100644
--- a/resources/src/test/java/org/robolectric/res/android/ZipFileROTest.java
+++ b/resources/src/test/java/org/robolectric/res/android/ZipFileROTest.java
@@ -3,9 +3,12 @@ package org.robolectric.res.android;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.io.ByteStreams;
+import com.google.common.io.Files;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
+import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -63,4 +66,33 @@ public final class ZipFileROTest {
ZipFileRO zipFile = ZipFileRO.open(blob.toString());
assertThat(zipFile).isNotNull();
}
+
+ @Test
+ public void testCreateJar() throws Exception {
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ ZipOutputStream out = new ZipOutputStream(byteArrayOutputStream);
+ // Write 2^16 + 1 entries, forcing zip64 EOCD to be written.
+ for (int i = 0; i < 65537; i++) {
+ out.putNextEntry(new ZipEntry(Integer.toString(i)));
+ out.closeEntry();
+ }
+ out.close();
+ byte[] zipBytes = byteArrayOutputStream.toByteArray();
+ // Write 0xff for the following fields in the EOCD, which some zip libraries do.
+ // Entries in this disk (2 bytes)
+ // Total Entries (2 byte)
+ // Size of Central Dir (4 bytes)
+ // Offset to Central Dir (4 bytes)
+ // Total: 12 bytes
+ for (int i = 0; i < 12; i++) {
+ zipBytes[zipBytes.length - 3 - i] = (byte) 0xff;
+ }
+ File tmpFile = File.createTempFile("zip64eocd", "zip");
+ Files.write(zipBytes, tmpFile);
+ ZipFileRO zro = ZipFileRO.open(tmpFile.getAbsolutePath());
+ assertThat(zro).isNotNull();
+ assertThat(zro.findEntryByName("0")).isNotNull();
+ assertThat(zro.findEntryByName("65536")).isNotNull();
+ assertThat(zro.findEntryByName("65537")).isNull();
+ }
}
diff --git a/robolectric/Android.bp b/robolectric/Android.bp
new file mode 100644
index 000000000..61359ab64
--- /dev/null
+++ b/robolectric/Android.bp
@@ -0,0 +1,98 @@
+//#############################################
+// Compile Robolectric robolectric
+//#############################################
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "external_robolectric-shadows_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["external_robolectric_license"],
+}
+
+java_library_host {
+ name: "Robolectric_robolectric_upstream",
+ libs: [
+ "Robolectric_shadows_framework_upstream",
+ "Robolectric_annotations_upstream",
+ "Robolectric_shadowapi_upstream",
+ "Robolectric_resources_upstream",
+ "Robolectric_sandbox_upstream",
+ "Robolectric_junit_upstream",
+ "Robolectric_utils_upstream",
+ "Robolectric_utils_reflector_upstream",
+ "robolectric-host-androidx-test-ext-junit_upstream",
+ "robolectric-host-androidx-test-monitor_upstream",
+ "robolectric-maven-ant-tasks-2.1.3",
+ "bouncycastle-unbundled",
+ "asm-commons-9.2",
+ "guava",
+ "robolectric-xstream-1.4.8",
+ "asm-tree-9.2",
+ "junit",
+ "robolectric-ant-1.8.0",
+ "asm-9.2",
+ "jsr305",
+ "conscrypt-unbundled",
+ "robolectric-host-androidx_test_espresso",
+ "robolectric-host-android_all_upstream",
+ ],
+ srcs: ["src/main/java/**/*.java"],
+ plugins: ["auto_service_plugin"],
+ java_resources: [":robolectric-version-upstream.properties"],
+}
+
+genrule {
+ name: "robolectric-version-upstream.properties",
+ out: ["robolectric-version-upstream.properties"],
+ cmd: "echo -n 'robolectric.version=4.8.2-SNAPSHOT' > $(out)",
+}
+
+//#############################################
+// Compile Robolectric robolectric tests
+//#############################################
+java_test_host {
+ name: "Robolectric_robolectric_tests_upstream",
+ srcs: ["src/test/java/**/*.java"],
+ java_resource_dirs: ["src/test/resources"],
+ static_libs: [
+ "Robolectric_robolectric_upstream",
+ "Robolectric_shadows_framework_upstream",
+ "Robolectric_annotations_upstream",
+ "Robolectric_shadowapi_upstream",
+ "Robolectric_resources_upstream",
+ "Robolectric_sandbox_upstream",
+ "Robolectric_junit_upstream",
+ "Robolectric_utils_upstream",
+ "Robolectric_utils_reflector_upstream",
+ "robolectric-host-androidx-test-ext-junit_upstream",
+ "robolectric-host-androidx-test-monitor_upstream",
+ "robolectric-host-androidx-test-core_upstream",
+ "robolectric-maven-ant-tasks-2.1.3",
+ "mockito",
+ "bouncycastle-unbundled",
+ "hamcrest",
+ "hamcrest-library",
+ "robolectric-sqlite4java-0.282",
+ "asm-commons-9.2",
+ "robolectric-diffutils-1.3.0",
+ "guava",
+ "objenesis",
+ "robolectric-xstream-1.4.8",
+ "asm-tree-9.2",
+ "junit",
+ "icu4j",
+ "truth-prebuilt",
+ "truth-java8-extension-jar",
+ "robolectric-ant-1.8.0",
+ "asm-9.2",
+ "jsr305",
+ "robolectric-host-androidx_test_espresso",
+ ],
+ libs: ["robolectric-host-android_all_upstream"],
+ // Robolectric tests do not work well with unit tests setup yet
+ test_options: {
+ unit_test: false,
+ },
+}
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 1189c43c6..267cc6df7 100755
--- a/robolectric/src/main/java/org/robolectric/android/internal/AndroidTestEnvironment.java
+++ b/robolectric/src/main/java/org/robolectric/android/internal/AndroidTestEnvironment.java
@@ -83,6 +83,7 @@ import org.robolectric.shadows.ShadowLooper;
import org.robolectric.shadows.ShadowPackageManager;
import org.robolectric.shadows.ShadowPackageParser;
import org.robolectric.shadows.ShadowPackageParser._Package_;
+import org.robolectric.util.Logger;
import org.robolectric.util.PerfStatsCollector;
import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.Scheduler;
@@ -149,6 +150,7 @@ public class AndroidTestEnvironment implements TestEnvironment {
loggingInitialized = true;
}
+ Logger.debug("Robolectric Test Configuration: " + configuration.map());
ConscryptMode.Mode conscryptMode = configuration.get(ConscryptMode.Mode.class);
Security.removeProvider(CONSCRYPT_PROVIDER);
if (conscryptMode != ConscryptMode.Mode.OFF) {
diff --git a/robolectric/src/main/java/org/robolectric/internal/dependency/LocalDependencyResolver.java b/robolectric/src/main/java/org/robolectric/internal/dependency/LocalDependencyResolver.java
index 5a77ede9b..a03f401c4 100644
--- a/robolectric/src/main/java/org/robolectric/internal/dependency/LocalDependencyResolver.java
+++ b/robolectric/src/main/java/org/robolectric/internal/dependency/LocalDependencyResolver.java
@@ -1,8 +1,13 @@
package org.robolectric.internal.dependency;
import java.io.File;
+import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.stream.Stream;
+import org.robolectric.util.Logger;
public class LocalDependencyResolver implements DependencyResolver {
private File offlineJarDir;
@@ -39,6 +44,12 @@ public class LocalDependencyResolver implements DependencyResolver {
*/
private static File validateFile(File file) throws IllegalArgumentException {
if (!file.isFile()) {
+ Logger.error("Directory contents: "+ file.getParentFile());
+ try (Stream<Path> stream = Files.list(file.getParentFile().toPath())) {
+ stream.forEach(s -> Logger.error(s.toString()));
+ } catch (IOException ioe) {
+ Logger.error("Not a directory " + file.getParentFile());
+ }
throw new IllegalArgumentException("Path is not a file: " + file);
}
if (!file.canRead()) {
diff --git a/robolectric/src/main/java/org/robolectric/junit/rules/ExpectedLogMessagesRule.java b/robolectric/src/main/java/org/robolectric/junit/rules/ExpectedLogMessagesRule.java
index 1bcc4cb9b..5b0b9e90c 100644
--- a/robolectric/src/main/java/org/robolectric/junit/rules/ExpectedLogMessagesRule.java
+++ b/robolectric/src/main/java/org/robolectric/junit/rules/ExpectedLogMessagesRule.java
@@ -284,7 +284,7 @@ public final class ExpectedLogMessagesRule implements TestRule {
return type == log.type
&& !(tag != null ? !tag.equals(log.tag) : log.tag != null)
&& !(msg != null ? !msg.equals(log.msg) : log.msg != null)
- && !(msgPattern != null ? !msgPattern.equals(log.msgPattern) : log.msgPattern != null)
+ && !(msgPattern != null ? !isEqual(msgPattern, log.msgPattern) : log.msgPattern != null)
&& !(throwableMatcher != null
? !throwableMatcher.equals(log.throwableMatcher)
: log.throwableMatcher != null);
@@ -292,7 +292,7 @@ public final class ExpectedLogMessagesRule implements TestRule {
@Override
public int hashCode() {
- return Objects.hash(type, tag, msg, msgPattern, throwableMatcher);
+ return Objects.hash(type, tag, msg, hash(msgPattern), throwableMatcher);
}
@Override
@@ -313,5 +313,17 @@ public final class ExpectedLogMessagesRule implements TestRule {
+ throwableStr
+ '}';
}
+
+ /** Returns true if the pattern and flags compiled in a {@link Pattern} were the same. */
+ private static boolean isEqual(Pattern a, Pattern b) {
+ return a != null && b != null
+ ? a.pattern().equals(b.pattern()) && a.flags() == b.flags()
+ : Objects.equals(a, b);
+ }
+
+ /** Returns hash for a {@link Pattern} based on the pattern and flags it was compiled with. */
+ private static int hash(Pattern pattern) {
+ return pattern == null ? 0 : Objects.hash(pattern.pattern(), pattern.flags());
+ }
}
}
diff --git a/robolectric/src/main/java/org/robolectric/plugins/DefaultSdkProvider.java b/robolectric/src/main/java/org/robolectric/plugins/DefaultSdkProvider.java
index 9a02d6b8a..c8b2693ea 100644
--- a/robolectric/src/main/java/org/robolectric/plugins/DefaultSdkProvider.java
+++ b/robolectric/src/main/java/org/robolectric/plugins/DefaultSdkProvider.java
@@ -17,6 +17,9 @@ 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.CUR_DEVELOPMENT;
+
+import android.os.Build;
import com.google.auto.service.AutoService;
import com.google.common.base.Preconditions;
@@ -82,6 +85,8 @@ public class DefaultSdkProvider implements SdkProvider {
knownSdks.put(S, new DefaultSdk(S, "12", "7732740", "REL", 9));
knownSdks.put(S_V2, new DefaultSdk(S_V2, "12.1", "8229987", "REL", 9));
knownSdks.put(TIRAMISU, new DefaultSdk(TIRAMISU, "13", "9030017", "Tiramisu", 9));
+ // TODO(rexhoffman): should this have a dedicated mechanism? Should we maintain a known good version?
+ knownSdks.put(CUR_DEVELOPMENT, new DefaultSdk(CUR_DEVELOPMENT, "current", "r0", "UpsideDownCake", 9));
}
@Override
diff --git a/robolectric/src/main/java/org/robolectric/plugins/GraphicsModeConfigurer.java b/robolectric/src/main/java/org/robolectric/plugins/GraphicsModeConfigurer.java
new file mode 100644
index 000000000..8ff0f6196
--- /dev/null
+++ b/robolectric/src/main/java/org/robolectric/plugins/GraphicsModeConfigurer.java
@@ -0,0 +1,65 @@
+package org.robolectric.plugins;
+
+import com.google.auto.service.AutoService;
+import java.lang.reflect.Method;
+import java.util.Properties;
+import javax.annotation.Nonnull;
+import org.robolectric.annotation.GraphicsMode;
+import org.robolectric.annotation.GraphicsMode.Mode;
+import org.robolectric.pluginapi.config.Configurer;
+
+/** Provides configuration to Robolectric for its @{@link GraphicsMode} annotation. */
+@AutoService(Configurer.class)
+public class GraphicsModeConfigurer implements Configurer<GraphicsMode.Mode> {
+
+ private final Properties systemProperties;
+
+ public GraphicsModeConfigurer(Properties systemProperties) {
+ this.systemProperties = systemProperties;
+ }
+
+ @Override
+ public Class<GraphicsMode.Mode> getConfigClass() {
+ return GraphicsMode.Mode.class;
+ }
+
+ @Nonnull
+ @Override
+ public GraphicsMode.Mode defaultConfig() {
+ return GraphicsMode.Mode.valueOf(
+ systemProperties.getProperty("robolectric.graphicsMode", "LEGACY"));
+ }
+
+ @Override
+ public GraphicsMode.Mode getConfigFor(@Nonnull String packageName) {
+ try {
+ Package pkg = Class.forName(packageName + ".package-info").getPackage();
+ return valueFrom(pkg.getAnnotation(GraphicsMode.class));
+ } catch (ClassNotFoundException e) {
+ // ignore
+ }
+ return null;
+ }
+
+ @Override
+ public GraphicsMode.Mode getConfigFor(@Nonnull Class<?> testClass) {
+ return valueFrom(testClass.getAnnotation(GraphicsMode.class));
+ }
+
+ @Override
+ public GraphicsMode.Mode getConfigFor(@Nonnull Method method) {
+ return valueFrom(method.getAnnotation(GraphicsMode.class));
+ }
+
+ @Nonnull
+ @Override
+ public GraphicsMode.Mode merge(
+ @Nonnull GraphicsMode.Mode parentConfig, @Nonnull GraphicsMode.Mode childConfig) {
+ // just take the childConfig - since GraphicsMode only has a single 'value' attribute
+ return childConfig;
+ }
+
+ private Mode valueFrom(GraphicsMode graphicsMode) {
+ return graphicsMode == null ? null : graphicsMode.value();
+ }
+}
diff --git a/robolectric/src/main/java/org/robolectric/plugins/LegacyDependencyResolver.java b/robolectric/src/main/java/org/robolectric/plugins/LegacyDependencyResolver.java
index aa72656ec..ce095a9cc 100644
--- a/robolectric/src/main/java/org/robolectric/plugins/LegacyDependencyResolver.java
+++ b/robolectric/src/main/java/org/robolectric/plugins/LegacyDependencyResolver.java
@@ -3,9 +3,14 @@ package org.robolectric.plugins;
import com.google.auto.service.AutoService;
import com.google.common.annotations.VisibleForTesting;
import java.io.File;
+import java.io.IOException;
import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Properties;
+import java.util.stream.Collectors;
+
import javax.annotation.Priority;
import javax.inject.Inject;
import org.robolectric.internal.dependency.DependencyJar;
@@ -13,6 +18,7 @@ import org.robolectric.internal.dependency.DependencyResolver;
import org.robolectric.internal.dependency.LocalDependencyResolver;
import org.robolectric.internal.dependency.PropertiesDependencyResolver;
import org.robolectric.res.Fs;
+import org.robolectric.util.Logger;
import org.robolectric.util.ReflectionHelpers;
/**
@@ -60,13 +66,16 @@ public class LegacyDependencyResolver implements DependencyResolver {
private static DependencyResolver pickOne(
Properties properties, DefinitelyNotAClassLoader classLoader) {
String propPath = properties.getProperty("robolectric-deps.properties");
+ Logger.debug("Robolectric-deps.properties path :" + propPath);
if (propPath != null) {
- return new PropertiesDependencyResolver(Paths.get(propPath));
+ Path path = Paths.get(propPath);
+ return new PropertiesDependencyResolver(path);
}
String dependencyDir = properties.getProperty("robolectric.dependency.dir");
if (dependencyDir != null
|| Boolean.parseBoolean(properties.getProperty("robolectric.offline"))) {
+ Logger.debug("Dependency dir: " + dependencyDir);
return new LocalDependencyResolver(new File(dependencyDir == null ? "." : dependencyDir));
}
diff --git a/robolectric/src/test/java/org/robolectric/junit/rules/ExpectedLogMessagesRuleTest.java b/robolectric/src/test/java/org/robolectric/junit/rules/ExpectedLogMessagesRuleTest.java
index 44e607da5..cd0f30191 100644
--- a/robolectric/src/test/java/org/robolectric/junit/rules/ExpectedLogMessagesRuleTest.java
+++ b/robolectric/src/test/java/org/robolectric/junit/rules/ExpectedLogMessagesRuleTest.java
@@ -4,6 +4,7 @@ import static org.hamcrest.CoreMatchers.instanceOf;
import android.util.Log;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import java.util.regex.Pattern;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
import org.junit.Rule;
@@ -206,4 +207,11 @@ public final class ExpectedLogMessagesRuleTest {
}
});
}
+
+ @Test
+ public void expectLogMessageWithPattern_duplicatePatterns() {
+ Log.e("Mytag", "message1");
+ rule.expectLogMessagePattern(Log.ERROR, "Mytag", Pattern.compile("message1"));
+ rule.expectLogMessagePattern(Log.ERROR, "Mytag", Pattern.compile("message1"));
+ }
}
diff --git a/robolectric/src/test/java/org/robolectric/plugins/GraphicsModeConfigurerTest.java b/robolectric/src/test/java/org/robolectric/plugins/GraphicsModeConfigurerTest.java
new file mode 100644
index 000000000..dae47d316
--- /dev/null
+++ b/robolectric/src/test/java/org/robolectric/plugins/GraphicsModeConfigurerTest.java
@@ -0,0 +1,21 @@
+package org.robolectric.plugins;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import java.util.Properties;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.robolectric.annotation.GraphicsMode;
+import org.robolectric.annotation.GraphicsMode.Mode;
+
+/** Unit tests for methods annotated with @{@link GraphicsMode}. */
+@RunWith(JUnit4.class)
+public class GraphicsModeConfigurerTest {
+ @Test
+ public void defaultConfig() {
+ Properties systemProperties = new Properties();
+ GraphicsModeConfigurer configurer = new GraphicsModeConfigurer(systemProperties);
+ assertThat(configurer.defaultConfig()).isSameInstanceAs(Mode.LEGACY);
+ }
+}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ResponderLocationBuilderTest.java b/robolectric/src/test/java/org/robolectric/shadows/ResponderLocationBuilderTest.java
new file mode 100644
index 000000000..0478046fd
--- /dev/null
+++ b/robolectric/src/test/java/org/robolectric/shadows/ResponderLocationBuilderTest.java
@@ -0,0 +1,93 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.Q;
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import android.net.wifi.rtt.ResponderLocation;
+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 final class ResponderLocationBuilderTest {
+
+ @Test
+ @Config(minSdk = Q)
+ public void getNewInstance_wouldHaveEmptySubelements() {
+ ResponderLocation responderLocation = ResponderLocationBuilder.newBuilder().build();
+
+ assertThat(responderLocation.isLciSubelementValid()).isFalse();
+ assertThat(responderLocation.isZaxisSubelementValid()).isFalse();
+ }
+
+ @Test
+ @Config(minSdk = Q)
+ public void settingAllLciSubelementFieldsWithNoZaxisFields() {
+ ResponderLocation responderLocation =
+ ResponderLocationBuilder.newBuilder()
+ .setAltitude(498.9)
+ .setAltitudeUncertainty(2.0)
+ .setLatitude(29.1)
+ .setLatitudeUncertainty(3.4)
+ .setLongitude(87.1)
+ .setLongitudeUncertainty(5.4)
+ .setAltitudeType(ResponderLocation.ALTITUDE_UNDEFINED)
+ .setLciVersion(ResponderLocation.LCI_VERSION_1)
+ .setLciRegisteredLocationAgreement(true)
+ .setDatum(1)
+ .build();
+
+ assertThat(responderLocation.isLciSubelementValid()).isTrue();
+ assertThat(responderLocation.isZaxisSubelementValid()).isFalse();
+ assertThrows(IllegalStateException.class, () -> responderLocation.getFloorNumber());
+ }
+
+ @Test
+ @Config(minSdk = Q)
+ public void settingPartsOfLciSubelementFields() {
+ ResponderLocation responderLocation =
+ ResponderLocationBuilder.newBuilder()
+ .setAltitude(498.9)
+ .setAltitudeUncertainty(2.0)
+ .setLatitude(29.1)
+ .setLatitudeUncertainty(3.4)
+ .setLongitude(87.1)
+ .setLongitudeUncertainty(5.4)
+ .setLciVersion(ResponderLocation.LCI_VERSION_1)
+ .setLciRegisteredLocationAgreement(true)
+ .setDatum(1)
+ .build();
+
+ assertThat(responderLocation.isLciSubelementValid()).isFalse();
+ assertThat(responderLocation.isZaxisSubelementValid()).isFalse();
+ assertThrows(IllegalStateException.class, () -> responderLocation.getAltitude());
+ assertThrows(IllegalStateException.class, () -> responderLocation.getFloorNumber());
+ }
+
+ @Test
+ @Config(minSdk = Q)
+ public void settingAllLciSubelementAndZaxisSubelementFields() {
+ ResponderLocation responderLocation =
+ ResponderLocationBuilder.newBuilder()
+ .setAltitude(498.9)
+ .setAltitudeUncertainty(2.0)
+ .setLatitude(29.1)
+ .setLatitudeUncertainty(3.4)
+ .setLongitude(87.1)
+ .setLongitudeUncertainty(5.4)
+ .setAltitudeType(ResponderLocation.ALTITUDE_METERS)
+ .setLciVersion(ResponderLocation.LCI_VERSION_1)
+ .setLciRegisteredLocationAgreement(true)
+ .setDatum(1)
+ .setHeightAboveFloorMeters(2.1)
+ .setHeightAboveFloorUncertaintyMeters(0.1)
+ .setFloorNumber(3.0)
+ .setExpectedToMove(1)
+ .build();
+
+ assertThat(responderLocation.isLciSubelementValid()).isTrue();
+ assertThat(responderLocation.isZaxisSubelementValid()).isTrue();
+ }
+}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowAbstractCursorTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowAbstractCursorTest.java
index 100f840c5..e7bdce690 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowAbstractCursorTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowAbstractCursorTest.java
@@ -12,7 +12,6 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.Shadows;
@RunWith(AndroidJUnit4.class)
public class ShadowAbstractCursorTest {
@@ -212,11 +211,10 @@ public class ShadowAbstractCursorTest {
@Test
public void testGetNotificationUri() {
Uri uri = Uri.parse("content://foo.com");
- ShadowAbstractCursor shadow = Shadows.shadowOf(cursor);
- assertThat(shadow.getNotificationUri_Compatibility()).isNull();
+ assertThat(cursor.getNotificationUri()).isNull();
cursor.setNotificationUri(
ApplicationProvider.getApplicationContext().getContentResolver(), uri);
- assertThat(shadow.getNotificationUri_Compatibility()).isEqualTo(uri);
+ assertThat(cursor.getNotificationUri()).isEqualTo(uri);
}
@Test
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowAccessibilityNodeInfoTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowAccessibilityNodeInfoTest.java
index 4ac1f2e7d..14c91f404 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowAccessibilityNodeInfoTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowAccessibilityNodeInfoTest.java
@@ -245,6 +245,18 @@ public class ShadowAccessibilityNodeInfoTest {
}
@Test
+ @Config(minSdk = P)
+ public void clone_preservesPaneTitle() {
+ String title = "pane title";
+ AccessibilityNodeInfo node = AccessibilityNodeInfo.obtain();
+ node.setPaneTitle(title);
+
+ AccessibilityNodeInfo clone = AccessibilityNodeInfo.obtain(node);
+
+ assertThat(clone.getPaneTitle().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/ShadowAlarmManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowAlarmManagerTest.java
index c8e1152f3..7a6ab89af 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowAlarmManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowAlarmManagerTest.java
@@ -1,52 +1,50 @@
package org.robolectric.shadows;
-import static android.app.AlarmManager.INTERVAL_HOUR;
-import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
-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;
-import static android.os.Build.VERSION_CODES.S;
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import static org.robolectric.Shadows.shadowOf;
-import android.app.Activity;
import android.app.AlarmManager;
import android.app.AlarmManager.AlarmClockInfo;
import android.app.AlarmManager.OnAlarmListener;
import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.os.Build.VERSION_CODES;
-import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.os.WorkSource;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import java.util.Date;
+import java.time.Duration;
+import java.util.Objects;
import java.util.TimeZone;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.annotation.Nullable;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.robolectric.Robolectric;
import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowAlarmManager.ScheduledAlarm;
@RunWith(AndroidJUnit4.class)
public class ShadowAlarmManagerTest {
private Context context;
- private Activity activity;
private AlarmManager alarmManager;
- private ShadowAlarmManager shadowAlarmManager;
@Before
public void setUp() {
context = ApplicationProvider.getApplicationContext();
alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
- shadowAlarmManager = shadowOf(alarmManager);
- activity = Robolectric.setupActivity(Activity.class);
- TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles"));
- assertThat(TimeZone.getDefault().getID()).isEqualTo("America/Los_Angeles");
+ ShadowAlarmManager.setAutoSchedule(true);
}
@Test
@@ -64,13 +62,7 @@ public class ShadowAlarmManagerTest {
@Test
@Config(minSdk = VERSION_CODES.M)
public void setTimeZone_abbreviateTimeZone_ignore() {
- try {
- alarmManager.setTimeZone("PST");
- fail("IllegalArgumentException not thrown");
- } catch (IllegalArgumentException e) {
- // expected
- }
- assertThat(TimeZone.getDefault().getID()).isEqualTo("America/Los_Angeles");
+ assertThrows(IllegalArgumentException.class, () -> alarmManager.setTimeZone("PST"));
}
@Test
@@ -83,13 +75,7 @@ public class ShadowAlarmManagerTest {
@Test
@Config(minSdk = VERSION_CODES.M)
public void setTimeZone_invalidTimeZone_ignore() {
- try {
- alarmManager.setTimeZone("-07:00");
- fail("IllegalArgumentException not thrown");
- } catch (IllegalArgumentException e) {
- // expected
- }
- assertThat(TimeZone.getDefault().getID()).isEqualTo("America/Los_Angeles");
+ assertThrows(IllegalArgumentException.class, () -> alarmManager.setTimeZone("-07:00"));
}
@Test
@@ -100,361 +86,623 @@ public class ShadowAlarmManagerTest {
}
@Test
- public void set_shouldRegisterAlarm() {
- assertThat(shadowAlarmManager.getNextScheduledAlarm()).isNull();
+ public void set_pendingIntent() {
+ Runnable onFire = mock(Runnable.class);
+ try (TestBroadcastListener listener = new TestBroadcastListener(onFire, "action").register()) {
+ alarmManager.set(
+ AlarmManager.ELAPSED_REALTIME,
+ SystemClock.elapsedRealtime() + 10,
+ listener.getPendingIntent());
+
+ ScheduledAlarm alarm = shadowOf(alarmManager).peekNextScheduledAlarm();
+ assertThat(alarm).isNotNull();
+ assertThat(alarm.getType()).isEqualTo(AlarmManager.ELAPSED_REALTIME);
+ assertThat(alarm.getTriggerAtMs()).isEqualTo(SystemClock.elapsedRealtime() + 10);
+
+ shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(10));
+ verify(onFire).run();
+ }
+ }
+
+ @Config(minSdk = VERSION_CODES.N)
+ @Test
+ public void set_alarmListener() {
+ OnAlarmListener onFire = mock(OnAlarmListener.class);
alarmManager.set(
- AlarmManager.ELAPSED_REALTIME,
- 0,
- PendingIntent.getActivity(activity, 0, new Intent(activity, activity.getClass()), 0));
+ AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 10, "tag", onFire, null);
+
+ ScheduledAlarm alarm = shadowOf(alarmManager).peekNextScheduledAlarm();
+ assertThat(alarm).isNotNull();
+ assertThat(alarm.getType()).isEqualTo(AlarmManager.ELAPSED_REALTIME);
+ assertThat(alarm.getTriggerAtMs()).isEqualTo(SystemClock.elapsedRealtime() + 10);
+ assertThat(alarm.getTag()).isEqualTo("tag");
- ShadowAlarmManager.ScheduledAlarm scheduledAlarm = shadowAlarmManager.getNextScheduledAlarm();
- assertThat(scheduledAlarm).isNotNull();
- assertThat(scheduledAlarm.allowWhileIdle).isFalse();
+ shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(10));
+ verify(onFire).onAlarm();
}
@Test
- @Config(minSdk = N)
- public void set_shouldRegisterAlarm_forApi24() {
- assertThat(shadowAlarmManager.getNextScheduledAlarm()).isNull();
- OnAlarmListener listener = () -> {};
- alarmManager.set(AlarmManager.ELAPSED_REALTIME, 0, "tag", listener, null);
- assertThat(shadowAlarmManager.getNextScheduledAlarm()).isNotNull();
+ public void setRepeating_pendingIntent() {
+ Runnable onFire = mock(Runnable.class);
+ try (TestBroadcastListener listener = new TestBroadcastListener(onFire, "action").register()) {
+ alarmManager.setRepeating(
+ AlarmManager.ELAPSED_REALTIME,
+ SystemClock.elapsedRealtime() + 10,
+ 20L,
+ listener.getPendingIntent());
+
+ ScheduledAlarm alarm = shadowOf(alarmManager).peekNextScheduledAlarm();
+ assertThat(alarm).isNotNull();
+ assertThat(alarm.getType()).isEqualTo(AlarmManager.ELAPSED_REALTIME);
+ assertThat(alarm.getTriggerAtMs()).isEqualTo(SystemClock.elapsedRealtime() + 10);
+ assertThat(alarm.getIntervalMs()).isEqualTo(20);
+
+ shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(10));
+ verify(onFire, times(1)).run();
+
+ alarm = shadowOf(alarmManager).peekNextScheduledAlarm();
+ assertThat(alarm).isNotNull();
+ assertThat(alarm.getType()).isEqualTo(AlarmManager.ELAPSED_REALTIME);
+ assertThat(alarm.getTriggerAtMs()).isEqualTo(SystemClock.elapsedRealtime() + 20);
+ assertThat(alarm.getIntervalMs()).isEqualTo(20);
+
+ shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(20));
+ verify(onFire, times(2)).run();
+ }
+
+ shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(20));
+ verify(onFire, times(2)).run();
}
+ @Config(minSdk = VERSION_CODES.KITKAT)
@Test
- @Config(minSdk = M)
- public void setAndAllowWhileIdle_shouldRegisterAlarm() {
- assertThat(shadowAlarmManager.getNextScheduledAlarm()).isNull();
- alarmManager.setAndAllowWhileIdle(
- AlarmManager.ELAPSED_REALTIME,
- 0,
- PendingIntent.getActivity(activity, 0, new Intent(activity, activity.getClass()), 0));
-
- ShadowAlarmManager.ScheduledAlarm scheduledAlarm = shadowAlarmManager.getNextScheduledAlarm();
- assertThat(scheduledAlarm).isNotNull();
- assertThat(scheduledAlarm.allowWhileIdle).isTrue();
+ public void setWindow_pendingIntent() {
+ Runnable onFire = mock(Runnable.class);
+ try (TestBroadcastListener listener = new TestBroadcastListener(onFire, "action").register()) {
+ alarmManager.setWindow(
+ AlarmManager.ELAPSED_REALTIME,
+ SystemClock.elapsedRealtime() + 10,
+ 20L,
+ listener.getPendingIntent());
+
+ ScheduledAlarm alarm = shadowOf(alarmManager).peekNextScheduledAlarm();
+ assertThat(alarm).isNotNull();
+ assertThat(alarm.getType()).isEqualTo(AlarmManager.ELAPSED_REALTIME);
+ assertThat(alarm.getTriggerAtMs()).isEqualTo(SystemClock.elapsedRealtime() + 10);
+ assertThat(alarm.getWindowLengthMs()).isEqualTo(20);
+
+ shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(10));
+ verify(onFire).run();
+ }
}
+ @Config(minSdk = VERSION_CODES.N)
@Test
- @Config(minSdk = M)
- public void setExactAndAllowWhileIdle_shouldRegisterAlarm() {
- assertThat(shadowAlarmManager.getNextScheduledAlarm()).isNull();
- alarmManager.setExactAndAllowWhileIdle(
+ public void setWindow_alarmListener() {
+ OnAlarmListener onFire = mock(OnAlarmListener.class);
+ alarmManager.setWindow(
AlarmManager.ELAPSED_REALTIME,
- 0,
- PendingIntent.getActivity(activity, 0, new Intent(activity, activity.getClass()), 0));
+ SystemClock.elapsedRealtime() + 10,
+ 20L,
+ "tag",
+ onFire,
+ null);
+
+ ScheduledAlarm alarm = shadowOf(alarmManager).peekNextScheduledAlarm();
+ assertThat(alarm).isNotNull();
+ assertThat(alarm.getType()).isEqualTo(AlarmManager.ELAPSED_REALTIME);
+ assertThat(alarm.getTriggerAtMs()).isEqualTo(SystemClock.elapsedRealtime() + 10);
+ assertThat(alarm.getWindowLengthMs()).isEqualTo(20);
+ assertThat(alarm.getTag()).isEqualTo("tag");
- ShadowAlarmManager.ScheduledAlarm scheduledAlarm = shadowAlarmManager.getNextScheduledAlarm();
- assertThat(scheduledAlarm).isNotNull();
- assertThat(scheduledAlarm.allowWhileIdle).isTrue();
+ shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(10));
+ verify(onFire).onAlarm();
}
+ @Config(minSdk = VERSION_CODES.S)
@Test
- @Config(minSdk = KITKAT)
- public void setExact_shouldRegisterAlarm_forApi19() {
- assertThat(shadowAlarmManager.getNextScheduledAlarm()).isNull();
- alarmManager.setExact(
+ public void setPrioritized_alarmListener() {
+ OnAlarmListener onFire = mock(OnAlarmListener.class);
+ alarmManager.setPrioritized(
AlarmManager.ELAPSED_REALTIME,
- 0,
- PendingIntent.getActivity(activity, 0, new Intent(activity, activity.getClass()), 0));
- assertThat(shadowAlarmManager.getNextScheduledAlarm()).isNotNull();
- }
+ SystemClock.elapsedRealtime() + 10,
+ 20L,
+ "tag",
+ Runnable::run,
+ onFire);
- @Test
- @Config(minSdk = N)
- public void setExact_shouldRegisterAlarm_forApi124() {
- assertThat(shadowAlarmManager.getNextScheduledAlarm()).isNull();
- OnAlarmListener listener = () -> {};
- alarmManager.setExact(AlarmManager.ELAPSED_REALTIME, 0, "tag", listener, null);
- assertThat(shadowAlarmManager.getNextScheduledAlarm()).isNotNull();
+ ScheduledAlarm alarm = shadowOf(alarmManager).peekNextScheduledAlarm();
+ assertThat(alarm).isNotNull();
+ assertThat(alarm.getType()).isEqualTo(AlarmManager.ELAPSED_REALTIME);
+ assertThat(alarm.getTriggerAtMs()).isEqualTo(SystemClock.elapsedRealtime() + 10);
+ assertThat(alarm.getWindowLengthMs()).isEqualTo(20);
+ assertThat(alarm.getTag()).isEqualTo("tag");
+
+ shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(10));
+ verify(onFire).onAlarm();
}
+ @Config(minSdk = VERSION_CODES.KITKAT)
@Test
- @Config(minSdk = KITKAT)
- public void setWindow_shouldRegisterAlarm_forApi19() {
- assertThat(shadowAlarmManager.getNextScheduledAlarm()).isNull();
- alarmManager.setWindow(
- AlarmManager.ELAPSED_REALTIME,
- 0,
- 1,
- PendingIntent.getActivity(activity, 0, new Intent(activity, activity.getClass()), 0));
- assertThat(shadowAlarmManager.getNextScheduledAlarm()).isNotNull();
+ public void setExact_pendingIntent() {
+ Runnable onFire = mock(Runnable.class);
+ try (TestBroadcastListener listener = new TestBroadcastListener(onFire, "action").register()) {
+ alarmManager.setExact(
+ AlarmManager.ELAPSED_REALTIME,
+ SystemClock.elapsedRealtime() + 10,
+ listener.getPendingIntent());
+
+ ScheduledAlarm alarm = shadowOf(alarmManager).peekNextScheduledAlarm();
+ assertThat(alarm).isNotNull();
+ assertThat(alarm.getType()).isEqualTo(AlarmManager.ELAPSED_REALTIME);
+ assertThat(alarm.getTriggerAtMs()).isEqualTo(SystemClock.elapsedRealtime() + 10);
+
+ shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(10));
+ verify(onFire).run();
+ }
}
+ @Config(minSdk = VERSION_CODES.N)
@Test
- @Config(minSdk = N)
- public void setWindow_shouldRegisterAlarm_forApi24() {
- assertThat(shadowAlarmManager.getNextScheduledAlarm()).isNull();
- OnAlarmListener listener = () -> {};
- alarmManager.setWindow(AlarmManager.ELAPSED_REALTIME, 0, 1, "tag", listener, null);
- assertThat(shadowAlarmManager.getNextScheduledAlarm()).isNotNull();
+ public void setExact_alarmListener() {
+ OnAlarmListener onFire = mock(OnAlarmListener.class);
+ alarmManager.setExact(
+ AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 10, "tag", onFire, null);
+
+ ScheduledAlarm alarm = shadowOf(alarmManager).peekNextScheduledAlarm();
+ assertThat(alarm).isNotNull();
+ assertThat(alarm.getType()).isEqualTo(AlarmManager.ELAPSED_REALTIME);
+ assertThat(alarm.getTriggerAtMs()).isEqualTo(SystemClock.elapsedRealtime() + 10);
+ assertThat(alarm.getTag()).isEqualTo("tag");
+
+ shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(10));
+ verify(onFire).onAlarm();
}
+ @Config(minSdk = VERSION_CODES.LOLLIPOP)
@Test
- public void setRepeating_shouldRegisterAlarm() {
- assertThat(shadowAlarmManager.getNextScheduledAlarm()).isNull();
- alarmManager.setRepeating(
- AlarmManager.ELAPSED_REALTIME,
- 0,
- INTERVAL_HOUR,
- PendingIntent.getActivity(activity, 0, new Intent(activity, activity.getClass()), 0));
- assertThat(shadowAlarmManager.getNextScheduledAlarm()).isNotNull();
+ public void setAlarmClock_pendingIntent() {
+ AlarmClockInfo alarmClockInfo =
+ new AlarmClockInfo(
+ SystemClock.elapsedRealtime() + 10,
+ PendingIntent.getBroadcast(context, 0, new Intent("show"), 0));
+
+ Runnable onFire = mock(Runnable.class);
+ try (TestBroadcastListener listener = new TestBroadcastListener(onFire, "action").register()) {
+ alarmManager.setAlarmClock(alarmClockInfo, listener.getPendingIntent());
+
+ ScheduledAlarm alarm = shadowOf(alarmManager).peekNextScheduledAlarm();
+ assertThat(alarm).isNotNull();
+ assertThat(alarm.getType()).isEqualTo(AlarmManager.RTC_WAKEUP);
+ assertThat(alarm.getTriggerAtMs()).isEqualTo(SystemClock.elapsedRealtime() + 10);
+ assertThat(alarm.getAlarmClockInfo()).isEqualTo(alarmClockInfo);
+
+ shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(10));
+ verify(onFire).run();
+ }
}
+ @Config(minSdk = VERSION_CODES.KITKAT)
@Test
- public void set_shouldReplaceAlarmsWithSameIntentReceiver() {
- alarmManager.set(
- AlarmManager.ELAPSED_REALTIME,
- 500,
- PendingIntent.getActivity(activity, 0, new Intent(activity, activity.getClass()), 0));
- alarmManager.set(
- AlarmManager.ELAPSED_REALTIME,
- 1000,
- PendingIntent.getActivity(activity, 0, new Intent(activity, activity.getClass()), 0));
- assertThat(shadowAlarmManager.getScheduledAlarms()).hasSize(1);
+ public void set_pendingIntent_workSource() {
+ Runnable onFire = mock(Runnable.class);
+ try (TestBroadcastListener listener = new TestBroadcastListener(onFire, "action").register()) {
+ alarmManager.set(
+ AlarmManager.ELAPSED_REALTIME,
+ SystemClock.elapsedRealtime() + 10,
+ 20L,
+ 0L,
+ listener.getPendingIntent(),
+ new WorkSource());
+
+ ScheduledAlarm alarm = shadowOf(alarmManager).peekNextScheduledAlarm();
+ assertThat(alarm).isNotNull();
+ assertThat(alarm.getType()).isEqualTo(AlarmManager.ELAPSED_REALTIME);
+ assertThat(alarm.getTriggerAtMs()).isEqualTo(SystemClock.elapsedRealtime() + 10);
+ assertThat(alarm.getWindowLengthMs()).isEqualTo(20);
+ assertThat(alarm.getIntervalMs()).isEqualTo(0);
+ assertThat(alarm.getWorkSource()).isEqualTo(new WorkSource());
+
+ shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(10));
+ verify(onFire).run();
+ }
}
+ @Config(minSdk = VERSION_CODES.N)
@Test
- public void set_shouldReplaceDuplicates() {
+ public void set_alarmListener_workSource() {
+ OnAlarmListener onFire = mock(OnAlarmListener.class);
alarmManager.set(
AlarmManager.ELAPSED_REALTIME,
- 0,
- PendingIntent.getActivity(activity, 0, new Intent(activity, activity.getClass()), 0));
+ SystemClock.elapsedRealtime() + 10,
+ 20L,
+ 0L,
+ "tag",
+ onFire,
+ null,
+ new WorkSource());
+
+ ScheduledAlarm alarm = shadowOf(alarmManager).peekNextScheduledAlarm();
+ assertThat(alarm).isNotNull();
+ assertThat(alarm.getType()).isEqualTo(AlarmManager.ELAPSED_REALTIME);
+ assertThat(alarm.getTriggerAtMs()).isEqualTo(SystemClock.elapsedRealtime() + 10);
+ assertThat(alarm.getWindowLengthMs()).isEqualTo(20);
+ assertThat(alarm.getIntervalMs()).isEqualTo(0);
+ assertThat(alarm.getTag()).isEqualTo("tag");
+ assertThat(alarm.getWorkSource()).isEqualTo(new WorkSource());
+
+ shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(10));
+ verify(onFire).onAlarm();
+ }
+
+ @Config(minSdk = VERSION_CODES.N)
+ @Test
+ public void set_alarmListener_workSource_noTag() {
+ OnAlarmListener onFire = mock(OnAlarmListener.class);
alarmManager.set(
AlarmManager.ELAPSED_REALTIME,
- 0,
- PendingIntent.getActivity(activity, 0, new Intent(activity, activity.getClass()), 0));
- assertThat(shadowAlarmManager.getScheduledAlarms()).hasSize(1);
- }
-
+ SystemClock.elapsedRealtime() + 10,
+ 20L,
+ 0L,
+ onFire,
+ null,
+ new WorkSource());
+
+ ScheduledAlarm alarm = shadowOf(alarmManager).peekNextScheduledAlarm();
+ assertThat(alarm).isNotNull();
+ assertThat(alarm.getType()).isEqualTo(AlarmManager.ELAPSED_REALTIME);
+ assertThat(alarm.getTriggerAtMs()).isEqualTo(SystemClock.elapsedRealtime() + 10);
+ assertThat(alarm.getWindowLengthMs()).isEqualTo(20);
+ assertThat(alarm.getIntervalMs()).isEqualTo(0);
+ assertThat(alarm.getWorkSource()).isEqualTo(new WorkSource());
+
+ shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(10));
+ verify(onFire).onAlarm();
+ }
+
+ @Config(minSdk = VERSION_CODES.S)
@Test
- public void setRepeating_shouldReplaceDuplicates() {
- alarmManager.setRepeating(
- AlarmManager.ELAPSED_REALTIME,
- 0,
- INTERVAL_HOUR,
- PendingIntent.getActivity(activity, 0, new Intent(activity, activity.getClass()), 0));
- alarmManager.setRepeating(
+ public void setExact_alarmListener_workSource() {
+ OnAlarmListener onFire = mock(OnAlarmListener.class);
+ alarmManager.setExact(
AlarmManager.ELAPSED_REALTIME,
- 0,
- INTERVAL_HOUR,
- PendingIntent.getActivity(activity, 0, new Intent(activity, activity.getClass()), 0));
- assertThat(shadowAlarmManager.getScheduledAlarms()).hasSize(1);
- }
-
- @Test
- @SuppressWarnings("JavaUtilDate")
- public void shouldSupportGetNextScheduledAlarm() {
- assertThat(shadowAlarmManager.getNextScheduledAlarm()).isNull();
+ SystemClock.elapsedRealtime() + 10,
+ "tag",
+ Runnable::run,
+ new WorkSource(),
+ onFire);
- long now = new Date().getTime();
- Intent intent = new Intent(activity, activity.getClass());
- PendingIntent pendingIntent = PendingIntent.getActivity(activity, 0, intent, 0);
- alarmManager.set(AlarmManager.ELAPSED_REALTIME, now, pendingIntent);
+ ScheduledAlarm alarm = shadowOf(alarmManager).peekNextScheduledAlarm();
+ assertThat(alarm).isNotNull();
+ assertThat(alarm.getType()).isEqualTo(AlarmManager.ELAPSED_REALTIME);
+ assertThat(alarm.getTriggerAtMs()).isEqualTo(SystemClock.elapsedRealtime() + 10);
+ assertThat(alarm.getTag()).isEqualTo("tag");
+ assertThat(alarm.getWorkSource()).isEqualTo(new WorkSource());
- ShadowAlarmManager.ScheduledAlarm scheduledAlarm = shadowAlarmManager.getNextScheduledAlarm();
- assertThat(shadowAlarmManager.getNextScheduledAlarm()).isNull();
- assertScheduledAlarm(now, pendingIntent, scheduledAlarm);
+ shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(10));
+ verify(onFire).onAlarm();
}
@Test
- @SuppressWarnings("JavaUtilDate")
- public void getNextScheduledAlarm_shouldReturnRepeatingAlarms() {
- assertThat(shadowAlarmManager.getNextScheduledAlarm()).isNull();
-
- long now = new Date().getTime();
- Intent intent = new Intent(activity, activity.getClass());
- PendingIntent pendingIntent = PendingIntent.getActivity(activity, 0, intent, 0);
- alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, now, INTERVAL_HOUR, pendingIntent);
+ public void setInexactRepeating_pendingIntent() {
+ Runnable onFire = mock(Runnable.class);
+ try (TestBroadcastListener listener = new TestBroadcastListener(onFire, "action").register()) {
+ alarmManager.setInexactRepeating(
+ AlarmManager.ELAPSED_REALTIME,
+ SystemClock.elapsedRealtime() + 10,
+ 20L,
+ listener.getPendingIntent());
+
+ ScheduledAlarm alarm = shadowOf(alarmManager).peekNextScheduledAlarm();
+ assertThat(alarm).isNotNull();
+ assertThat(alarm.getType()).isEqualTo(AlarmManager.ELAPSED_REALTIME);
+ assertThat(alarm.getTriggerAtMs()).isEqualTo(SystemClock.elapsedRealtime() + 10);
+ assertThat(alarm.getIntervalMs()).isEqualTo(20);
+
+ shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(10));
+ verify(onFire, times(1)).run();
+
+ shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(20));
+ verify(onFire, times(2)).run();
+ }
- ShadowAlarmManager.ScheduledAlarm scheduledAlarm = shadowAlarmManager.getNextScheduledAlarm();
- assertThat(shadowAlarmManager.getNextScheduledAlarm()).isNull();
- assertRepeatingScheduledAlarm(now, INTERVAL_HOUR, pendingIntent, scheduledAlarm);
+ shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(20));
+ verify(onFire, times(2)).run();
}
+ @Config(minSdk = VERSION_CODES.M)
@Test
- @SuppressWarnings("JavaUtilDate")
- public void peekNextScheduledAlarm_shouldReturnNextAlarm() {
- assertThat(shadowAlarmManager.getNextScheduledAlarm()).isNull();
-
- long now = new Date().getTime();
- Intent intent = new Intent(activity, activity.getClass());
- PendingIntent pendingIntent = PendingIntent.getActivity(activity, 0, intent, 0);
- alarmManager.set(AlarmManager.ELAPSED_REALTIME, now, pendingIntent);
-
- ShadowAlarmManager.ScheduledAlarm scheduledAlarm = shadowAlarmManager.peekNextScheduledAlarm();
- assertThat(shadowAlarmManager.peekNextScheduledAlarm()).isNotNull();
- assertScheduledAlarm(now, pendingIntent, scheduledAlarm);
+ public void setAndAllowWhileIdle_pendingIntent() {
+ Runnable onFire = mock(Runnable.class);
+ try (TestBroadcastListener listener = new TestBroadcastListener(onFire, "action").register()) {
+ alarmManager.setAndAllowWhileIdle(
+ AlarmManager.ELAPSED_REALTIME,
+ SystemClock.elapsedRealtime() + 10,
+ listener.getPendingIntent());
+
+ ScheduledAlarm alarm = shadowOf(alarmManager).peekNextScheduledAlarm();
+ assertThat(alarm).isNotNull();
+ assertThat(alarm.getType()).isEqualTo(AlarmManager.ELAPSED_REALTIME);
+ assertThat(alarm.getTriggerAtMs()).isEqualTo(SystemClock.elapsedRealtime() + 10);
+ assertThat(alarm.isAllowWhileIdle()).isTrue();
+
+ shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(10));
+ verify(onFire, times(1)).run();
+ }
}
+ @Config(minSdk = VERSION_CODES.M)
@Test
- public void cancel_removesMatchingPendingIntents() {
- Intent intent = new Intent(context, String.class);
- PendingIntent pendingIntent =
- PendingIntent.getBroadcast(context, 0, intent, FLAG_UPDATE_CURRENT);
- alarmManager.set(AlarmManager.RTC, 1337, pendingIntent);
-
- Intent intent2 = new Intent(context, Integer.class);
- PendingIntent pendingIntent2 =
- PendingIntent.getBroadcast(context, 0, intent2, FLAG_UPDATE_CURRENT);
- alarmManager.set(AlarmManager.RTC, 1337, pendingIntent2);
-
- assertThat(shadowAlarmManager.getScheduledAlarms()).hasSize(2);
-
- Intent intent3 = new Intent(context, String.class);
- PendingIntent pendingIntent3 =
- PendingIntent.getBroadcast(context, 0, intent3, FLAG_UPDATE_CURRENT);
- alarmManager.cancel(pendingIntent3);
-
- assertThat(shadowAlarmManager.getScheduledAlarms()).hasSize(1);
+ public void setExactAndAllowWhileIdle_pendingIntent() {
+ Runnable onFire = mock(Runnable.class);
+ try (TestBroadcastListener listener = new TestBroadcastListener(onFire, "action").register()) {
+ alarmManager.setExactAndAllowWhileIdle(
+ AlarmManager.ELAPSED_REALTIME,
+ SystemClock.elapsedRealtime() + 10,
+ listener.getPendingIntent());
+
+ ScheduledAlarm alarm = shadowOf(alarmManager).peekNextScheduledAlarm();
+ assertThat(alarm).isNotNull();
+ assertThat(alarm.getType()).isEqualTo(AlarmManager.ELAPSED_REALTIME);
+ assertThat(alarm.getTriggerAtMs()).isEqualTo(SystemClock.elapsedRealtime() + 10);
+ assertThat(alarm.isAllowWhileIdle()).isTrue();
+
+ shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(10));
+ verify(onFire, times(1)).run();
+ }
}
@Test
- public void cancel_removesMatchingPendingIntentsWithActions() {
- Intent newIntent = new Intent("someAction");
- PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, newIntent, 0);
-
- alarmManager.set(AlarmManager.RTC, 1337, pendingIntent);
- assertThat(shadowAlarmManager.getScheduledAlarms()).hasSize(1);
-
- alarmManager.cancel(PendingIntent.getBroadcast(context, 0, new Intent("anotherAction"), 0));
- assertThat(shadowAlarmManager.getScheduledAlarms()).hasSize(1);
-
- alarmManager.cancel(PendingIntent.getBroadcast(context, 0, new Intent("someAction"), 0));
- assertThat(shadowAlarmManager.getScheduledAlarms()).hasSize(0);
+ public void cancel_pendingIntent() {
+ Runnable onFire1 = mock(Runnable.class);
+ Runnable onFire2 = mock(Runnable.class);
+ try (TestBroadcastListener listener1 =
+ new TestBroadcastListener(onFire1, "action1").register();
+ TestBroadcastListener listener2 =
+ new TestBroadcastListener(onFire2, "action2").register()) {
+ alarmManager.set(
+ AlarmManager.ELAPSED_REALTIME,
+ SystemClock.elapsedRealtime() + 20,
+ listener1.getPendingIntent());
+ alarmManager.set(
+ AlarmManager.ELAPSED_REALTIME,
+ SystemClock.elapsedRealtime() + 10,
+ listener2.getPendingIntent());
+
+ assertThat(shadowOf(alarmManager).getScheduledAlarms()).hasSize(2);
+ ScheduledAlarm alarm = shadowOf(alarmManager).peekNextScheduledAlarm();
+ assertThat(alarm).isNotNull();
+ assertThat(alarm.getTriggerAtMs()).isEqualTo(SystemClock.elapsedRealtime() + 10);
+
+ alarmManager.cancel(listener2.getPendingIntent());
+
+ assertThat(shadowOf(alarmManager).getScheduledAlarms()).hasSize(1);
+ alarm = shadowOf(alarmManager).peekNextScheduledAlarm();
+ assertThat(alarm).isNotNull();
+ assertThat(alarm.getTriggerAtMs()).isEqualTo(SystemClock.elapsedRealtime() + 20);
+
+ alarmManager.cancel(listener1.getPendingIntent());
+
+ assertThat(shadowOf(alarmManager).getScheduledAlarms()).isEmpty();
+ assertThat(shadowOf(alarmManager).peekNextScheduledAlarm()).isNull();
+
+ shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(20));
+ verify(onFire1, never()).run();
+ verify(onFire2, never()).run();
+ }
}
+ @Config(minSdk = VERSION_CODES.N)
@Test
- public void schedule_useRequestCodeToMatchExistingPendingIntents() {
- Intent intent = new Intent("ACTION!");
- PendingIntent pI = PendingIntent.getService(context, 1, intent, 0);
- alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, 10, pI);
+ public void cancel_alarmListener() {
+ OnAlarmListener onFire1 = mock(OnAlarmListener.class);
+ OnAlarmListener onFire2 = mock(OnAlarmListener.class);
+ alarmManager.set(
+ AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 20, "tag", onFire1, null);
+ alarmManager.set(
+ AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 10, "tag", onFire2, null);
- PendingIntent pI2 = PendingIntent.getService(context, 2, intent, 0);
- alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, 10, pI2);
+ assertThat(shadowOf(alarmManager).getScheduledAlarms()).hasSize(2);
+ ScheduledAlarm alarm = shadowOf(alarmManager).peekNextScheduledAlarm();
+ assertThat(alarm).isNotNull();
+ assertThat(alarm.getTriggerAtMs()).isEqualTo(SystemClock.elapsedRealtime() + 10);
- assertThat(shadowAlarmManager.getScheduledAlarms()).hasSize(2);
- }
+ alarmManager.cancel(onFire2);
- @Test
- public void cancel_useRequestCodeToMatchExistingPendingIntents() {
- Intent intent = new Intent("ACTION!");
- PendingIntent pI = PendingIntent.getService(context, 1, intent, 0);
- alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, 10, pI);
+ assertThat(shadowOf(alarmManager).getScheduledAlarms()).hasSize(1);
+ alarm = shadowOf(alarmManager).peekNextScheduledAlarm();
+ assertThat(alarm).isNotNull();
+ assertThat(alarm.getTriggerAtMs()).isEqualTo(SystemClock.elapsedRealtime() + 20);
- PendingIntent pI2 = PendingIntent.getService(context, 2, intent, 0);
- alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, 10, pI2);
+ alarmManager.cancel(onFire1);
- assertThat(shadowAlarmManager.getScheduledAlarms()).hasSize(2);
+ assertThat(shadowOf(alarmManager).getScheduledAlarms()).isEmpty();
+ assertThat(shadowOf(alarmManager).peekNextScheduledAlarm()).isNull();
- alarmManager.cancel(pI);
- assertThat(shadowAlarmManager.getScheduledAlarms()).hasSize(1);
- assertThat(shadowAlarmManager.getNextScheduledAlarm().operation).isEqualTo(pI2);
+ shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(20));
+ verify(onFire1, never()).onAlarm();
+ verify(onFire2, never()).onAlarm();
}
@Test
- @Config(minSdk = N)
- public void cancel_removesMatchingListeners() {
- Intent intent = new Intent("ACTION!");
- PendingIntent pI = PendingIntent.getService(context, 1, intent, 0);
- OnAlarmListener listener1 = () -> {};
- OnAlarmListener listener2 = () -> {};
- Handler handler = new Handler();
+ @Config(minSdk = VERSION_CODES.S)
+ public void canScheduleExactAlarms() {
+ assertThat(alarmManager.canScheduleExactAlarms()).isFalse();
- alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, 20, "tag", listener1, handler);
- alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, 30, "tag", listener2, handler);
- alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, 40, pI);
- assertThat(shadowAlarmManager.getScheduledAlarms()).hasSize(3);
+ ShadowAlarmManager.setCanScheduleExactAlarms(true);
+ assertThat(alarmManager.canScheduleExactAlarms()).isTrue();
- alarmManager.cancel(listener1);
- assertThat(shadowAlarmManager.getScheduledAlarms()).hasSize(2);
- assertThat(shadowAlarmManager.peekNextScheduledAlarm().onAlarmListener).isEqualTo(listener2);
- assertThat(shadowAlarmManager.peekNextScheduledAlarm().handler).isEqualTo(handler);
+ ShadowAlarmManager.setCanScheduleExactAlarms(false);
+ assertThat(alarmManager.canScheduleExactAlarms()).isFalse();
}
@Test
- @Config(minSdk = LOLLIPOP)
+ @Config(minSdk = VERSION_CODES.LOLLIPOP)
public void getNextAlarmClockInfo() {
+ AlarmClockInfo alarmClockInfo1 =
+ new AlarmClockInfo(
+ SystemClock.elapsedRealtime() + 10,
+ PendingIntent.getBroadcast(context, 0, new Intent("show1"), 0));
+ AlarmClockInfo alarmClockInfo2 =
+ new AlarmClockInfo(
+ SystemClock.elapsedRealtime() + 5,
+ PendingIntent.getBroadcast(context, 0, new Intent("show2"), 0));
+
+ alarmManager.setAlarmClock(
+ alarmClockInfo1, PendingIntent.getBroadcast(context, 0, new Intent("fire1"), 0));
+ alarmManager.setAlarmClock(
+ alarmClockInfo2, PendingIntent.getBroadcast(context, 0, new Intent("fire2"), 0));
+ assertThat(alarmManager.getNextAlarmClock()).isEqualTo(alarmClockInfo2);
+
+ shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(5));
+ assertThat(alarmManager.getNextAlarmClock()).isEqualTo(alarmClockInfo1);
+
+ shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(5));
assertThat(alarmManager.getNextAlarmClock()).isNull();
- assertThat(shadowAlarmManager.peekNextScheduledAlarm()).isNull();
-
- // Schedule an alarm.
- PendingIntent show = PendingIntent.getBroadcast(context, 0, new Intent("showAction"), 0);
- PendingIntent operation = PendingIntent.getBroadcast(context, 0, new Intent("opAction"), 0);
- AlarmClockInfo info = new AlarmClockInfo(1000, show);
- alarmManager.setAlarmClock(info, operation);
-
- AlarmClockInfo next = alarmManager.getNextAlarmClock();
- assertThat(next).isNotNull();
- assertThat(next.getTriggerTime()).isEqualTo(1000);
- assertThat(next.getShowIntent()).isSameInstanceAs(show);
- assertThat(shadowAlarmManager.peekNextScheduledAlarm().operation).isSameInstanceAs(operation);
-
- // Schedule another alarm sooner.
- PendingIntent show2 = PendingIntent.getBroadcast(context, 0, new Intent("showAction2"), 0);
- PendingIntent operation2 = PendingIntent.getBroadcast(context, 0, new Intent("opAction2"), 0);
- AlarmClockInfo info2 = new AlarmClockInfo(500, show2);
- alarmManager.setAlarmClock(info2, operation2);
-
- next = alarmManager.getNextAlarmClock();
- assertThat(next).isNotNull();
- assertThat(next.getTriggerTime()).isEqualTo(500);
- assertThat(next.getShowIntent()).isSameInstanceAs(show2);
- assertThat(shadowAlarmManager.peekNextScheduledAlarm().operation).isSameInstanceAs(operation2);
-
- // Remove the soonest alarm.
- alarmManager.cancel(operation2);
-
- next = alarmManager.getNextAlarmClock();
- assertThat(next).isNotNull();
- assertThat(next.getTriggerTime()).isEqualTo(1000);
- assertThat(next.getShowIntent()).isSameInstanceAs(show);
- assertThat(shadowAlarmManager.peekNextScheduledAlarm().operation).isSameInstanceAs(operation);
-
- // Remove the sole alarm.
- alarmManager.cancel(operation);
-
- assertThat(alarmManager.getNextAlarmClock()).isNull();
- assertThat(shadowAlarmManager.peekNextScheduledAlarm()).isNull();
}
@Test
- @Config(minSdk = S)
- public void canScheduleExactAlarms_default_returnsTrue() {
- assertThat(alarmManager.canScheduleExactAlarms()).isFalse();
+ public void replace_pendingIntent() {
+ Runnable onFire = mock(Runnable.class);
+ try (TestBroadcastListener listener = new TestBroadcastListener(onFire, "action").register()) {
+ alarmManager.set(
+ AlarmManager.ELAPSED_REALTIME,
+ SystemClock.elapsedRealtime() + 10,
+ listener.getPendingIntent());
+ alarmManager.set(
+ AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime() + 20,
+ listener.getPendingIntent());
+
+ ScheduledAlarm alarm = shadowOf(alarmManager).peekNextScheduledAlarm();
+ assertThat(alarm).isNotNull();
+ assertThat(alarm.getType()).isEqualTo(AlarmManager.ELAPSED_REALTIME_WAKEUP);
+ assertThat(alarm.getTriggerAtMs()).isEqualTo(SystemClock.elapsedRealtime() + 20);
+
+ shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(10));
+ verify(onFire, never()).run();
+
+ shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(10));
+ verify(onFire).run();
+ }
}
+ @Config(minSdk = VERSION_CODES.N)
@Test
- @Config(minSdk = S)
- public void canScheduleExactAlarms_setCanScheduleExactAlarms_returnsTrue() {
- ShadowAlarmManager.setCanScheduleExactAlarms(true);
+ public void replace_alarmListener() {
+ OnAlarmListener onFire = mock(OnAlarmListener.class);
+ alarmManager.set(
+ AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 10, "tag", onFire, null);
+ alarmManager.set(
+ AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime() + 20,
+ "tag1",
+ onFire,
+ null);
- assertThat(alarmManager.canScheduleExactAlarms()).isTrue();
+ ScheduledAlarm alarm = shadowOf(alarmManager).peekNextScheduledAlarm();
+ assertThat(alarm).isNotNull();
+ assertThat(alarm.getType()).isEqualTo(AlarmManager.ELAPSED_REALTIME_WAKEUP);
+ assertThat(alarm.getTriggerAtMs()).isEqualTo(SystemClock.elapsedRealtime() + 20);
+ assertThat(alarm.getTag()).isEqualTo("tag1");
+
+ shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(10));
+ verify(onFire, never()).onAlarm();
+
+ shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(10));
+ verify(onFire).onAlarm();
}
@Test
- @Config(minSdk = S)
- public void canScheduleExactAlarms_setCannotScheduleExactAlarms_returnsFalse() {
- ShadowAlarmManager.setCanScheduleExactAlarms(false);
-
- assertThat(alarmManager.canScheduleExactAlarms()).isFalse();
+ public void pastTime() {
+ Runnable onFire = mock(Runnable.class);
+ try (TestBroadcastListener listener = new TestBroadcastListener(onFire, "action").register()) {
+ alarmManager.set(
+ AlarmManager.ELAPSED_REALTIME,
+ SystemClock.elapsedRealtime() - 10,
+ listener.getPendingIntent());
+
+ ScheduledAlarm alarm = shadowOf(alarmManager).peekNextScheduledAlarm();
+ assertThat(alarm).isNotNull();
+ assertThat(alarm.getType()).isEqualTo(AlarmManager.ELAPSED_REALTIME);
+ assertThat(alarm.getTriggerAtMs()).isEqualTo(SystemClock.elapsedRealtime() - 10);
+
+ shadowOf(Looper.getMainLooper()).idle();
+ verify(onFire).run();
+
+ assertThat(shadowOf(alarmManager).peekNextScheduledAlarm()).isNull();
+ }
}
- private void assertScheduledAlarm(
- long now, PendingIntent pendingIntent, ShadowAlarmManager.ScheduledAlarm scheduledAlarm) {
- assertRepeatingScheduledAlarm(now, 0L, pendingIntent, scheduledAlarm);
+ @Config(minSdk = VERSION_CODES.N)
+ @Test
+ public void reentrant() {
+ AtomicReference<OnAlarmListener> listenerRef = new AtomicReference<>();
+ listenerRef.set(
+ () ->
+ alarmManager.set(
+ AlarmManager.ELAPSED_REALTIME,
+ SystemClock.elapsedRealtime() + 10,
+ "tag",
+ listenerRef.get(),
+ null));
+ alarmManager.set(
+ AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime() + 10,
+ "tag",
+ listenerRef.get(),
+ null);
+
+ ScheduledAlarm alarm = shadowOf(alarmManager).peekNextScheduledAlarm();
+ assertThat(alarm).isNotNull();
+ assertThat(alarm.getType()).isEqualTo(AlarmManager.ELAPSED_REALTIME_WAKEUP);
+ assertThat(alarm.getTriggerAtMs()).isEqualTo(SystemClock.elapsedRealtime() + 10);
+ assertThat(alarm.getTag()).isEqualTo("tag");
+
+ shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(10));
+
+ alarm = shadowOf(alarmManager).peekNextScheduledAlarm();
+ assertThat(alarm).isNotNull();
+ assertThat(alarm.getType()).isEqualTo(AlarmManager.ELAPSED_REALTIME);
+ assertThat(alarm.getTriggerAtMs()).isEqualTo(SystemClock.elapsedRealtime() + 10);
+ assertThat(alarm.getTag()).isEqualTo("tag");
}
- private void assertRepeatingScheduledAlarm(
- long now,
- long interval,
- PendingIntent pendingIntent,
- ShadowAlarmManager.ScheduledAlarm scheduledAlarm) {
- assertThat(scheduledAlarm).isNotNull();
- assertThat(scheduledAlarm.operation).isNotNull();
- assertThat(scheduledAlarm.operation).isSameInstanceAs(pendingIntent);
- assertThat(scheduledAlarm.type).isEqualTo(AlarmManager.ELAPSED_REALTIME);
- assertThat(scheduledAlarm.triggerAtTime).isEqualTo(now);
- assertThat(scheduledAlarm.interval).isEqualTo(interval);
+ private class TestBroadcastListener extends BroadcastReceiver implements AutoCloseable {
+
+ private final Runnable alarm;
+ private final String action;
+
+ @Nullable private PendingIntent pendingIntent;
+
+ TestBroadcastListener(Runnable alarm, String action) {
+ this.alarm = alarm;
+ this.action = action;
+ }
+
+ TestBroadcastListener register() {
+ pendingIntent = PendingIntent.getBroadcast(context, 0, new Intent(action), 0);
+ context.registerReceiver(this, new IntentFilter(action));
+ return this;
+ }
+
+ PendingIntent getPendingIntent() {
+ return Objects.requireNonNull(pendingIntent);
+ }
+
+ @Override
+ public void close() {
+ context.unregisterReceiver(this);
+ if (pendingIntent != null) {
+ pendingIntent.cancel();
+ }
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Objects.equals(action, intent.getAction())) {
+ alarm.run();
+ }
+ }
}
}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowAnimationUtilsTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowAnimationUtilsTest.java
index 7ecc1f8bf..a429301d3 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowAnimationUtilsTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowAnimationUtilsTest.java
@@ -5,29 +5,24 @@ import static com.google.common.truth.Truth.assertThat;
import android.R;
import android.app.Activity;
import android.view.animation.AnimationUtils;
-import android.view.animation.LayoutAnimationController;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
-import org.robolectric.Shadows;
@RunWith(AndroidJUnit4.class)
public class ShadowAnimationUtilsTest {
@Test
public void loadAnimation_shouldCreateAnimation() {
- assertThat(AnimationUtils.loadAnimation(Robolectric.setupActivity(Activity.class), R.anim.fade_in)).isNotNull();
+ assertThat(
+ AnimationUtils.loadAnimation(Robolectric.setupActivity(Activity.class), R.anim.fade_in))
+ .isNotNull();
}
@Test
public void loadLayoutAnimation_shouldCreateAnimation() {
- assertThat(AnimationUtils.loadLayoutAnimation(Robolectric.setupActivity(Activity.class), 1)).isNotNull();
- }
-
- @Test
- public void getLoadedFromResourceId_forAnimationController_shouldReturnAnimationResourceId() {
- final LayoutAnimationController anim = AnimationUtils.loadLayoutAnimation(Robolectric.setupActivity(Activity.class), R.anim.fade_in);
- assertThat(Shadows.shadowOf(anim).getLoadedFromResourceId()).isEqualTo(R.anim.fade_in);
+ assertThat(AnimationUtils.loadLayoutAnimation(Robolectric.setupActivity(Activity.class), 1))
+ .isNotNull();
}
}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowAudioManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowAudioManagerTest.java
index f5a29a8fe..b798a74a3 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowAudioManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowAudioManagerTest.java
@@ -18,6 +18,7 @@ import static org.robolectric.Shadows.shadowOf;
import android.content.Context;
import android.media.AudioAttributes;
+import android.media.AudioDeviceCallback;
import android.media.AudioDeviceInfo;
import android.media.AudioFormat;
import android.media.AudioManager;
@@ -31,6 +32,7 @@ import com.google.common.collect.ImmutableList;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
@@ -399,6 +401,240 @@ public class ShadowAudioManagerTest {
@Test
@Config(minSdk = M)
+ public void registerAudioDeviceCallback_availableDevices_onAudioDevicesAddedCallback()
+ throws Exception {
+ AudioDeviceInfo device = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
+ shadowOf(audioManager).setInputDevices(Collections.singletonList(device));
+
+ AudioDeviceCallback callback = mock(AudioDeviceCallback.class);
+ audioManager.registerAudioDeviceCallback(callback, /* handler= */ null);
+
+ verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {device});
+ }
+
+ @Test
+ @Config(minSdk = M)
+ public void setInputDevices_withCallbackRegistered_noNotificationCallback() throws Exception {
+ AudioDeviceCallback callback = mock(AudioDeviceCallback.class);
+ audioManager.registerAudioDeviceCallback(callback, /* handler= */ null);
+ verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration
+
+ AudioDeviceInfo device = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
+ shadowOf(audioManager).setInputDevices(Collections.singletonList(device));
+
+ verifyNoMoreInteractions(callback);
+ }
+
+ @Test
+ @Config(minSdk = M)
+ public void addInputDevice_callbackRegisteredUnregistered_noNotificationCallback()
+ throws Exception {
+ AudioDeviceCallback callback = mock(AudioDeviceCallback.class);
+ audioManager.registerAudioDeviceCallback(callback, /* handler= */ null);
+ audioManager.unregisterAudioDeviceCallback(callback);
+ verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration
+
+ AudioDeviceInfo device = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
+ shadowOf(audioManager).addInputDevice(device, /* notifyAudioDeviceCallbacks= */ true);
+
+ verifyNoMoreInteractions(callback);
+ }
+
+ @Test
+ @Config(minSdk = M)
+ public void addInputDevice_withCallbackRegisteredAndNoDevice_deviceAddedAndNotifiesCallback()
+ throws Exception {
+ AudioDeviceCallback callback = mock(AudioDeviceCallback.class);
+ audioManager.registerAudioDeviceCallback(callback, /* handler= */ null);
+ verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration
+
+ AudioDeviceInfo device = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
+ shadowOf(audioManager).addInputDevice(device, /* notifyAudioDeviceCallbacks= */ true);
+
+ verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {device});
+ }
+
+ @Test
+ @Config(minSdk = M)
+ public void
+ addInputDeviceNoCallbackNotification_withCallbackRegisteredAndNoDevice_noNotificationCallback()
+ throws Exception {
+ AudioDeviceCallback callback = mock(AudioDeviceCallback.class);
+ audioManager.registerAudioDeviceCallback(callback, /* handler= */ null);
+ verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration
+
+ AudioDeviceInfo device = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
+ shadowOf(audioManager).addInputDevice(device, /* notifyAudioDeviceCallbacks= */ false);
+
+ verifyNoMoreInteractions(callback);
+ }
+
+ @Test
+ @Config(minSdk = M)
+ public void addInputDevice_withCallbackRegisteredAndDevicePresent_noNotificationCallback()
+ throws Exception {
+ AudioDeviceCallback callback = mock(AudioDeviceCallback.class);
+ audioManager.registerAudioDeviceCallback(callback, /* handler= */ null);
+ verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration
+ AudioDeviceInfo device = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
+ shadowOf(audioManager).setInputDevices(Collections.singletonList(device));
+
+ shadowOf(audioManager).addInputDevice(device, /* notifyAudioDeviceCallbacks= */ true);
+
+ verifyNoMoreInteractions(callback);
+ }
+
+ @Test
+ @Config(minSdk = M)
+ public void
+ removeInputDevice_withCallbackRegisteredAndDevicePresent_deviceRemovedAndNotifiesCallback()
+ throws Exception {
+ AudioDeviceCallback callback = mock(AudioDeviceCallback.class);
+ audioManager.registerAudioDeviceCallback(callback, /* handler= */ null);
+ verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration
+ AudioDeviceInfo device = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
+ shadowOf(audioManager).setInputDevices(Collections.singletonList(device));
+
+ shadowOf(audioManager).removeInputDevice(device, /* notifyAudioDeviceCallbacks= */ true);
+
+ verify(callback).onAudioDevicesRemoved(new AudioDeviceInfo[] {device});
+ }
+
+ @Test
+ @Config(minSdk = M)
+ public void
+ removeInputDeviceNoCallbackNotification_withCallbackRegisteredAndDevicePresent_noNotificationCallback()
+ throws Exception {
+ AudioDeviceCallback callback = mock(AudioDeviceCallback.class);
+ audioManager.registerAudioDeviceCallback(callback, /* handler= */ null);
+ verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration
+ AudioDeviceInfo device = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
+ shadowOf(audioManager).setInputDevices(Collections.singletonList(device));
+
+ shadowOf(audioManager).removeInputDevice(device, /* notifyAudioDeviceCallbacks= */ false);
+
+ verifyNoMoreInteractions(callback);
+ }
+
+ @Test
+ @Config(minSdk = M)
+ public void removeInputDevice_withCallbackRegisteredAndNoDevice_noNotificationCallback()
+ throws Exception {
+ AudioDeviceCallback callback = mock(AudioDeviceCallback.class);
+ audioManager.registerAudioDeviceCallback(callback, /* handler= */ null);
+ verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration
+
+ AudioDeviceInfo device = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
+ shadowOf(audioManager).removeInputDevice(device, /* notifyAudioDeviceCallbacks= */ true);
+
+ verifyNoMoreInteractions(callback);
+ }
+
+ @Test
+ @Config(minSdk = M)
+ public void setOutputDevices_withCallbackRegistered_noNotificationCallback() throws Exception {
+ AudioDeviceCallback callback = mock(AudioDeviceCallback.class);
+ audioManager.registerAudioDeviceCallback(callback, /* handler= */ null);
+ verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration
+
+ AudioDeviceInfo device = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
+ shadowOf(audioManager).setOutputDevices(Collections.singletonList(device));
+
+ verifyNoMoreInteractions(callback);
+ }
+
+ @Test
+ @Config(minSdk = M)
+ public void addOutputDevice_withCallbackRegisteredAndNoDevice_deviceAddedAndNotifiesCallback()
+ throws Exception {
+ AudioDeviceCallback callback = mock(AudioDeviceCallback.class);
+ audioManager.registerAudioDeviceCallback(callback, /* handler= */ null);
+ verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration
+
+ AudioDeviceInfo device = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
+ shadowOf(audioManager).addOutputDevice(device, /* notifyAudioDeviceCallbacks= */ true);
+
+ verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {device});
+ }
+
+ @Test
+ @Config(minSdk = M)
+ public void
+ addOutputDeviceNoCallbackNotification_withCallbackRegisteredAndNoDevice_noNotificationCallback()
+ throws Exception {
+ AudioDeviceCallback callback = mock(AudioDeviceCallback.class);
+ audioManager.registerAudioDeviceCallback(callback, /* handler= */ null);
+ verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration
+
+ AudioDeviceInfo device = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
+ shadowOf(audioManager).addOutputDevice(device, /* notifyAudioDeviceCallbacks= */ false);
+
+ verifyNoMoreInteractions(callback);
+ }
+
+ @Test
+ @Config(minSdk = M)
+ public void addOutputDevice_withCallbackRegisteredAndDevicePresent_noNotificationCallback()
+ throws Exception {
+ AudioDeviceCallback callback = mock(AudioDeviceCallback.class);
+ audioManager.registerAudioDeviceCallback(callback, /* handler= */ null);
+ verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration
+ AudioDeviceInfo device = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
+ shadowOf(audioManager).setOutputDevices(Collections.singletonList(device));
+
+ shadowOf(audioManager).addOutputDevice(device, /* notifyAudioDeviceCallbacks= */ true);
+
+ verifyNoMoreInteractions(callback);
+ }
+
+ @Test
+ @Config(minSdk = M)
+ public void
+ removeOutputDevice_withCallbackRegisteredAndDevicePresent_deviceRemovedAndNotifiesCallback()
+ throws Exception {
+ AudioDeviceCallback callback = mock(AudioDeviceCallback.class);
+ audioManager.registerAudioDeviceCallback(callback, /* handler= */ null);
+ verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration
+ AudioDeviceInfo device = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
+ shadowOf(audioManager).setOutputDevices(Collections.singletonList(device));
+
+ shadowOf(audioManager).removeOutputDevice(device, /* notifyAudioDeviceCallbacks= */ true);
+
+ verify(callback).onAudioDevicesRemoved(new AudioDeviceInfo[] {device});
+ }
+
+ @Test
+ @Config(minSdk = M)
+ public void
+ removeOutputDeviceNoCallbackNotification_withCallbackRegisteredAndDevicePresent_noNotificationCallback()
+ throws Exception {
+ AudioDeviceCallback callback = mock(AudioDeviceCallback.class);
+ audioManager.registerAudioDeviceCallback(callback, /* handler= */ null);
+ verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration
+ AudioDeviceInfo device = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
+ shadowOf(audioManager).setOutputDevices(Collections.singletonList(device));
+
+ shadowOf(audioManager).removeOutputDevice(device, /* notifyAudioDeviceCallbacks= */ false);
+
+ verifyNoMoreInteractions(callback);
+ }
+
+ @Test
+ @Config(minSdk = M)
+ public void removeOutputDevice_withCallbackRegisteredAndNoDevice_noNotificationCallback()
+ throws Exception {
+ AudioDeviceCallback callback = mock(AudioDeviceCallback.class);
+ audioManager.registerAudioDeviceCallback(callback, /* handler= */ null);
+ verify(callback).onAudioDevicesAdded(new AudioDeviceInfo[] {}); // initial registration
+
+ AudioDeviceInfo device = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
+ shadowOf(audioManager).removeOutputDevice(device, /* notifyAudioDeviceCallbacks= */ true);
+
+ verifyNoMoreInteractions(callback);
+ }
+
+ @Test
+ @Config(minSdk = M)
public void getDevices_criteriaInputs_getsAllInputDevices() throws Exception {
AudioDeviceInfo scoDevice = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
AudioDeviceInfo a2dpDevice = createAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP);
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBitmapTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBitmapTest.java
index 2e5be1301..dfe336b6c 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBitmapTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBitmapTest.java
@@ -149,13 +149,13 @@ public class ShadowBitmapTest {
@Test
public void shouldCreateBitmapWithMatrix() {
Bitmap originalBitmap = create("Original bitmap");
- shadowOf(originalBitmap).setWidth(200);
- shadowOf(originalBitmap).setHeight(200);
+ ((ShadowLegacyBitmap) Shadow.extract(originalBitmap)).setWidth(200);
+ ((ShadowLegacyBitmap) Shadow.extract(originalBitmap)).setHeight(200);
Matrix m = new Matrix();
m.postRotate(90);
Bitmap newBitmap = Bitmap.createBitmap(originalBitmap, 0, 0, 100, 50, m, true);
- ShadowBitmap shadowBitmap = shadowOf(newBitmap);
+ ShadowLegacyBitmap shadowBitmap = Shadow.extract(newBitmap);
assertThat(shadowBitmap.getDescription())
.isEqualTo(
"Original bitmap at (0,0) with width 100 and height 50"
@@ -246,8 +246,8 @@ public class ShadowBitmapTest {
public void shouldCopyBitmap() {
Bitmap bitmap = Shadow.newInstanceOf(Bitmap.class);
Bitmap bitmapCopy = bitmap.copy(Bitmap.Config.ARGB_8888, true);
- assertThat(shadowOf(bitmapCopy).getConfig()).isEqualTo(Bitmap.Config.ARGB_8888);
- assertThat(shadowOf(bitmapCopy).isMutable()).isTrue();
+ assertThat(bitmapCopy.getConfig()).isEqualTo(Bitmap.Config.ARGB_8888);
+ assertThat(bitmapCopy.isMutable()).isTrue();
}
@Test(expected = NullPointerException.class)
@@ -538,7 +538,7 @@ public class ShadowBitmapTest {
@Test
public void compress_shouldSucceedForNullPixelData() {
Bitmap bitmap = Shadow.newInstanceOf(Bitmap.class);
- ShadowBitmap shadowBitmap = Shadow.extract(bitmap);
+ ShadowLegacyBitmap shadowBitmap = Shadow.extract(bitmap);
shadowBitmap.setWidth(100);
shadowBitmap.setHeight(100);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
@@ -548,15 +548,15 @@ public class ShadowBitmapTest {
@Config(sdk = O)
@Test
public void getBytesPerPixel_O() {
- assertThat(ShadowBitmap.getBytesPerPixel(Bitmap.Config.RGBA_F16)).isEqualTo(8);
+ assertThat(ShadowLegacyBitmap.getBytesPerPixel(Bitmap.Config.RGBA_F16)).isEqualTo(8);
}
@Test
public void getBytesPerPixel_preO() {
- assertThat(ShadowBitmap.getBytesPerPixel(Bitmap.Config.ARGB_8888)).isEqualTo(4);
- assertThat(ShadowBitmap.getBytesPerPixel(Bitmap.Config.RGB_565)).isEqualTo(2);
- assertThat(ShadowBitmap.getBytesPerPixel(Bitmap.Config.ARGB_4444)).isEqualTo(2);
- assertThat(ShadowBitmap.getBytesPerPixel(Bitmap.Config.ALPHA_8)).isEqualTo(1);
+ assertThat(ShadowLegacyBitmap.getBytesPerPixel(Bitmap.Config.ARGB_8888)).isEqualTo(4);
+ assertThat(ShadowLegacyBitmap.getBytesPerPixel(Bitmap.Config.RGB_565)).isEqualTo(2);
+ assertThat(ShadowLegacyBitmap.getBytesPerPixel(Bitmap.Config.ARGB_4444)).isEqualTo(2);
+ assertThat(ShadowLegacyBitmap.getBytesPerPixel(Bitmap.Config.ALPHA_8)).isEqualTo(1);
}
@Test(expected = RuntimeException.class)
@@ -642,9 +642,7 @@ public class ShadowBitmapTest {
@Test(expected = IllegalStateException.class)
public void reconfigure_withHardwareBitmap_validDimensionsAndConfig_throws() {
Bitmap original = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
- ShadowBitmap shadowBitmap = Shadow.extract(original);
- shadowBitmap.setConfig(Bitmap.Config.HARDWARE);
-
+ original.setConfig(Bitmap.Config.HARDWARE);
original.reconfigure(100, 100, Bitmap.Config.ARGB_8888);
}
@@ -814,15 +812,17 @@ public class ShadowBitmapTest {
private void createScaledBitmap_expectedUpSize(boolean filter) {
Bitmap bitmap = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888);
Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, 32, 32, filter);
- assertThat(Shadows.shadowOf(scaledBitmap).getBufferedImage().getWidth()).isEqualTo(32);
- assertThat(Shadows.shadowOf(scaledBitmap).getBufferedImage().getHeight()).isEqualTo(32);
+ ShadowLegacyBitmap shadowBitmap = Shadow.extract(scaledBitmap);
+ assertThat(shadowBitmap.getBufferedImage().getWidth()).isEqualTo(32);
+ assertThat(shadowBitmap.getBufferedImage().getHeight()).isEqualTo(32);
}
private void createScaledBitmap_expectedDownSize(boolean filter) {
Bitmap bitmap = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888);
Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, 10, 10, filter);
- assertThat(Shadows.shadowOf(scaledBitmap).getBufferedImage().getWidth()).isEqualTo(10);
- assertThat(Shadows.shadowOf(scaledBitmap).getBufferedImage().getHeight()).isEqualTo(10);
+ ShadowLegacyBitmap shadowBitmap = Shadow.extract(scaledBitmap);
+ assertThat(shadowBitmap.getBufferedImage().getWidth()).isEqualTo(10);
+ assertThat(shadowBitmap.getBufferedImage().getHeight()).isEqualTo(10);
}
private void createScaledBitmap_drawOnScaled(boolean filter) {
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothAdapterTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothAdapterTest.java
index 69d7e7b22..10cb14814 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothAdapterTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothAdapterTest.java
@@ -495,6 +495,51 @@ public class ShadowBluetoothAdapterTest {
}
@Test
+ public void closeProfileProxy_severalCallersObserving_allNotified() {
+ BluetoothProfile mockProxy = mock(BluetoothProfile.class);
+ BluetoothProfile.ServiceListener mockServiceListener =
+ mock(BluetoothProfile.ServiceListener.class);
+ BluetoothProfile.ServiceListener mockServiceListener2 =
+ mock(BluetoothProfile.ServiceListener.class);
+ shadowOf(bluetoothAdapter).setProfileProxy(MOCK_PROFILE1, mockProxy);
+
+ bluetoothAdapter.getProfileProxy(
+ RuntimeEnvironment.getApplication(), mockServiceListener, MOCK_PROFILE1);
+ bluetoothAdapter.getProfileProxy(
+ RuntimeEnvironment.getApplication(), mockServiceListener2, MOCK_PROFILE1);
+
+ bluetoothAdapter.closeProfileProxy(MOCK_PROFILE1, mockProxy);
+
+ verify(mockServiceListener).onServiceDisconnected(MOCK_PROFILE1);
+ verify(mockServiceListener2).onServiceDisconnected(MOCK_PROFILE1);
+ }
+
+ @Test
+ public void closeProfileProxy_severalCallersObservingAndClosedTwice_allNotifiedOnce() {
+ BluetoothProfile mockProxy = mock(BluetoothProfile.class);
+ BluetoothProfile.ServiceListener mockServiceListener =
+ mock(BluetoothProfile.ServiceListener.class);
+ BluetoothProfile.ServiceListener mockServiceListener2 =
+ mock(BluetoothProfile.ServiceListener.class);
+ shadowOf(bluetoothAdapter).setProfileProxy(MOCK_PROFILE1, mockProxy);
+
+ bluetoothAdapter.getProfileProxy(
+ RuntimeEnvironment.getApplication(), mockServiceListener, MOCK_PROFILE1);
+ bluetoothAdapter.getProfileProxy(
+ RuntimeEnvironment.getApplication(), mockServiceListener2, MOCK_PROFILE1);
+
+ bluetoothAdapter.closeProfileProxy(MOCK_PROFILE1, mockProxy);
+ verify(mockServiceListener).onServiceConnected(MOCK_PROFILE1, mockProxy);
+ verify(mockServiceListener2).onServiceConnected(MOCK_PROFILE1, mockProxy);
+ verify(mockServiceListener).onServiceDisconnected(MOCK_PROFILE1);
+ verify(mockServiceListener2).onServiceDisconnected(MOCK_PROFILE1);
+
+ bluetoothAdapter.closeProfileProxy(MOCK_PROFILE1, mockProxy);
+ verifyNoMoreInteractions(mockServiceListener);
+ verifyNoMoreInteractions(mockServiceListener2);
+ }
+
+ @Test
public void closeProfileProxy_reversesSetProfileProxy() {
BluetoothProfile mockProxy = mock(BluetoothProfile.class);
BluetoothProfile.ServiceListener mockServiceListener =
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothGattTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothGattTest.java
index a76dbc78c..caed17812 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothGattTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothGattTest.java
@@ -1,13 +1,20 @@
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;
import static org.robolectric.Shadows.shadowOf;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothProfile;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import java.util.UUID;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
@@ -17,30 +24,451 @@ import org.robolectric.annotation.Config;
@Config(minSdk = JELLY_BEAN_MR2)
public class ShadowBluetoothGattTest {
+ private static final byte[] CHARACTERISTIC_VALUE = new byte[] {'a', 'b', 'c'};
+ private static final int INITIAL_VALUE = -99;
private static final String MOCK_MAC_ADDRESS = "00:11:22:33:AA:BB";
+ private static final String ACTION_CONNECTION = "CONNECT/DISCONNECT";
+ private static final String ACTION_DISCOVER = "DISCOVER";
+ private static final String ACTION_READ = "READ";
+ private static final String ACTION_WRITE = "WRITE";
+
+ private int resultStatus = INITIAL_VALUE;
+ private int resultState = INITIAL_VALUE;
+ private String resultAction;
+ private BluetoothGattCharacteristic resultCharacteristic;
+ private BluetoothGatt bluetoothGatt;
+
+ private static final BluetoothGattService service1 =
+ new BluetoothGattService(
+ UUID.fromString("00000000-0000-0000-0000-0000000000A1"),
+ BluetoothGattService.SERVICE_TYPE_PRIMARY);
+ private static final BluetoothGattService service2 =
+ new BluetoothGattService(
+ UUID.fromString("00000000-0000-0000-0000-0000000000A2"),
+ BluetoothGattService.SERVICE_TYPE_SECONDARY);
+
+ private final BluetoothGattCallback callback =
+ new BluetoothGattCallback() {
+ @Override
+ public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
+ resultStatus = status;
+ resultState = newState;
+ resultAction = ACTION_CONNECTION;
+ }
+
+ @Override
+ public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+ resultStatus = status;
+ resultAction = ACTION_DISCOVER;
+ }
+
+ @Override
+ public void onCharacteristicRead(
+ BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+ resultStatus = status;
+ resultCharacteristic = characteristic;
+ resultAction = ACTION_READ;
+ }
+
+ @Override
+ public void onCharacteristicWrite(
+ BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+ resultStatus = status;
+ resultCharacteristic = characteristic;
+ resultAction = ACTION_WRITE;
+ }
+ };
+
+ private final BluetoothGattCharacteristic characteristicWithReadProperty =
+ new BluetoothGattCharacteristic(
+ UUID.fromString("00000000-0000-0000-0000-0000000000A3"),
+ BluetoothGattCharacteristic.PROPERTY_READ,
+ BluetoothGattCharacteristic.PERMISSION_READ);
+
+ private final BluetoothGattCharacteristic characteristicWithWriteProperties =
+ new BluetoothGattCharacteristic(
+ UUID.fromString("00000000-0000-0000-0000-0000000000A4"),
+ BluetoothGattCharacteristic.PROPERTY_WRITE
+ | BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE,
+ BluetoothGattCharacteristic.PERMISSION_WRITE);
+
+ @Before
+ public void setUp() throws Exception {
+ BluetoothDevice bluetoothDevice = ShadowBluetoothDevice.newInstance(MOCK_MAC_ADDRESS);
+ bluetoothGatt = ShadowBluetoothGatt.newInstance(bluetoothDevice);
+ }
@Test
public void canCreateBluetoothGattViaNewInstance() {
- BluetoothDevice bluetoothDevice = ShadowBluetoothDevice.newInstance(MOCK_MAC_ADDRESS);
- BluetoothGatt bluetoothGatt = ShadowBluetoothGatt.newInstance(bluetoothDevice);
assertThat(bluetoothGatt).isNotNull();
}
@Test
public void canSetAndGetGattCallback() {
- BluetoothDevice bluetoothDevice = ShadowBluetoothDevice.newInstance(MOCK_MAC_ADDRESS);
- BluetoothGatt bluetoothGatt = ShadowBluetoothGatt.newInstance(bluetoothDevice);
- BluetoothGattCallback callback = new BluetoothGattCallback() {};
-
shadowOf(bluetoothGatt).setGattCallback(callback);
-
assertThat(shadowOf(bluetoothGatt).getGattCallback()).isEqualTo(callback);
}
- @Config(minSdk = JELLY_BEAN_MR2)
- public void connect_returnsTrue() {
- BluetoothDevice bluetoothDevice = ShadowBluetoothDevice.newInstance(MOCK_MAC_ADDRESS);
- BluetoothGatt bluetoothGatt = ShadowBluetoothGatt.newInstance(bluetoothDevice);
+ @Test
+ public void isNotConnected_beforeConnect() {
+ assertThat(shadowOf(bluetoothGatt).isConnected()).isFalse();
+ assertThat(resultStatus).isEqualTo(INITIAL_VALUE);
+ assertThat(resultState).isEqualTo(INITIAL_VALUE);
+ assertThat(resultAction).isNull();
+ }
+
+ @Test
+ public void isConnected_returnsFalseWithoutCallback() {
+ assertThat(bluetoothGatt.connect()).isFalse();
+ assertThat(shadowOf(bluetoothGatt).isConnected()).isFalse();
+ }
+
+ @Test
+ public void isConnected_afterConnect() {
+ shadowOf(bluetoothGatt).setGattCallback(callback);
assertThat(bluetoothGatt.connect()).isTrue();
+ assertThat(shadowOf(bluetoothGatt).isConnected()).isTrue();
+ assertThat(resultStatus).isEqualTo(BluetoothGatt.GATT_SUCCESS);
+ assertThat(resultState).isEqualTo(BluetoothProfile.STATE_CONNECTED);
+ assertThat(resultAction).isEqualTo(ACTION_CONNECTION);
+ }
+
+ @Test
+ public void isConnected_afterConnectAndDisconnect() {
+ shadowOf(bluetoothGatt).setGattCallback(callback);
+ bluetoothGatt.connect();
+ bluetoothGatt.disconnect();
+ assertThat(shadowOf(bluetoothGatt).isConnected()).isFalse();
+ assertThat(resultStatus).isEqualTo(BluetoothGatt.GATT_SUCCESS);
+ assertThat(resultState).isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
+ assertThat(resultAction).isEqualTo(ACTION_CONNECTION);
+ }
+
+ @Test
+ public void isNotConnected_afterOnlyDisconnect() {
+ shadowOf(bluetoothGatt).setGattCallback(callback);
+ bluetoothGatt.disconnect();
+ assertThat(shadowOf(bluetoothGatt).isConnected()).isFalse();
+ assertThat(resultStatus).isEqualTo(INITIAL_VALUE);
+ assertThat(resultState).isEqualTo(INITIAL_VALUE);
+ assertThat(resultAction).isNull();
+ }
+
+ @Test
+ public void isNotConnected_afterConnectAndDisconnectWithoutCallback() {
+ shadowOf(bluetoothGatt).setGattCallback(callback);
+ bluetoothGatt.connect();
+ shadowOf(bluetoothGatt).setGattCallback(null);
+ bluetoothGatt.disconnect();
+ assertThat(shadowOf(bluetoothGatt).isConnected()).isFalse();
+ assertThat(resultStatus).isEqualTo(BluetoothGatt.GATT_SUCCESS);
+ assertThat(resultState).isEqualTo(BluetoothProfile.STATE_CONNECTED);
+ }
+
+ @Test
+ public void isNotClosedbeforeClose() {
+ assertThat(shadowOf(bluetoothGatt).isClosed()).isFalse();
+ }
+
+ @Test
+ public void isClosedafterClose() {
+ bluetoothGatt.close();
+ assertThat(shadowOf(bluetoothGatt).isClosed()).isTrue();
+ }
+
+ @Test
+ public void isDisconnected_afterClose() {
+ shadowOf(bluetoothGatt).setGattCallback(callback);
+ bluetoothGatt.connect();
+ bluetoothGatt.close();
+ assertThat(shadowOf(bluetoothGatt).isConnected()).isFalse();
+ }
+
+ @Test
+ @Config(minSdk = O)
+ public void getConnectionPriority_atInitiation() {
+ assertThat(shadowOf(bluetoothGatt).getConnectionPriority())
+ .isEqualTo(BluetoothGatt.CONNECTION_PRIORITY_BALANCED);
+ }
+
+ @Test
+ @Config(minSdk = O)
+ public void requestConnectionPriority_inRange() {
+ boolean res =
+ bluetoothGatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER);
+ assertThat(shadowOf(bluetoothGatt).getConnectionPriority())
+ .isEqualTo(BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER);
+ assertThat(res).isTrue();
+ res = bluetoothGatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_BALANCED);
+ assertThat(shadowOf(bluetoothGatt).getConnectionPriority())
+ .isEqualTo(BluetoothGatt.CONNECTION_PRIORITY_BALANCED);
+ assertThat(res).isTrue();
+ }
+
+ @Test
+ @Config(minSdk = O)
+ public void requestConnectionPriority_notInRange_throwsException() {
+ assertThrows(IllegalArgumentException.class, () -> bluetoothGatt.requestConnectionPriority(-9));
+ assertThrows(IllegalArgumentException.class, () -> bluetoothGatt.requestConnectionPriority(9));
+ }
+
+ @Test
+ @Config(minSdk = O)
+ public void discoverServices_noDiscoverableServices_returnsFalse() {
+ assertThat(bluetoothGatt.discoverServices()).isFalse();
+ assertThat(bluetoothGatt.getServices()).isEmpty();
+ }
+
+ @Test
+ @Config(minSdk = O)
+ public void getServices_afterAddService() {
+ shadowOf(bluetoothGatt).addDiscoverableService(service1);
+ assertThat(bluetoothGatt.discoverServices()).isFalse();
+ assertThat(bluetoothGatt.getServices()).hasSize(1);
+ }
+
+ @Test
+ @Config(minSdk = O)
+ public void getServices_afterAddMultipleService() {
+ shadowOf(bluetoothGatt).addDiscoverableService(service1);
+ shadowOf(bluetoothGatt).addDiscoverableService(service2);
+ assertThat(bluetoothGatt.discoverServices()).isFalse();
+ assertThat(bluetoothGatt.getServices()).hasSize(2);
+ }
+
+ @Test
+ @Config(minSdk = O)
+ public void getServices_noDiscoverableServices_withCallback() {
+ shadowOf(bluetoothGatt).setGattCallback(callback);
+ assertThat(bluetoothGatt.discoverServices()).isFalse();
+ assertThat(bluetoothGatt.getServices()).isEmpty();
+ assertThat(resultStatus).isEqualTo(INITIAL_VALUE);
+ assertThat(resultAction).isNull();
+ }
+
+ @Test
+ @Config(minSdk = O)
+ public void getServices_afterAddService_withCallback() {
+ shadowOf(bluetoothGatt).setGattCallback(callback);
+ shadowOf(bluetoothGatt).addDiscoverableService(service1);
+ assertThat(bluetoothGatt.discoverServices()).isTrue();
+ assertThat(bluetoothGatt.getServices()).hasSize(1);
+ assertThat(bluetoothGatt.getServices()).contains(service1);
+ assertThat(resultStatus).isEqualTo(BluetoothGatt.GATT_SUCCESS);
+ assertThat(resultAction).isEqualTo(ACTION_DISCOVER);
+ }
+
+ @Test
+ @Config(minSdk = O)
+ public void getServices_afterAddMultipleService_withCallback() {
+ shadowOf(bluetoothGatt).setGattCallback(callback);
+ shadowOf(bluetoothGatt).addDiscoverableService(service1);
+ shadowOf(bluetoothGatt).addDiscoverableService(service2);
+ assertThat(bluetoothGatt.discoverServices()).isTrue();
+ assertThat(bluetoothGatt.getServices()).hasSize(2);
+ assertThat(bluetoothGatt.getServices()).contains(service1);
+ assertThat(bluetoothGatt.getServices()).contains(service2);
+ assertThat(resultStatus).isEqualTo(BluetoothGatt.GATT_SUCCESS);
+ assertThat(resultAction).isEqualTo(ACTION_DISCOVER);
+ }
+
+ @Test
+ @Config(minSdk = O)
+ public void discoverServices_clearsService() {
+ shadowOf(bluetoothGatt).setGattCallback(callback);
+ shadowOf(bluetoothGatt).addDiscoverableService(service1);
+ shadowOf(bluetoothGatt).removeDiscoverableService(service1);
+ shadowOf(bluetoothGatt).removeDiscoverableService(service2);
+ assertThat(bluetoothGatt.discoverServices()).isFalse();
+ assertThat(bluetoothGatt.getServices()).isEmpty();
+ }
+
+ @Test
+ @Config
+ public void readIncomingCharacteristic_withoutCallback() {
+ assertThrows(
+ IllegalStateException.class,
+ () -> shadowOf(bluetoothGatt).readIncomingCharacteristic(characteristicWithReadProperty));
+ }
+
+ @Test
+ @Config
+ public void readIncomingCharacteristic_withCallback() {
+ shadowOf(bluetoothGatt).setGattCallback(callback);
+ assertThat(shadowOf(bluetoothGatt).readIncomingCharacteristic(characteristicWithReadProperty))
+ .isFalse();
+ assertThat(resultStatus).isEqualTo(INITIAL_VALUE);
+ assertThat(resultAction).isNull();
+ assertThat(resultCharacteristic).isNull();
+ assertThat(shadowOf(bluetoothGatt).getLatestReadBytes()).isNull();
+ }
+
+ @Test
+ @Config
+ public void readIncomingCharacteristic_withCallbackAndServiceSet() {
+ shadowOf(bluetoothGatt).setGattCallback(callback);
+ service1.addCharacteristic(characteristicWithReadProperty);
+ assertThat(characteristicWithReadProperty.getService()).isNotNull();
+ assertThat(shadowOf(bluetoothGatt).readIncomingCharacteristic(characteristicWithReadProperty))
+ .isTrue();
+ assertThat(resultStatus).isEqualTo(BluetoothGatt.GATT_SUCCESS);
+ assertThat(resultAction).isEqualTo(ACTION_READ);
+ assertThat(resultCharacteristic).isEqualTo(characteristicWithReadProperty);
+ assertThat(shadowOf(bluetoothGatt).getLatestReadBytes()).isNull();
+ }
+
+ @Test
+ @Config
+ public void readIncomingCharacteristic_withCallbackAndServiceSet_withValue() {
+ shadowOf(bluetoothGatt).setGattCallback(callback);
+ service1.addCharacteristic(characteristicWithReadProperty);
+ assertThat(characteristicWithReadProperty.getService()).isNotNull();
+ characteristicWithReadProperty.setValue(CHARACTERISTIC_VALUE);
+ assertThat(shadowOf(bluetoothGatt).readIncomingCharacteristic(characteristicWithReadProperty))
+ .isTrue();
+ assertThat(resultStatus).isEqualTo(BluetoothGatt.GATT_SUCCESS);
+ assertThat(resultAction).isEqualTo(ACTION_READ);
+ assertThat(resultCharacteristic).isEqualTo(characteristicWithReadProperty);
+ assertThat(shadowOf(bluetoothGatt).getLatestReadBytes()).isEqualTo(CHARACTERISTIC_VALUE);
+ }
+
+ @Test
+ @Config
+ public void readIncomingCharacteristic_withCallbackAndServiceSet_wrongProperty() {
+ shadowOf(bluetoothGatt).setGattCallback(callback);
+ service1.addCharacteristic(characteristicWithWriteProperties);
+ assertThat(characteristicWithWriteProperties.getService()).isNotNull();
+ assertThat(
+ shadowOf(bluetoothGatt).readIncomingCharacteristic(characteristicWithWriteProperties))
+ .isFalse();
+ assertThat(resultStatus).isEqualTo(INITIAL_VALUE);
+ assertThat(resultAction).isNull();
+ assertThat(resultCharacteristic).isNull();
+ assertThat(shadowOf(bluetoothGatt).getLatestReadBytes()).isNull();
+ }
+
+ @Test
+ @Config
+ public void writeIncomingCharacteristic_withoutCallback() {
+ service1.addCharacteristic(characteristicWithWriteProperties);
+ assertThrows(
+ IllegalStateException.class,
+ () ->
+ shadowOf(bluetoothGatt).writeIncomingCharacteristic(characteristicWithWriteProperties));
+ }
+
+ @Test
+ @Config
+ public void writeIncomingCharacteristic_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_withCallbackAndServiceSet() {
+ shadowOf(bluetoothGatt).setGattCallback(callback);
+ 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()).isNull();
+ }
+
+ @Test
+ @Config
+ public void writeIncomingCharacteristic_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();
+ assertThat(shadowOf(bluetoothGatt).getLatestWrittenBytes()).isNull();
+ }
+
+ @Test
+ @Config
+ public void writeIncomingCharacteristic_correctlySetup_noValue() {
+ shadowOf(bluetoothGatt).setGattCallback(callback);
+ service1.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()).isNull();
+ }
+
+ @Test
+ @Config
+ public void writeIncomingCharacteristic_correctlySetup_withValue() {
+ shadowOf(bluetoothGatt).setGattCallback(callback);
+ service1.addCharacteristic(characteristicWithWriteProperties);
+ characteristicWithWriteProperties.setValue(CHARACTERISTIC_VALUE);
+ 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_correctlySetup_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_correctlySetup_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);
}
}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothHeadsetTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothHeadsetTest.java
index 9a13954db..9482ba8d7 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothHeadsetTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowBluetoothHeadsetTest.java
@@ -2,6 +2,7 @@ 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;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
@@ -174,6 +175,20 @@ public class ShadowBluetoothHeadsetTest {
}
@Test
+ @Config(minSdk = S)
+ public void isVoiceRecognitionSupported_supportedByDefault() {
+ assertThat(bluetoothHeadset.isVoiceRecognitionSupported(device1)).isTrue();
+ }
+
+ @Test
+ @Config(minSdk = S)
+ public void setVoiceRecognitionSupported_false_notSupported() {
+ shadowOf(bluetoothHeadset).setVoiceRecognitionSupported(false);
+
+ assertThat(bluetoothHeadset.isVoiceRecognitionSupported(device1)).isFalse();
+ }
+
+ @Test
@Config(minSdk = KITKAT)
public void sendVendorSpecificResultCode_defaultsToTrueForConnectedDevice() {
shadowOf(bluetoothHeadset).addConnectedDevice(device1);
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowDevicePolicyManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowDevicePolicyManagerTest.java
index 09a3b5427..7d36f356a 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowDevicePolicyManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowDevicePolicyManagerTest.java
@@ -9,6 +9,9 @@ import static android.app.admin.DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_DEF
import static android.app.admin.DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER;
import static android.app.admin.DevicePolicyManager.ENCRYPTION_STATUS_INACTIVE;
import static android.app.admin.DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED;
+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.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH;
import static android.app.admin.DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT;
import static android.app.admin.DevicePolicyManager.STATE_USER_SETUP_COMPLETE;
@@ -1698,6 +1701,91 @@ public final class ShadowDevicePolicyManagerTest {
}
@Test
+ @Config(minSdk = P)
+ public void getLockTaskFeatures_nullAdmin_throwsNullPointerException() {
+ shadowOf(devicePolicyManager).setProfileOwner(testComponent);
+ assertThrows(NullPointerException.class, () -> devicePolicyManager.getLockTaskFeatures(null));
+ }
+
+ @Test
+ @Config(minSdk = P)
+ public void getLockTaskFeatures_notOwner_throwsSecurityException() {
+ assertThrows(
+ SecurityException.class, () -> devicePolicyManager.getLockTaskFeatures(testComponent));
+ }
+
+ @Test
+ @Config(minSdk = P)
+ public void getLockTaskFeatures_default_noFeatures() {
+ shadowOf(devicePolicyManager).setProfileOwner(testComponent);
+
+ assertThat(devicePolicyManager.getLockTaskFeatures(testComponent)).isEqualTo(0);
+ }
+
+ @Test
+ @Config(minSdk = P)
+ public void setLockTaskFeatures_nullAdmin_throwsNullPointerException() {
+ shadowOf(devicePolicyManager).setProfileOwner(testComponent);
+
+ assertThrows(
+ NullPointerException.class, () -> devicePolicyManager.setLockTaskFeatures(null, 0));
+ }
+
+ @Test
+ @Config(minSdk = P)
+ public void setLockTaskFeatures_notOwner_throwsSecurityException() {
+ assertThrows(
+ SecurityException.class, () -> devicePolicyManager.setLockTaskFeatures(testComponent, 0));
+ }
+
+ @Test
+ @Config(minSdk = P)
+ public void setLockTaskFeatures_overviewWithoutHome_throwsIllegalArgumentException() {
+ shadowOf(devicePolicyManager).setProfileOwner(testComponent);
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> devicePolicyManager.setLockTaskFeatures(testComponent, LOCK_TASK_FEATURE_OVERVIEW));
+ }
+
+ @Test
+ @Config(minSdk = P)
+ public void setLockTaskFeatures_notificationsWithoutHome_throwsIllegalArgumentException() {
+ shadowOf(devicePolicyManager).setProfileOwner(testComponent);
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ devicePolicyManager.setLockTaskFeatures(
+ testComponent, LOCK_TASK_FEATURE_NOTIFICATIONS));
+ }
+
+ @Test
+ @Config(minSdk = P)
+ public void setLockTaskFeatures_homeOverviewNotifications_success() {
+ shadowOf(devicePolicyManager).setProfileOwner(testComponent);
+
+ int flags =
+ LOCK_TASK_FEATURE_HOME | LOCK_TASK_FEATURE_OVERVIEW | LOCK_TASK_FEATURE_NOTIFICATIONS;
+ devicePolicyManager.setLockTaskFeatures(testComponent, flags);
+
+ assertThat(devicePolicyManager.getLockTaskFeatures(testComponent)).isEqualTo(flags);
+ }
+
+ @Test
+ @Config(minSdk = P)
+ public void setLockTaskFeatures_setFeaturesTwice_keepsLatestFeatures() {
+ shadowOf(devicePolicyManager).setProfileOwner(testComponent);
+ devicePolicyManager.setLockTaskFeatures(testComponent, LOCK_TASK_FEATURE_HOME);
+
+ int flags =
+ LOCK_TASK_FEATURE_HOME | LOCK_TASK_FEATURE_OVERVIEW | LOCK_TASK_FEATURE_NOTIFICATIONS;
+ devicePolicyManager.setLockTaskFeatures(testComponent, flags);
+
+ assertThat(devicePolicyManager.getLockTaskFeatures(testComponent)).isEqualTo(flags);
+ }
+
+ @Test
@Config(minSdk = LOLLIPOP)
public void getLockTaskPackages_notOwner() {
try {
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowImsMmTelManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowImsMmTelManagerTest.java
index d31d1858e..f0196218f 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowImsMmTelManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowImsMmTelManagerTest.java
@@ -11,10 +11,12 @@ import android.os.Build.VERSION_CODES;
import android.telephony.ims.ImsException;
import android.telephony.ims.ImsMmTelManager;
import android.telephony.ims.ImsMmTelManager.CapabilityCallback;
-import android.telephony.ims.ImsMmTelManager.RegistrationCallback;
import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.ImsRegistrationAttributes;
+import android.telephony.ims.RegistrationManager;
import android.telephony.ims.feature.MmTelFeature.MmTelCapabilities;
import android.telephony.ims.stub.ImsRegistrationImplBase;
+import android.util.ArraySet;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -34,9 +36,152 @@ public class ShadowImsMmTelManagerTest {
}
@Test
- public void registerImsRegistrationCallback_imsRegistering_onRegisteringInvoked()
+ public void registerImsRegistrationManagerCallback_imsRegistering_onRegisteringInvoked()
throws ImsException {
- RegistrationCallback registrationCallback = mock(RegistrationCallback.class);
+ RegistrationManager.RegistrationCallback registrationCallback =
+ mock(RegistrationManager.RegistrationCallback.class);
+
+ shadowImsMmTelManager.registerImsRegistrationCallback(Runnable::run, registrationCallback);
+ shadowImsMmTelManager.setImsRegistering(ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+
+ verify(registrationCallback).onRegistering(ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+
+ shadowImsMmTelManager.unregisterImsRegistrationCallback(registrationCallback);
+ shadowImsMmTelManager.setImsRegistering(ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+
+ verifyNoMoreInteractions(registrationCallback);
+ }
+
+ @Test
+ @Config(sdk = {VERSION_CODES.S, Config.NEWEST_SDK})
+ public void registerImsRegistrationManagerCallbackImsAttrs_imsRegistering_onRegisteringInvoked()
+ throws ImsException {
+ RegistrationManager.RegistrationCallback registrationCallback =
+ mock(RegistrationManager.RegistrationCallback.class);
+
+ int imsRegistrationTech = ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN;
+ int imsTransportType = RegistrationManager.getAccessType(imsRegistrationTech);
+ int imsAttributeFlags = 0;
+ ArraySet<String> featureTags = new ArraySet<>();
+
+ ImsRegistrationAttributes imsRegistrationAttrs =
+ new ImsRegistrationAttributes(
+ imsRegistrationTech, imsTransportType, imsAttributeFlags, featureTags);
+
+ shadowImsMmTelManager.registerImsRegistrationCallback(Runnable::run, registrationCallback);
+ shadowImsMmTelManager.setImsRegistering(imsRegistrationAttrs);
+
+ verify(registrationCallback).onRegistering(imsRegistrationAttrs);
+
+ shadowImsMmTelManager.unregisterImsRegistrationCallback(registrationCallback);
+ shadowImsMmTelManager.setImsRegistering(imsRegistrationAttrs);
+
+ verifyNoMoreInteractions(registrationCallback);
+ }
+
+ @Test
+ public void registerImsRegistrationManagerCallback_imsRegistered_onRegisteredInvoked()
+ throws ImsException {
+ RegistrationManager.RegistrationCallback registrationCallback =
+ mock(RegistrationManager.RegistrationCallback.class);
+ shadowImsMmTelManager.registerImsRegistrationCallback(Runnable::run, registrationCallback);
+ shadowImsMmTelManager.setImsRegistered(ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN);
+
+ verify(registrationCallback).onRegistered(ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN);
+
+ shadowImsMmTelManager.unregisterImsRegistrationCallback(registrationCallback);
+ shadowImsMmTelManager.setImsRegistered(ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+
+ verifyNoMoreInteractions(registrationCallback);
+ }
+
+ @Test
+ @Config(sdk = {VERSION_CODES.S, Config.NEWEST_SDK})
+ public void registerImsRegistrationManagerCallbackImsAttrs_imsRegistered_onRegisteredInvoked()
+ throws ImsException {
+ RegistrationManager.RegistrationCallback registrationCallback =
+ mock(RegistrationManager.RegistrationCallback.class);
+
+ int imsRegistrationTech = ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN;
+ int imsTransportType = RegistrationManager.getAccessType(imsRegistrationTech);
+ int imsAttributeFlags = 0;
+ ArraySet<String> featureTags = new ArraySet<>();
+
+ ImsRegistrationAttributes imsRegistrationAttrs =
+ new ImsRegistrationAttributes(
+ imsRegistrationTech, imsTransportType, imsAttributeFlags, featureTags);
+
+ shadowImsMmTelManager.registerImsRegistrationCallback(Runnable::run, registrationCallback);
+ shadowImsMmTelManager.setImsRegistered(imsRegistrationAttrs);
+
+ verify(registrationCallback).onRegistered(imsRegistrationAttrs);
+
+ shadowImsMmTelManager.unregisterImsRegistrationCallback(registrationCallback);
+ shadowImsMmTelManager.setImsRegistered(imsRegistrationAttrs);
+
+ verifyNoMoreInteractions(registrationCallback);
+ }
+
+ @Test
+ public void registerImsRegistrationManagerCallback_imsDeregistered_onDeregisteredInvoked()
+ throws ImsException {
+ RegistrationManager.RegistrationCallback registrationCallback =
+ mock(RegistrationManager.RegistrationCallback.class);
+ shadowImsMmTelManager.registerImsRegistrationCallback(Runnable::run, registrationCallback);
+ ImsReasonInfo imsReasonInfoWithCallbackRegistered = new ImsReasonInfo();
+ shadowImsMmTelManager.setImsUnregistered(imsReasonInfoWithCallbackRegistered);
+
+ verify(registrationCallback).onUnregistered(imsReasonInfoWithCallbackRegistered);
+
+ ImsReasonInfo imsReasonInfoAfterUnregisteringCallback = new ImsReasonInfo();
+ shadowImsMmTelManager.unregisterImsRegistrationCallback(registrationCallback);
+ shadowImsMmTelManager.setImsUnregistered(imsReasonInfoAfterUnregisteringCallback);
+
+ verifyNoMoreInteractions(registrationCallback);
+ }
+
+ @Test
+ public void
+ registerImsRegistrationManagerCallback_imsTechnologyChangeFailed_onTechnologyChangeFailedInvoked()
+ throws ImsException {
+ RegistrationManager.RegistrationCallback registrationCallback =
+ mock(RegistrationManager.RegistrationCallback.class);
+ shadowImsMmTelManager.registerImsRegistrationCallback(Runnable::run, registrationCallback);
+ ImsReasonInfo imsReasonInfoWithCallbackRegistered = new ImsReasonInfo();
+ shadowImsMmTelManager.setOnTechnologyChangeFailed(
+ ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN, imsReasonInfoWithCallbackRegistered);
+
+ verify(registrationCallback)
+ .onTechnologyChangeFailed(
+ ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN, imsReasonInfoWithCallbackRegistered);
+
+ ImsReasonInfo imsReasonInfoAfterUnregisteringCallback = new ImsReasonInfo();
+ shadowImsMmTelManager.unregisterImsRegistrationCallback(registrationCallback);
+ shadowImsMmTelManager.setOnTechnologyChangeFailed(
+ ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN, imsReasonInfoAfterUnregisteringCallback);
+
+ verifyNoMoreInteractions(registrationCallback);
+ }
+
+ @Test
+ public void
+ registerImsMmTelManagerRegistrationManagerCallback_imsNotSupported_imsExceptionThrown() {
+ shadowImsMmTelManager.setImsAvailableOnDevice(false);
+ try {
+ shadowImsMmTelManager.registerImsRegistrationCallback(
+ Runnable::run, mock(RegistrationManager.RegistrationCallback.class));
+ assertWithMessage("Expected ImsException was not thrown").fail();
+ } catch (ImsException e) {
+ assertThat(e.getCode()).isEqualTo(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION);
+ assertThat(e).hasMessageThat().contains("IMS not available on device.");
+ }
+ }
+
+ @Test
+ public void registerImsMmTelManagerRegistrationCallback_imsRegistering_onRegisteringInvoked()
+ throws ImsException {
+ ImsMmTelManager.RegistrationCallback registrationCallback =
+ mock(ImsMmTelManager.RegistrationCallback.class);
shadowImsMmTelManager.registerImsRegistrationCallback(Runnable::run, registrationCallback);
shadowImsMmTelManager.setImsRegistering(ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
@@ -49,9 +194,10 @@ public class ShadowImsMmTelManagerTest {
}
@Test
- public void registerImsRegistrationCallback_imsRegistered_onRegisteredInvoked()
+ public void registerImsMmTelManagerRegistrationCallback_imsRegistered_onRegisteredInvoked()
throws ImsException {
- RegistrationCallback registrationCallback = mock(RegistrationCallback.class);
+ ImsMmTelManager.RegistrationCallback registrationCallback =
+ mock(ImsMmTelManager.RegistrationCallback.class);
shadowImsMmTelManager.registerImsRegistrationCallback(Runnable::run, registrationCallback);
shadowImsMmTelManager.setImsRegistered(ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN);
@@ -64,9 +210,10 @@ public class ShadowImsMmTelManagerTest {
}
@Test
- public void registerImsRegistrationCallback_imsUnregistered_onUnregisteredInvoked()
+ public void registerImsMmTelManagerRegistrationCallback_imsUnregistered_onUnregisteredInvoked()
throws ImsException {
- RegistrationCallback registrationCallback = mock(RegistrationCallback.class);
+ ImsMmTelManager.RegistrationCallback registrationCallback =
+ mock(ImsMmTelManager.RegistrationCallback.class);
shadowImsMmTelManager.registerImsRegistrationCallback(Runnable::run, registrationCallback);
ImsReasonInfo imsReasonInfoWithCallbackRegistered = new ImsReasonInfo();
shadowImsMmTelManager.setImsUnregistered(imsReasonInfoWithCallbackRegistered);
@@ -81,11 +228,11 @@ public class ShadowImsMmTelManagerTest {
}
@Test
- public void registerImsRegistrationCallback_imsNotSupported_imsExceptionThrown() {
+ public void registerImsMmTelManagerRegistrationCallback_imsNotSupported_imsExceptionThrown() {
shadowImsMmTelManager.setImsAvailableOnDevice(false);
try {
shadowImsMmTelManager.registerImsRegistrationCallback(
- Runnable::run, mock(RegistrationCallback.class));
+ Runnable::run, mock(ImsMmTelManager.RegistrationCallback.class));
assertWithMessage("Expected ImsException was not thrown").fail();
} catch (ImsException e) {
assertThat(e.getCode()).isEqualTo(ImsException.CODE_ERROR_UNSUPPORTED_OPERATION);
@@ -98,7 +245,8 @@ public class ShadowImsMmTelManagerTest {
registerMmTelCapabilityCallback_imsRegistered_availabilityChange_onCapabilitiesStatusChangedInvoked()
throws ImsException {
MmTelCapabilities[] mmTelCapabilities = new MmTelCapabilities[1];
- CapabilityCallback capabilityCallback = new CapabilityCallback() {
+ CapabilityCallback capabilityCallback =
+ new CapabilityCallback() {
@Override
public void onCapabilitiesStatusChanged(MmTelCapabilities capabilities) {
super.onCapabilitiesStatusChanged(capabilities);
@@ -129,7 +277,8 @@ public class ShadowImsMmTelManagerTest {
registerMmTelCapabilityCallback_imsNotRegistered_availabilityChange_onCapabilitiesStatusChangedNotInvoked()
throws ImsException {
MmTelCapabilities[] mmTelCapabilities = new MmTelCapabilities[1];
- CapabilityCallback capabilityCallback = new CapabilityCallback() {
+ CapabilityCallback capabilityCallback =
+ new CapabilityCallback() {
@Override
public void onCapabilitiesStatusChanged(MmTelCapabilities capabilities) {
super.onCapabilitiesStatusChanged(capabilities);
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowInsetsControllerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowInsetsControllerTest.java
new file mode 100644
index 000000000..45c428416
--- /dev/null
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowInsetsControllerTest.java
@@ -0,0 +1,72 @@
+package org.robolectric.shadows;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Activity;
+import android.os.Build;
+import android.view.WindowInsets;
+import android.view.WindowInsetsController;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.android.controller.ActivityController;
+import org.robolectric.annotation.Config;
+
+@RunWith(AndroidJUnit4.class)
+@Config(minSdk = Build.VERSION_CODES.R)
+public class ShadowInsetsControllerTest {
+ private ActivityController<Activity> activityController;
+ private Activity activity;
+ private WindowInsetsController controller;
+
+ @Before
+ public void setUp() {
+ activityController = Robolectric.buildActivity(Activity.class);
+ activityController.setup();
+
+ activity = activityController.get();
+ controller = activity.getWindow().getInsetsController();
+ }
+
+ @Test
+ public void statusBar_show_hide_trackedByWindowInsets() {
+ // Responds to hide.
+ controller.hide(WindowInsets.Type.statusBars());
+ assertStatusBarVisibility(/* isVisible= */ false);
+
+ // Responds to show.
+ controller.show(WindowInsets.Type.statusBars());
+ assertStatusBarVisibility(/* isVisible= */ true);
+
+ // Does not respond to different type.
+ controller.hide(WindowInsets.Type.navigationBars());
+ assertStatusBarVisibility(/* isVisible= */ true);
+ }
+
+ @Test
+ public void navigationBar_show_hide_trackedByWindowInsets() {
+ // Responds to hide.
+ controller.hide(WindowInsets.Type.navigationBars());
+ assertNavigationBarVisibility(/* isVisible= */ false);
+
+ // Responds to show.
+ controller.show(WindowInsets.Type.navigationBars());
+ assertNavigationBarVisibility(/* isVisible= */ true);
+
+ // Does not respond to different type.
+ controller.hide(WindowInsets.Type.statusBars());
+ assertNavigationBarVisibility(/* isVisible= */ true);
+ }
+
+ private void assertStatusBarVisibility(boolean isVisible) {
+ WindowInsets insets = activity.getWindow().getDecorView().getRootWindowInsets();
+ assertThat(insets.isVisible(WindowInsets.Type.statusBars())).isEqualTo(isVisible);
+ }
+
+ private void assertNavigationBarVisibility(boolean isVisible) {
+ WindowInsets insets = activity.getWindow().getDecorView().getRootWindowInsets();
+ assertThat(insets.isVisible(WindowInsets.Type.navigationBars())).isEqualTo(isVisible);
+ }
+}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowLayoutAnimationControllerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowLayoutAnimationControllerTest.java
deleted file mode 100644
index 75a1c8d5f..000000000
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowLayoutAnimationControllerTest.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package org.robolectric.shadows;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.view.animation.LayoutAnimationController;
-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.Shadows;
-
-@RunWith(AndroidJUnit4.class)
-public class ShadowLayoutAnimationControllerTest {
- private ShadowLayoutAnimationController shadow;
-
- @Before
- public void setup() {
- LayoutAnimationController controller =
- new LayoutAnimationController(ApplicationProvider.getApplicationContext(), null);
- shadow = Shadows.shadowOf(controller);
- }
-
- @Test
- public void testResourceId() {
- int id = 1;
- shadow.setLoadedFromResourceId(1);
- assertThat(shadow.getLoadedFromResourceId()).isEqualTo(id);
- }
-
-}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowMatrixTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowMatrixTest.java
index 8ee1f6f97..bd99b4c83 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowMatrixTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowMatrixTest.java
@@ -2,7 +2,6 @@ package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static com.google.common.truth.Truth.assertThat;
-import static org.robolectric.Shadows.shadowOf;
import android.graphics.Matrix;
import android.graphics.PointF;
@@ -11,6 +10,7 @@ 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)
public class ShadowMatrixTest {
@@ -23,7 +23,7 @@ public class ShadowMatrixTest {
m.preTranslate(16, 23);
m.preSkew(42, 108);
- assertThat(shadowOf(m).getPreOperations())
+ assertThat(((ShadowMatrix) Shadow.extract(m)).getPreOperations())
.containsExactly("skew 42.0 108.0", "translate 16.0 23.0", "rotate 4.0 8.0 15.0");
}
@@ -34,7 +34,7 @@ public class ShadowMatrixTest {
m.postTranslate(16, 23);
m.postSkew(42, 108);
- assertThat(shadowOf(m).getPostOperations())
+ assertThat(((ShadowMatrix) Shadow.extract(m)).getPostOperations())
.containsExactly("rotate 4.0 8.0 15.0", "translate 16.0 23.0", "skew 42.0 108.0");
}
@@ -49,7 +49,8 @@ public class ShadowMatrixTest {
m.setRotate(42);
m.setRotate(108);
- assertThat(shadowOf(m).getSetOperations()).containsEntry("rotate", "108.0");
+ assertThat(((ShadowMatrix) Shadow.extract(m)).getSetOperations())
+ .containsEntry("rotate", "108.0");
}
@Test
@@ -59,7 +60,7 @@ public class ShadowMatrixTest {
matrix.preScale(2, 2, 2, 2);
matrix.postScale(3, 3, 3, 3);
- final ShadowMatrix shadow = shadowOf(matrix);
+ final ShadowMatrix shadow = Shadow.extract(matrix);
assertThat(shadow.getSetOperations().get("scale")).isEqualTo("1.0 1.0");
assertThat(shadow.getPreOperations().get(0)).isEqualTo("scale 2.0 2.0 2.0 2.0");
assertThat(shadow.getPostOperations().get(0)).isEqualTo("scale 3.0 3.0 3.0 3.0");
@@ -70,7 +71,7 @@ public class ShadowMatrixTest {
final Matrix matrix = new Matrix();
matrix.setScale(1, 2, 3, 4);
- final ShadowMatrix shadow = shadowOf(matrix);
+ final ShadowMatrix shadow = Shadow.extract(matrix);
assertThat(shadow.getSetOperations().get("scale")).isEqualTo("1.0 2.0 3.0 4.0");
}
@@ -83,7 +84,7 @@ public class ShadowMatrixTest {
matrix2.setScale(3, 4);
matrix2.set(matrix1);
- final ShadowMatrix shadow = shadowOf(matrix2);
+ final ShadowMatrix shadow = Shadow.extract(matrix2);
assertThat(shadow.getSetOperations().get("scale")).isEqualTo("1.0 2.0");
}
@@ -96,7 +97,7 @@ public class ShadowMatrixTest {
matrix2.set(matrix1);
matrix2.set(null);
- final ShadowMatrix shadow = shadowOf(matrix2);
+ final ShadowMatrix shadow = Shadow.extract(matrix2);
assertThat(shadow.getSetOperations()).isEmpty();
}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaControllerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaControllerTest.java
index 2bb0dbf91..2299246bd 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaControllerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowMediaControllerTest.java
@@ -21,6 +21,7 @@ import android.media.session.MediaController;
import android.media.session.MediaController.PlaybackInfo;
import android.media.session.MediaSession;
import android.media.session.PlaybackState;
+import android.os.Bundle;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.util.ArrayList;
@@ -134,6 +135,16 @@ public final class ShadowMediaControllerTest {
@Test
@Config(minSdk = LOLLIPOP)
+ public void setAndGetExtras() {
+ String extraKey = "test.extra.key";
+ Bundle extras = new Bundle();
+ extras.putBoolean(extraKey, true);
+ shadowMediaController.setExtras(extras);
+ assertEquals(true, mediaController.getExtras().getBoolean(extraKey, false));
+ }
+
+ @Test
+ @Config(minSdk = LOLLIPOP)
public void registerAndGetCallback() {
List<MediaController.Callback> mockCallbacks = new ArrayList<>();
assertEquals(mockCallbacks, shadowMediaController.getCallbacks());
@@ -151,6 +162,23 @@ public final class ShadowMediaControllerTest {
@Test
@Config(minSdk = LOLLIPOP)
+ public void registerWithHandlerAndGetCallback() {
+ List<MediaController.Callback> mockCallbacks = new ArrayList<>();
+ assertEquals(mockCallbacks, shadowMediaController.getCallbacks());
+
+ MediaController.Callback mockCallback1 = mock(MediaController.Callback.class);
+ mockCallbacks.add(mockCallback1);
+ mediaController.registerCallback(mockCallback1, null);
+ assertEquals(mockCallbacks, shadowMediaController.getCallbacks());
+
+ MediaController.Callback mockCallback2 = mock(MediaController.Callback.class);
+ mockCallbacks.add(mockCallback2);
+ mediaController.registerCallback(mockCallback2, null);
+ assertEquals(mockCallbacks, shadowMediaController.getCallbacks());
+ }
+
+ @Test
+ @Config(minSdk = LOLLIPOP)
public void unregisterCallback() {
List<MediaController.Callback> mockCallbacks = new ArrayList<>();
MediaController.Callback mockCallback1 = mock(MediaController.Callback.class);
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowNetworkCapabilitiesTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowNetworkCapabilitiesTest.java
index cb46834bf..727fce40f 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowNetworkCapabilitiesTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowNetworkCapabilitiesTest.java
@@ -107,4 +107,11 @@ public class ShadowNetworkCapabilitiesTest {
assertThat(wifiInfo.getSSID()).isEqualTo(String.format("\"%s\"", fakeSsid));
assertThat(wifiInfo.getBSSID()).isEqualTo(fakeBssid);
}
+
+ @Test
+ public void setLinkDownstreamBandwidthKbps() {
+ NetworkCapabilities networkCapabilities = ShadowNetworkCapabilities.newInstance();
+ shadowOf(networkCapabilities).setLinkDownstreamBandwidthKbps(100);
+ assertThat(networkCapabilities.getLinkDownstreamBandwidthKbps()).isEqualTo(100);
+ }
}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowPackageManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowPackageManagerTest.java
index 870bb1dc1..e3242fadd 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowPackageManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowPackageManagerTest.java
@@ -46,6 +46,7 @@ import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.common.truth.TruthJUnit.assume;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
@@ -76,6 +77,7 @@ import android.content.pm.ModuleInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.ApplicationInfoFlags;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManager.OnPermissionsChangedListener;
import android.content.pm.PackageManager.PackageInfoFlags;
@@ -2038,6 +2040,55 @@ public class ShadowPackageManagerTest {
}
@Test
+ @Config(minSdk = TIRAMISU)
+ public void getPackageInfoAfterT_shouldReturnRequestedPermissions() throws Exception {
+ PackageInfo packageInfo =
+ packageManager.getPackageInfo(
+ context.getPackageName(), PackageInfoFlags.of(PackageManager.GET_PERMISSIONS));
+ String[] permissions = packageInfo.requestedPermissions;
+ assertThat(permissions).isNotNull();
+ assertThat(permissions).hasLength(4);
+ }
+
+ @Test
+ @Config(minSdk = TIRAMISU)
+ public void getPackageInfoAfterT_uninstalledPackage_includeUninstalled() throws Exception {
+ String packageName = context.getPackageName();
+ shadowOf(packageManager).deletePackage(packageName);
+
+ PackageInfo info =
+ packageManager.getPackageInfo(packageName, PackageInfoFlags.of(MATCH_UNINSTALLED_PACKAGES));
+ assertThat(info).isNotNull();
+ assertThat(info.packageName).isEqualTo(packageName);
+ }
+
+ @Test
+ @Config(minSdk = TIRAMISU)
+ public void getPackageInfoAfterT_uninstalledPackage_dontIncludeUninstalled() {
+ String packageName = context.getPackageName();
+ shadowOf(packageManager).deletePackage(packageName);
+
+ try {
+ PackageInfo info = packageManager.getPackageInfo(packageName, PackageInfoFlags.of(0));
+ fail("should have thrown NameNotFoundException:" + info.applicationInfo.flags);
+ } catch (NameNotFoundException e) {
+ // expected
+ }
+ }
+
+ @Test
+ @Config(minSdk = TIRAMISU)
+ public void getPackageInfoAfterT_disabledPackage_includeDisabled() throws Exception {
+ packageManager.setApplicationEnabledSetting(
+ context.getPackageName(), COMPONENT_ENABLED_STATE_DISABLED, 0);
+ PackageInfo info =
+ packageManager.getPackageInfo(
+ context.getPackageName(), PackageInfoFlags.of(MATCH_DISABLED_COMPONENTS));
+ assertThat(info).isNotNull();
+ assertThat(info.packageName).isEqualTo(context.getPackageName());
+ }
+
+ @Test
public void getInstalledPackages_uninstalledPackage_includeUninstalled() {
shadowOf(packageManager).deletePackage(context.getPackageName());
@@ -2064,6 +2115,45 @@ public class ShadowPackageManagerTest {
}
@Test
+ @Config(minSdk = TIRAMISU)
+ public void getInstalledPackagesAfterT_uninstalledPackage_includeUninstalled() {
+ shadowOf(packageManager).deletePackage(context.getPackageName());
+
+ assertThat(packageManager.getInstalledPackages(PackageInfoFlags.of(MATCH_UNINSTALLED_PACKAGES)))
+ .isNotEmpty();
+ assertThat(
+ packageManager
+ .getInstalledPackages(PackageInfoFlags.of(MATCH_UNINSTALLED_PACKAGES))
+ .get(0)
+ .packageName)
+ .isEqualTo(context.getPackageName());
+ }
+
+ @Test
+ @Config(minSdk = TIRAMISU)
+ public void getInstalledPackagesAfterT_uninstalledPackage_dontIncludeUninstalled() {
+ shadowOf(packageManager).deletePackage(context.getPackageName());
+
+ assertThat(packageManager.getInstalledPackages(PackageInfoFlags.of(0))).isEmpty();
+ }
+
+ @Test
+ @Config(minSdk = TIRAMISU)
+ public void getInstalledPackagesAfterT_disabledPackage_includeDisabled() {
+ packageManager.setApplicationEnabledSetting(
+ context.getPackageName(), COMPONENT_ENABLED_STATE_DISABLED, 0);
+
+ assertThat(packageManager.getInstalledPackages(PackageInfoFlags.of(MATCH_DISABLED_COMPONENTS)))
+ .isNotEmpty();
+ assertThat(
+ packageManager
+ .getInstalledPackages(PackageInfoFlags.of(MATCH_DISABLED_COMPONENTS))
+ .get(0)
+ .packageName)
+ .isEqualTo(context.getPackageName());
+ }
+
+ @Test
public void testGetPreferredActivities() {
final String packageName = "com.example.dummy";
ComponentName name = new ComponentName(packageName, "LauncherActivity");
@@ -2390,6 +2480,24 @@ public class ShadowPackageManagerTest {
}
@Test
+ @Config(minSdk = TIRAMISU)
+ public void getPackageUid_sdkT() throws NameNotFoundException {
+ shadowOf(packageManager).setPackagesForUid(10, new String[] {"a_name"});
+ assertThat(packageManager.getPackageUid("a_name", PackageInfoFlags.of(0))).isEqualTo(10);
+ }
+
+ @Test
+ @Config(minSdk = TIRAMISU)
+ public void getPackageUid_sdkT_shouldThrowNameNotFoundExceptionIfNotExist() {
+ try {
+ packageManager.getPackageUid("a_name", PackageInfoFlags.of(0));
+ fail("should have thrown NameNotFoundException");
+ } catch (PackageManager.NameNotFoundException e) {
+ assertThat(e).hasMessageThat().contains("a_name");
+ }
+ }
+
+ @Test
public void getPackagesForUid_shouldReturnSetPackageName() {
shadowOf(packageManager).setPackagesForUid(10, new String[] {"a_name"});
assertThat(packageManager.getPackagesForUid(10)).asList().containsExactly("a_name");
@@ -2637,7 +2745,7 @@ public class ShadowPackageManagerTest {
}
@Test
- public void getInstalledApplications() {
+ public void getInstalledApplications_noFlags_oldSdk() {
List<ApplicationInfo> installedApplications = packageManager.getInstalledApplications(0);
// Default should include the application under test
@@ -2656,6 +2764,33 @@ public class ShadowPackageManagerTest {
}
@Test
+ @Config(minSdk = TIRAMISU)
+ public void getInstalledApplications_null_throwsException() {
+ assertThrows(Exception.class, () -> packageManager.getInstalledApplications(null));
+ }
+
+ @Test
+ @Config(minSdk = TIRAMISU)
+ public void getInstalledApplications_noFlags_returnsAllInstalledApplications() {
+ List<ApplicationInfo> installedApplications =
+ packageManager.getInstalledApplications(ApplicationInfoFlags.of(0));
+
+ // Default should include the application under test
+ assertThat(installedApplications).hasSize(1);
+ assertThat(installedApplications.get(0).packageName).isEqualTo("org.robolectric");
+
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = "org.other";
+ packageInfo.applicationInfo = new ApplicationInfo();
+ packageInfo.applicationInfo.packageName = "org.other";
+ shadowOf(packageManager).installPackage(packageInfo);
+
+ installedApplications = packageManager.getInstalledApplications(0);
+ assertThat(installedApplications).hasSize(2);
+ assertThat(installedApplications.get(1).packageName).isEqualTo("org.other");
+ }
+
+ @Test
public void getPermissionInfo() throws Exception {
PermissionInfo permission =
context.getPackageManager().getPermissionInfo("org.robolectric.some_permission", 0);
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowPhoneWindowTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowPhoneWindowTest.java
index 019cc7547..52721f069 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowPhoneWindowTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowPhoneWindowTest.java
@@ -5,12 +5,14 @@ import static org.robolectric.Shadows.shadowOf;
import android.app.Activity;
import android.graphics.drawable.Drawable;
+import android.os.Build.VERSION_CODES;
import android.view.Window;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
+import org.robolectric.annotation.Config;
@RunWith(AndroidJUnit4.class)
public class ShadowPhoneWindowTest {
@@ -36,4 +38,26 @@ public class ShadowPhoneWindowTest {
window.setBackgroundDrawable(drawable);
assertThat(shadowOf(window).getBackgroundDrawable()).isSameInstanceAs(drawable);
}
+
+ @Test
+ @Config(minSdk = VERSION_CODES.R)
+ public void getDecorFitsSystemWindows_noCall_returnsDefault() {
+ ShadowWindow candidate = shadowOf(window);
+ assertThat(candidate).isInstanceOf(ShadowPhoneWindow.class);
+
+ assertThat(((ShadowPhoneWindow) candidate).getDecorFitsSystemWindows()).isTrue();
+ }
+
+ @Test
+ @Config(minSdk = VERSION_CODES.R)
+ public void getDecorFitsSystemWindows_recordsLastValue() {
+ ShadowWindow candidate = shadowOf(window);
+ assertThat(candidate).isInstanceOf(ShadowPhoneWindow.class);
+
+ window.setDecorFitsSystemWindows(true);
+ assertThat(((ShadowPhoneWindow) candidate).getDecorFitsSystemWindows()).isTrue();
+
+ window.setDecorFitsSystemWindows(false);
+ assertThat(((ShadowPhoneWindow) candidate).getDecorFitsSystemWindows()).isFalse();
+ }
} \ No newline at end of file
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowSensorManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowSensorManagerTest.java
index ee02ce296..3f2417136 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowSensorManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowSensorManagerTest.java
@@ -11,12 +11,16 @@ import android.hardware.Sensor;
import android.hardware.SensorDirectChannel;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
+import android.hardware.SensorEventListener2;
import android.hardware.SensorManager;
import android.os.Build;
+import android.os.Looper;
import android.os.MemoryFile;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.common.base.Optional;
+import java.util.ArrayList;
+import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -226,8 +230,56 @@ public class ShadowSensorManagerTest {
assertThat(sensorManager.getSensorList(0)).isNotNull();
}
- private static class TestSensorEventListener implements SensorEventListener {
+ @Test
+ @Config(minSdk = Build.VERSION_CODES.KITKAT)
+ public void flush_shouldCallOnFlushCompleted() {
+ Sensor accelSensor = ShadowSensor.newInstance(TYPE_ACCELEROMETER);
+ Sensor gyroSensor = ShadowSensor.newInstance(TYPE_GYROSCOPE);
+
+ TestSensorEventListener listener1 = new TestSensorEventListener();
+ TestSensorEventListener listener2 = new TestSensorEventListener();
+ TestSensorEventListener listener3 = new TestSensorEventListener();
+
+ sensorManager.registerListener(listener1, accelSensor, SensorManager.SENSOR_DELAY_NORMAL);
+ sensorManager.registerListener(listener2, accelSensor, SensorManager.SENSOR_DELAY_NORMAL);
+ sensorManager.registerListener(listener2, gyroSensor, SensorManager.SENSOR_DELAY_NORMAL);
+
+ // Call flush with the first listener. It should return true (as the flush
+ // succeeded), and should call onFlushCompleted for all listeners registered for accelSensor.
+ assertThat(sensorManager.flush(listener1)).isTrue();
+ shadowOf(Looper.getMainLooper()).idle();
+
+ assertThat(listener1.getOnFlushCompletedCalls()).containsExactly(accelSensor);
+ assertThat(listener2.getOnFlushCompletedCalls()).containsExactly(accelSensor);
+ assertThat(listener3.getOnFlushCompletedCalls()).isEmpty();
+
+ // Call flush with the second listener. It should again return true, and should call
+ // onFlushCompleted for all listeners registered for accelSensor and gyroSensor.
+ assertThat(sensorManager.flush(listener2)).isTrue();
+ shadowOf(Looper.getMainLooper()).idle();
+
+ // From the two calls to flush, onFlushCompleted should have been called twice for accelSensor
+ // and once for gyroSensor.
+ assertThat(listener1.getOnFlushCompletedCalls()).containsExactly(accelSensor, accelSensor);
+ assertThat(listener2.getOnFlushCompletedCalls())
+ .containsExactly(accelSensor, accelSensor, gyroSensor);
+ assertThat(listener3.getOnFlushCompletedCalls()).isEmpty();
+
+ // Call flush with the third listener. This listener is not registered for any sensors, so it
+ // should return false.
+ assertThat(sensorManager.flush(listener3)).isFalse();
+ shadowOf(Looper.getMainLooper()).idle();
+
+ // There should not have been any more onFlushCompleted calls.
+ assertThat(listener1.getOnFlushCompletedCalls()).containsExactly(accelSensor, accelSensor);
+ assertThat(listener2.getOnFlushCompletedCalls())
+ .containsExactly(accelSensor, accelSensor, gyroSensor);
+ assertThat(listener3.getOnFlushCompletedCalls()).isEmpty();
+ }
+
+ private static class TestSensorEventListener implements SensorEventListener2 {
private Optional<SensorEvent> latestSensorEvent = Optional.absent();
+ private List<Sensor> onFlushCompletedCalls = new ArrayList<>();
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {}
@@ -237,6 +289,15 @@ public class ShadowSensorManagerTest {
latestSensorEvent = Optional.of(event);
}
+ @Override
+ public void onFlushCompleted(Sensor sensor) {
+ onFlushCompletedCalls.add(sensor);
+ }
+
+ public List<Sensor> getOnFlushCompletedCalls() {
+ return onFlushCompletedCalls;
+ }
+
public Optional<SensorEvent> getLatestSensorEvent() {
return latestSensorEvent;
}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowSubscriptionManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowSubscriptionManagerTest.java
index 9ea0e9a12..4e7fb16ce 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowSubscriptionManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowSubscriptionManagerTest.java
@@ -3,6 +3,8 @@ 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.P;
+import static android.os.Build.VERSION_CODES.R;
+import static android.os.Build.VERSION_CODES.TIRAMISU;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
@@ -31,6 +33,14 @@ public class ShadowSubscriptionManagerTest {
getApplicationContext().getSystemService(TELEPHONY_SUBSCRIPTION_SERVICE);
}
+ @Config(minSdk = R)
+ @Test
+ public void shouldGiveActiveDataSubscriptionId() {
+ int testId = 42;
+ ShadowSubscriptionManager.setActiveDataSubscriptionId(testId);
+ assertThat(SubscriptionManager.getActiveDataSubscriptionId()).isEqualTo(testId);
+ }
+
@Test
public void shouldGiveDefaultSubscriptionId() {
int testId = 42;
@@ -161,24 +171,24 @@ public class ShadowSubscriptionManagerTest {
@Test
public void isNetworkRoaming_shouldReturnTrueIfSet() {
- shadowOf(subscriptionManager).setNetworkRoamingStatus(123, /*isNetworkRoaming=*/ true);
+ shadowOf(subscriptionManager).setNetworkRoamingStatus(123, /* isNetworkRoaming= */ true);
assertThat(shadowOf(subscriptionManager).isNetworkRoaming(123)).isTrue();
}
/** Multi act-asserts are discouraged but here we are testing the set+unset. */
@Test
public void isNetworkRoaming_shouldReturnFalseIfUnset() {
- shadowOf(subscriptionManager).setNetworkRoamingStatus(123, /*isNetworkRoaming=*/ true);
+ shadowOf(subscriptionManager).setNetworkRoamingStatus(123, /* isNetworkRoaming= */ true);
assertThat(shadowOf(subscriptionManager).isNetworkRoaming(123)).isTrue();
- shadowOf(subscriptionManager).setNetworkRoamingStatus(123, /*isNetworkRoaming=*/ false);
+ shadowOf(subscriptionManager).setNetworkRoamingStatus(123, /* isNetworkRoaming= */ false);
assertThat(shadowOf(subscriptionManager).isNetworkRoaming(123)).isFalse();
}
/** Multi act-asserts are discouraged but here we are testing the set+clear. */
@Test
public void isNetworkRoaming_shouldReturnFalseOnClear() {
- shadowOf(subscriptionManager).setNetworkRoamingStatus(123, /*isNetworkRoaming=*/ true);
+ shadowOf(subscriptionManager).setNetworkRoamingStatus(123, /* isNetworkRoaming= */ true);
assertThat(shadowOf(subscriptionManager).isNetworkRoaming(123)).isTrue();
shadowOf(subscriptionManager).clearNetworkRoamingStatus();
@@ -305,6 +315,22 @@ public class ShadowSubscriptionManagerTest {
.isEqualTo(123);
}
+ @Test
+ @Config(minSdk = TIRAMISU)
+ public void getPhoneNumber_phoneNumberNotSet_returnsEmptyString() {
+ assertThat(subscriptionManager.getPhoneNumber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID))
+ .isEqualTo("");
+ }
+
+ @Test
+ @Config(minSdk = TIRAMISU)
+ public void getPhoneNumber_setPhoneNumber_returnsPhoneNumber() {
+ shadowOf(subscriptionManager)
+ .setPhoneNumber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, "123");
+ assertThat(subscriptionManager.getPhoneNumber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID))
+ .isEqualTo("123");
+ }
+
private static class DummySubscriptionsChangedListener
extends SubscriptionManager.OnSubscriptionsChangedListener {
private int subscriptionChangedCount;
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyManagerTest.java
index 5f6d6b885..cb6359f8e 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowTelephonyManagerTest.java
@@ -33,6 +33,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
@@ -45,6 +46,7 @@ import static org.robolectric.Shadows.shadowOf;
import static org.robolectric.shadows.ShadowTelephonyManager.createTelephonyDisplayInfo;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build.VERSION;
@@ -962,7 +964,7 @@ public class ShadowTelephonyManagerTest {
@Test
@Config(minSdk = S)
public void setCallComposerStatus() {
- ShadowTelephonyManager.setCallComposerStatus(CALL_COMPOSER_STATUS_ON);
+ telephonyManager.setCallComposerStatus(CALL_COMPOSER_STATUS_ON);
assertThat(telephonyManager.getCallComposerStatus()).isEqualTo(CALL_COMPOSER_STATUS_ON);
}
@@ -1030,4 +1032,12 @@ public class ShadowTelephonyManagerTest {
assertThat(shadowOf(telephonyManager).getVisualVoicemailSmsFilterSettings()).isNull();
}
+
+ @Test
+ @Config(minSdk = Q)
+ public void isEmergencyNumber_telephonyServiceUnavailable_throwsIllegalStateException() {
+ ShadowServiceManager.setServiceAvailability(Context.TELEPHONY_SERVICE, false);
+
+ assertThrows(IllegalStateException.class, () -> telephonyManager.isEmergencyNumber("911"));
+ }
}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowTypefaceTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowTypefaceTest.java
index 47789de3b..c86c5e95d 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowTypefaceTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowTypefaceTest.java
@@ -162,7 +162,7 @@ public class ShadowTypefaceTest {
// This invokes the Typeface static initializer, which creates some default typefaces.
Typeface.create("roboto", Typeface.BOLD);
// Call the resetter to clear the FONTS map in Typeface
- ShadowTypeface.reset();
+ ShadowLegacyTypeface.reset();
Typeface typeface =
new Typeface.CustomFallbackBuilder(family).setStyle(font.getStyle()).build();
assertThat(typeface).isNotNull();
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowUserManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowUserManagerTest.java
index d165ec10d..75df1101a 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowUserManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowUserManagerTest.java
@@ -7,7 +7,6 @@ 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.N_MR1;
-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 com.google.common.truth.Truth.assertThat;
@@ -907,13 +906,13 @@ public class ShadowUserManagerTest {
}
@Test
- @Config(minSdk = O)
+ @Config(minSdk = N)
public void isQuietModeEnabled_shouldReturnFalse() {
assertThat(userManager.isQuietModeEnabled(Process.myUserHandle())).isFalse();
}
@Test
- @Config(minSdk = Q)
+ @Config(minSdk = N)
public void isQuietModeEnabled_withProfile_shouldReturnFalse() {
shadowOf(userManager).addProfile(0, 10, "Work profile", UserInfo.FLAG_MANAGED_PROFILE);
@@ -921,6 +920,16 @@ public class ShadowUserManagerTest {
}
@Test
+ @Config(minSdk = N)
+ public void isQuietModeEnabled_withProfileQuietMode_shouldReturnTrue() {
+ shadowOf(userManager)
+ .addProfile(
+ 0, 10, "Work profile", UserInfo.FLAG_MANAGED_PROFILE | UserInfo.FLAG_QUIET_MODE);
+
+ assertThat(userManager.isQuietModeEnabled(new UserHandle(10))).isTrue();
+ }
+
+ @Test
@Config(minSdk = Q)
public void requestQuietModeEnabled_withoutPermission_shouldThrowException() {
shadowOf(userManager).enforcePermissionChecks(true);
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowViewRootImplTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowViewRootImplTest.java
new file mode 100644
index 000000000..e10cbb3fa
--- /dev/null
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowViewRootImplTest.java
@@ -0,0 +1,55 @@
+package org.robolectric.shadows;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Activity;
+import android.os.Build;
+import android.view.View;
+import android.view.WindowInsets;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.android.controller.ActivityController;
+import org.robolectric.annotation.Config;
+
+@RunWith(AndroidJUnit4.class)
+public class ShadowViewRootImplTest {
+ private ActivityController<Activity> activityController;
+ private Activity activity;
+ private View rootView;
+
+ @Before
+ public void setUp() {
+ activityController = Robolectric.buildActivity(Activity.class);
+ activityController.setup();
+
+ activity = activityController.get();
+ rootView = activity.getWindow().getDecorView();
+ }
+
+ @Test
+ @Config(minSdk = Build.VERSION_CODES.R)
+ public void setIsStatusBarVisible_impactsGetWindowInsets() {
+ ShadowViewRootImpl.setIsStatusBarVisible(false);
+ WindowInsets windowInsets = rootView.getRootWindowInsets();
+ assertThat(windowInsets.isVisible(WindowInsets.Type.statusBars())).isFalse();
+
+ ShadowViewRootImpl.setIsStatusBarVisible(true);
+ windowInsets = rootView.getRootWindowInsets();
+ assertThat(windowInsets.isVisible(WindowInsets.Type.statusBars())).isTrue();
+ }
+
+ @Test
+ @Config(minSdk = Build.VERSION_CODES.R)
+ public void setIsNavigationBarVisible_impactsGetWindowInsets() {
+ ShadowViewRootImpl.setIsNavigationBarVisible(false);
+ WindowInsets windowInsets = rootView.getRootWindowInsets();
+ assertThat(windowInsets.isVisible(WindowInsets.Type.navigationBars())).isFalse();
+
+ ShadowViewRootImpl.setIsNavigationBarVisible(true);
+ windowInsets = rootView.getRootWindowInsets();
+ assertThat(windowInsets.isVisible(WindowInsets.Type.navigationBars())).isTrue();
+ }
+}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowWallpaperManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowWallpaperManagerTest.java
index 89535f6d0..51e69abfb 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowWallpaperManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowWallpaperManagerTest.java
@@ -3,7 +3,6 @@ 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.P;
import static android.os.Build.VERSION_CODES.TIRAMISU;
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.fail;
@@ -17,12 +16,11 @@ import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
-import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import com.google.common.io.ByteStreams;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
-import java.io.Closeable;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
@@ -43,7 +41,7 @@ public class ShadowWallpaperManagerTest {
private static final Bitmap TEST_IMAGE_3 = Bitmap.createBitmap(1, 5, Bitmap.Config.ARGB_8888);
- private static final int UNSUPPORTED_FLAG = WallpaperManager.FLAG_LOCK + 123;
+ private static final int UNSUPPORTED_FLAG = 0x100; // neither FLAG_SYSTEM nor FLAG_LOCK
private static final String SET_WALLPAPER_COMPONENT =
"android.permission.SET_WALLPAPER_COMPONENT";
@@ -150,7 +148,7 @@ public class ShadowWallpaperManagerTest {
}
@Test
- @Config(minSdk = P)
+ @Config(minSdk = N)
public void setBitmap_flagSystem_shouldCacheInMemory() throws Exception {
int returnCode =
manager.setBitmap(
@@ -165,7 +163,7 @@ public class ShadowWallpaperManagerTest {
}
@Test
- @Config(minSdk = P)
+ @Config(minSdk = N)
public void setBitmap_liveWallpaperWasDefault_flagSystem_shouldRemoveLiveWallpaper()
throws Exception {
manager.setWallpaperComponent(TEST_WALLPAPER_SERVICE);
@@ -180,7 +178,7 @@ public class ShadowWallpaperManagerTest {
}
@Test
- @Config(minSdk = P)
+ @Config(minSdk = N)
public void setBitmap_multipleCallsWithFlagSystem_shouldCacheLastBitmapInMemory()
throws Exception {
manager.setBitmap(
@@ -204,7 +202,7 @@ public class ShadowWallpaperManagerTest {
}
@Test
- @Config(minSdk = P)
+ @Config(minSdk = N)
public void setBitmap_flagLock_shouldCacheInMemory() throws Exception {
int returnCode =
manager.setBitmap(
@@ -219,7 +217,7 @@ public class ShadowWallpaperManagerTest {
}
@Test
- @Config(minSdk = P)
+ @Config(minSdk = N)
public void setBitmap_liveWallpaperWasDefault_flagLock_shouldRemoveLiveWallpaper()
throws Exception {
manager.setWallpaperComponent(TEST_WALLPAPER_SERVICE);
@@ -234,7 +232,7 @@ public class ShadowWallpaperManagerTest {
}
@Test
- @Config(minSdk = P)
+ @Config(minSdk = N)
public void setBitmap_multipleCallsWithFlagLock_shouldCacheLastBitmapInMemory() throws Exception {
manager.setBitmap(
TEST_IMAGE_1,
@@ -257,7 +255,7 @@ public class ShadowWallpaperManagerTest {
}
@Test
- @Config(minSdk = P)
+ @Config(minSdk = N)
public void setBitmap_unsupportedFlag_shouldNotCacheInMemory() throws Exception {
int code =
manager.setBitmap(
@@ -268,7 +266,7 @@ public class ShadowWallpaperManagerTest {
}
@Test
- @Config(minSdk = P)
+ @Config(minSdk = N)
public void setBitmap_liveWallpaperWasDefault_unsupportedFlag_shouldNotRemoveLiveWallpaper()
throws Exception {
manager.setWallpaperComponent(TEST_WALLPAPER_SERVICE);
@@ -280,13 +278,13 @@ public class ShadowWallpaperManagerTest {
}
@Test
- @Config(minSdk = P)
+ @Config(minSdk = N)
public void getWallpaperFile_flagSystem_nothingCached_shouldReturnNull() throws Exception {
assertThat(manager.getWallpaperFile(WallpaperManager.FLAG_SYSTEM)).isNull();
}
@Test
- @Config(minSdk = P)
+ @Config(minSdk = N)
public void getWallpaperFile_flagSystem_previouslyCached_shouldReturnParcelFileDescriptor()
throws Exception {
manager.setBitmap(
@@ -303,13 +301,13 @@ public class ShadowWallpaperManagerTest {
}
@Test
- @Config(minSdk = P)
+ @Config(minSdk = N)
public void getWallpaperFile_flagLock_nothingCached_shouldReturnNull() throws Exception {
assertThat(manager.getWallpaperFile(WallpaperManager.FLAG_LOCK)).isNull();
}
@Test
- @Config(minSdk = P)
+ @Config(minSdk = N)
public void getWallpaperFile_flagLock_previouslyCached_shouldReturnParcelFileDescriptor()
throws Exception {
manager.setBitmap(
@@ -326,7 +324,7 @@ public class ShadowWallpaperManagerTest {
}
@Test
- @Config(minSdk = P)
+ @Config(minSdk = N)
public void getWallpaperFile_unsupportedFlag_shouldReturnNull() throws Exception {
assertThat(manager.getWallpaperFile(UNSUPPORTED_FLAG)).isNull();
}
@@ -366,61 +364,47 @@ public class ShadowWallpaperManagerTest {
@Test
@Config(minSdk = N)
public void setStream_flagSystem_shouldCacheInMemory() throws Exception {
- InputStream inputStream = null;
byte[] testImageBytes = getBytesFromBitmap(TEST_IMAGE_1);
- try {
- inputStream = new ByteArrayInputStream(testImageBytes);
- manager.setStream(
- inputStream,
- /* visibleCropHint= */ null,
- /* allowBackup= */ true,
- WallpaperManager.FLAG_SYSTEM);
+
+ manager.setStream(
+ new ByteArrayInputStream(testImageBytes),
+ /* visibleCropHint= */ null,
+ /* allowBackup= */ true,
+ WallpaperManager.FLAG_SYSTEM);
assertThat(getBytesFromBitmap(shadowOf(manager).getBitmap(WallpaperManager.FLAG_SYSTEM)))
.isEqualTo(testImageBytes);
assertThat(shadowOf(manager).getBitmap(WallpaperManager.FLAG_LOCK)).isNull();
- } finally {
- close(inputStream);
- }
}
@Test
@Config(minSdk = N)
public void setStream_flagLock_shouldCacheInMemory() throws Exception {
- InputStream inputStream = null;
byte[] testImageBytes = getBytesFromBitmap(TEST_IMAGE_2);
- try {
- inputStream = new ByteArrayInputStream(testImageBytes);
- manager.setStream(
- inputStream,
- /* visibleCropHint= */ null,
- /* allowBackup= */ true,
- WallpaperManager.FLAG_LOCK);
+ manager.setStream(
+ new ByteArrayInputStream(testImageBytes),
+ /* visibleCropHint= */ null,
+ /* allowBackup= */ true,
+ WallpaperManager.FLAG_LOCK);
assertThat(getBytesFromBitmap(shadowOf(manager).getBitmap(WallpaperManager.FLAG_LOCK)))
.isEqualTo(testImageBytes);
assertThat(shadowOf(manager).getBitmap(WallpaperManager.FLAG_SYSTEM)).isNull();
- } finally {
- close(inputStream);
- }
}
@Test
@Config(minSdk = N)
public void setStream_unsupportedFlag_shouldNotCacheInMemory() throws Exception {
- InputStream inputStream = null;
byte[] testImageBytes = getBytesFromBitmap(TEST_IMAGE_2);
- try {
- inputStream = new ByteArrayInputStream(testImageBytes);
- manager.setStream(
- inputStream, /* visibleCropHint= */ null, /* allowBackup= */ true, UNSUPPORTED_FLAG);
+ manager.setStream(
+ new ByteArrayInputStream(testImageBytes),
+ /* visibleCropHint= */ null,
+ /* allowBackup= */ true,
+ UNSUPPORTED_FLAG);
assertThat(shadowOf(manager).getBitmap(WallpaperManager.FLAG_LOCK)).isNull();
assertThat(shadowOf(manager).getBitmap(WallpaperManager.FLAG_SYSTEM)).isNull();
assertThat(shadowOf(manager).getBitmap(UNSUPPORTED_FLAG)).isNull();
- } finally {
- close(inputStream);
- }
}
@Test
@@ -465,7 +449,7 @@ public class ShadowWallpaperManagerTest {
assertThat(manager.getWallpaperInfo()).isNull();
}
- @Config(minSdk = P)
+ @Config(minSdk = N)
public void
getWallpaperInfo_staticWallpaperWasDefault_liveWallpaperSet_shouldRemoveCachedStaticWallpaper()
throws Exception {
@@ -541,39 +525,48 @@ public class ShadowWallpaperManagerTest {
.isEqualTo(1f);
}
+ @Test
+ @Config(minSdk = N)
+ public void setBitmap_bothLockAndHome() throws Exception {
+ int returnCode =
+ manager.setBitmap(
+ TEST_IMAGE_1,
+ /* visibleCropHint= */ null,
+ /* allowBackup= */ false,
+ WallpaperManager.FLAG_SYSTEM | WallpaperManager.FLAG_LOCK);
+
+ assertThat(returnCode).isEqualTo(1);
+ assertThat(shadowOf(manager).getBitmap(WallpaperManager.FLAG_SYSTEM)).isEqualTo(TEST_IMAGE_1);
+ assertThat(shadowOf(manager).getBitmap(WallpaperManager.FLAG_LOCK)).isEqualTo(TEST_IMAGE_1);
+ }
+
+ @Test
+ @Config(minSdk = N)
+ public void setStream_bothLockAndHome() throws Exception {
+ byte[] testImageBytes = getBytesFromBitmap(TEST_IMAGE_1);
+ manager.setStream(
+ new ByteArrayInputStream(testImageBytes),
+ /* visibleCropHint= */ null,
+ /* allowBackup= */ true,
+ WallpaperManager.FLAG_SYSTEM | WallpaperManager.FLAG_LOCK);
+
+ assertThat(getBytesFromBitmap(shadowOf(manager).getBitmap(WallpaperManager.FLAG_SYSTEM)))
+ .isEqualTo(testImageBytes);
+ assertThat(getBytesFromBitmap(shadowOf(manager).getBitmap(WallpaperManager.FLAG_LOCK)))
+ .isEqualTo(testImageBytes);
+ }
+
private static byte[] getBytesFromFileDescriptor(FileDescriptor fileDescriptor)
throws IOException {
- FileInputStream inputStream = null;
- ByteArrayOutputStream outputStream = null;
- try {
- inputStream = new FileInputStream(fileDescriptor);
- outputStream = new ByteArrayOutputStream();
- byte[] buffer = new byte[1024];
- int numOfBytes = 0;
- while ((numOfBytes = inputStream.read(buffer, 0, buffer.length)) != -1) {
- outputStream.write(buffer, 0, numOfBytes);
- }
+ InputStream inputStream = new FileInputStream(fileDescriptor);
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ ByteStreams.copy(inputStream, outputStream);
return outputStream.toByteArray();
- } finally {
- close(inputStream);
- close(outputStream);
- }
- }
-
- private static byte[] getBytesFromBitmap(Bitmap bitmap) throws IOException {
- ByteArrayOutputStream stream = null;
- try {
- stream = new ByteArrayOutputStream();
- bitmap.compress(Bitmap.CompressFormat.PNG, /* quality= */ 0, stream);
- return stream.toByteArray();
- } finally {
- close(stream);
- }
}
- private static void close(@Nullable Closeable closeable) throws IOException {
- if (closeable != null) {
- closeable.close();
- }
+ private static byte[] getBytesFromBitmap(Bitmap bitmap) {
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ bitmap.compress(Bitmap.CompressFormat.PNG, /* quality= */ 0, stream);
+ return stream.toByteArray();
}
}
diff --git a/robolectric/src/test/java/org/robolectric/shadows/ShadowWifiManagerTest.java b/robolectric/src/test/java/org/robolectric/shadows/ShadowWifiManagerTest.java
index 593327ab3..299a85333 100644
--- a/robolectric/src/test/java/org/robolectric/shadows/ShadowWifiManagerTest.java
+++ b/robolectric/src/test/java/org/robolectric/shadows/ShadowWifiManagerTest.java
@@ -675,6 +675,21 @@ public class ShadowWifiManagerTest {
}
@Test
+ @Config(minSdk = R)
+ public void testSetClearWifiConnectedNetworkScorer() {
+ // GIVEN
+ WifiManager.WifiConnectedNetworkScorer mockScorer =
+ mock(WifiManager.WifiConnectedNetworkScorer.class);
+ // WHEN
+ wifiManager.setWifiConnectedNetworkScorer(directExecutor(), mockScorer);
+ assertThat(shadowOf(wifiManager).isWifiConnectedNetworkScorerEnabled()).isTrue();
+ wifiManager.clearWifiConnectedNetworkScorer();
+
+ // THEN
+ assertThat(shadowOf(wifiManager).isWifiConnectedNetworkScorerEnabled()).isFalse();
+ }
+
+ @Test
@Config(minSdk = Q)
public void testGetUsabilityScores() {
// GIVEN
diff --git a/sandbox/Android.bp b/sandbox/Android.bp
new file mode 100644
index 000000000..8244722df
--- /dev/null
+++ b/sandbox/Android.bp
@@ -0,0 +1,69 @@
+//#############################################
+// Compile Robolectric sandbox
+//#############################################
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "external_robolectric_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["external_robolectric_license"],
+}
+
+java_library_host {
+ name: "Robolectric_sandbox_upstream",
+ srcs: ["src/main/java/**/*.java"],
+ libs: [
+ "Robolectric_annotations_upstream",
+ "Robolectric_shadowapi_upstream",
+ "Robolectric_utils_reflector_upstream",
+ "Robolectric_utils_upstream",
+ "asm-commons-9.2",
+ "guava",
+ "asm-tree-9.2",
+ "asm-9.2",
+ "jsr305",
+ ],
+ plugins: [
+ "auto_service_plugin",
+ "auto_value_plugin",
+ ],
+ openjdk9: {
+ javacflags: [
+ "--add-opens=java.base/java.lang=ALL-UNNAMED",
+ ],
+ },
+}
+
+//#############################################
+// Compile Robolectric sandbox tests
+//#############################################
+
+java_test_host {
+ name: "Robolectric_sandbox_tests_upstream",
+ srcs: ["src/test/java/**/*.java"],
+ static_libs: [
+ "Robolectric_annotations_upstream",
+ "Robolectric_shadowapi_upstream",
+ "Robolectric_sandbox_upstream",
+ "Robolectric_utils_reflector_upstream",
+ "Robolectric_utils_upstream",
+ "Robolectric_junit_upstream",
+ "mockito",
+ "hamcrest",
+ "asm-commons-9.2",
+ "guava",
+ "objenesis",
+ "asm-tree-9.2",
+ "junit",
+ "truth-prebuilt",
+ "asm-9.2",
+ "jsr305",
+ ],
+ plugins: [
+ "auto_service_plugin",
+ "auto_value_plugin",
+ ],
+ test_suites: ["general-tests"],
+}
diff --git a/sandbox/src/main/java/org/robolectric/config/AndroidConfigurer.java b/sandbox/src/main/java/org/robolectric/config/AndroidConfigurer.java
index 3a549a763..ce81f2a61 100644
--- a/sandbox/src/main/java/org/robolectric/config/AndroidConfigurer.java
+++ b/sandbox/src/main/java/org/robolectric/config/AndroidConfigurer.java
@@ -60,6 +60,7 @@ public class AndroidConfigurer {
.doNotAcquirePackage("jdk.internal.")
.doNotAcquirePackage("org.junit")
.doNotAcquirePackage("org.hamcrest")
+ .doNotAcquirePackage("org.objectweb.asm")
.doNotAcquirePackage("org.robolectric.annotation.")
.doNotAcquirePackage("org.robolectric.internal.")
.doNotAcquirePackage("org.robolectric.pluginapi.")
@@ -98,8 +99,7 @@ public class AndroidConfigurer {
}
// Instrumenting these classes causes a weird failure.
- builder.doNotInstrumentClass("android.R")
- .doNotInstrumentClass("android.R$styleable");
+ builder.doNotInstrumentClass("android.R").doNotInstrumentClass("android.R$styleable");
builder
.addInstrumentedPackage("dalvik.")
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 53872c13b..00e200941 100644
--- a/sandbox/src/main/java/org/robolectric/internal/bytecode/ClassInstrumentor.java
+++ b/sandbox/src/main/java/org/robolectric/internal/bytecode/ClassInstrumentor.java
@@ -49,7 +49,7 @@ public class ClassInstrumentor {
private static final Handle BOOTSTRAP_STATIC;
private static final Handle BOOTSTRAP_INTRINSIC;
private static final String ROBO_INIT_METHOD_NAME = "$$robo$init";
- static final Type OBJECT_TYPE = Type.getType(Object.class);
+ protected static final Type OBJECT_TYPE = Type.getType(Object.class);
private static final ShadowImpl SHADOW_IMPL = new ShadowImpl();
final Decorator decorator;
@@ -175,8 +175,6 @@ public class ClassInstrumentor {
// If there is no constructor, adds one
addNoArgsConstructor(mutableClass);
- addDirectCallConstructor(mutableClass);
-
addRoboInitMethod(mutableClass);
removeFinalFromFields(mutableClass);
@@ -236,20 +234,27 @@ public class ClassInstrumentor {
* Adds a call $$robo$init, which instantiates a shadow object if required. This is to support
* custom shadows for Jacoco-instrumented classes (except cnstructor shadows).
*/
- private void addCallToRoboInit(MutableClass mutableClass, MethodNode ctor) {
+ protected void addCallToRoboInit(MutableClass mutableClass, MethodNode ctor) {
AbstractInsnNode returnNode =
Iterables.find(
ctor.instructions,
- node -> node instanceof InsnNode && node.getOpcode() == Opcodes.RETURN,
+ node -> {
+ if (node.getOpcode() == Opcodes.INVOKESPECIAL) {
+ MethodInsnNode mNode = (MethodInsnNode) node;
+ return (mNode.owner.equals(mutableClass.internalClassName)
+ || mNode.owner.equals(mutableClass.classNode.superName));
+ }
+ return false;
+ },
null);
- ctor.instructions.insertBefore(returnNode, new VarInsnNode(Opcodes.ALOAD, 0));
- ctor.instructions.insertBefore(
+ ctor.instructions.insert(
returnNode,
new MethodInsnNode(
Opcodes.INVOKEVIRTUAL,
mutableClass.classType.getInternalName(),
ROBO_INIT_METHOD_NAME,
"()V"));
+ ctor.instructions.insert(returnNode, new VarInsnNode(Opcodes.ALOAD, 0));
}
private void instrumentMethods(MutableClass mutableClass) {
@@ -292,8 +297,6 @@ public class ClassInstrumentor {
}
}
- protected void addDirectCallConstructor(MutableClass mutableClass) {}
-
/**
* Generates code like this:
*
@@ -351,12 +354,24 @@ public class ClassInstrumentor {
}
/**
- * Constructors are instrumented as follows: TODO(slliu): Fill in constructor instrumentation
- * directions
+ * Constructors are instrumented as follows:
+ *
+ * <ul>
+ * <li>The original constructor will be stripped of its instructions leading up to, and
+ * including, the call to super() or this(). It is also renamed to $$robo$$__constructor__
+ * <li>A method called __constructor__ is created and its job is to call
+ * $$robo$$__constructor__. The __constructor__ method is what gets shadowed if a Shadow
+ * wants to shadow a constructor.
+ * <li>A new constructor is created and contains the stripped instructions of the original
+ * constructor leading up to, and including, the call to super() or this(). Then, it has a
+ * call to $$robo$init to initialize the Class' Shadow Object. Then, it uses invokedynamic
+ * to call __constructor__. Finally, it contains any instructions that might occur after the
+ * return statement in the original constructor.
+ * </ul>
*
* @param method the constructor to instrument
*/
- private void instrumentConstructor(MutableClass mutableClass, MethodNode method) {
+ protected void instrumentConstructor(MutableClass mutableClass, MethodNode method) {
makeMethodPrivate(method);
InsnList callSuper = extractCallToSuperConstructor(mutableClass, method);
@@ -488,7 +503,8 @@ public class ClassInstrumentor {
instrumentNativeMethod(mutableClass, method);
}
- // todo figure out
+ // Create delegator method with same name as original method. The delegator method will use
+ // invokedynamic to decide at runtime whether to call original method or shadowed method
String originalName = method.name;
method.name = directMethodName(mutableClass, originalName);
@@ -505,7 +521,6 @@ public class ClassInstrumentor {
generator.endMethod();
mutableClass.addMethod(delegatorMethodNode);
}
-
/**
* Creates native stub which returns the default return value.
*
@@ -715,6 +730,14 @@ public class ClassInstrumentor {
return Modifier.isStatic(m.access) ? Opcodes.H_INVOKESTATIC : Opcodes.H_INVOKESPECIAL;
}
+ // implemented in DirectClassInstrumentor
+ public void setAndroidJarSDKVersion(int androidJarSDKVersion) {}
+
+ // implemented in DirectClassInstrumentor
+ protected int getAndroidJarSDKVersion() {
+ return -1;
+ }
+
public interface Decorator {
void decorate(MutableClass mutableClass);
}
diff --git a/sandbox/src/main/java/org/robolectric/internal/bytecode/MutableClass.java b/sandbox/src/main/java/org/robolectric/internal/bytecode/MutableClass.java
index 305431f7a..63b4b2002 100644
--- a/sandbox/src/main/java/org/robolectric/internal/bytecode/MutableClass.java
+++ b/sandbox/src/main/java/org/robolectric/internal/bytecode/MutableClass.java
@@ -54,6 +54,10 @@ public class MutableClass {
return new ArrayList<>(classNode.methods);
}
+ public Type getClassType() {
+ return classType;
+ }
+
public void addMethod(MethodNode methodNode) {
classNode.methods.add(methodNode);
}
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 39cfd0199..cb77a1f74 100644
--- a/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowMap.java
+++ b/sandbox/src/main/java/org/robolectric/internal/bytecode/ShadowMap.java
@@ -14,15 +14,16 @@ import org.robolectric.annotation.Implements;
import org.robolectric.internal.ShadowProvider;
import org.robolectric.sandbox.ShadowMatcher;
import org.robolectric.shadow.api.ShadowPicker;
+import org.robolectric.util.Logger;
/**
* Maps from instrumented class to shadow class.
*
- * We deal with class names rather than actual classes here, since a ShadowMap is built outside of
- * any sandboxes, but instrumented and shadowed classes must be loaded through a
- * {@link SandboxClassLoader}. We don't want to try to resolve those classes outside of a sandbox.
+ * <p>We deal with class names rather than actual classes here, since a ShadowMap is built outside
+ * of any sandboxes, but instrumented and shadowed classes must be loaded through a {@link
+ * SandboxClassLoader}. We don't want to try to resolve those classes outside of a sandbox.
*
- * Once constructed, instances are immutable.
+ * <p>Once constructed, instances are immutable.
*/
@SuppressWarnings("NewApi")
public class ShadowMap {
@@ -39,7 +40,9 @@ public class ShadowMap {
final Map<String, String> shadowPickerMap = new HashMap<>();
// These are sorted in descending order (higher priority providers are first).
+ Logger.debug("Shadow providers: " + sortedProviders);
for (ShadowProvider provider : sortedProviders) {
+ Logger.debug("Shadow provider: " + provider.getClass().getName());
for (Map.Entry<String, String> entry : provider.getShadows()) {
shadowMap.put(entry.getKey(), entry.getValue());
}
@@ -66,6 +69,10 @@ public class ShadowMap {
this.shadowPickers = ImmutableMap.copyOf(shadowPickers);
}
+ public boolean hasShadowPicker(MutableClass mutableClass) {
+ return shadowPickers.containsKey(mutableClass.getName().replace('$', '.'));
+ }
+
public ShadowInfo getShadowInfo(Class<?> clazz, ShadowMatcher shadowMatcher) {
String instrumentedClassName = clazz.getName();
@@ -114,8 +121,8 @@ public class ShadowMap {
return pickShadow(instrumentedClassName, clazz, shadowPickerClassName);
}
- private ShadowInfo pickShadow(String instrumentedClassName, Class<?> clazz,
- String shadowPickerClassName) {
+ private ShadowInfo pickShadow(
+ String instrumentedClassName, Class<?> clazz, String shadowPickerClassName) {
ClassLoader sandboxClassLoader = clazz.getClassLoader();
try {
Class<? extends ShadowPicker<?>> shadowPickerClass =
@@ -128,16 +135,22 @@ public class ShadowMap {
ShadowInfo shadowInfo = obtainShadowInfo(selectedShadowClass);
if (!shadowInfo.shadowedClassName.equals(instrumentedClassName)) {
- throw new IllegalArgumentException("Implemented class for "
- + selectedShadowClass.getName() + " (" + shadowInfo.shadowedClassName + ") != "
- + instrumentedClassName);
+ throw new IllegalArgumentException(
+ "Implemented class for "
+ + selectedShadowClass.getName()
+ + " ("
+ + shadowInfo.shadowedClassName
+ + ") != "
+ + instrumentedClassName);
}
return shadowInfo;
- } catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException
- | IllegalAccessException | InstantiationException e) {
- throw new RuntimeException("Failed to resolve shadow picker for " + instrumentedClassName,
- e);
+ } catch (ClassNotFoundException
+ | NoSuchMethodException
+ | InvocationTargetException
+ | IllegalAccessException
+ | InstantiationException e) {
+ throw new RuntimeException("Failed to resolve shadow picker for " + instrumentedClassName, e);
}
}
@@ -224,7 +237,7 @@ public class ShadowMap {
private final Map<String, ShadowInfo> overriddenShadows;
private final Map<String, String> shadowPickers;
- public Builder () {
+ public Builder() {
defaultShadows = ImmutableListMultimap.of();
overriddenShadows = new HashMap<>();
shadowPickers = new HashMap<>();
@@ -262,8 +275,8 @@ public class ShadowMap {
private void addShadowInfo(ShadowInfo shadowInfo) {
overriddenShadows.put(shadowInfo.shadowedClassName, shadowInfo);
if (shadowInfo.hasShadowPicker()) {
- shadowPickers
- .put(shadowInfo.shadowedClassName, shadowInfo.getShadowPickerClass().getName());
+ shadowPickers.put(
+ shadowInfo.shadowedClassName, shadowInfo.getShadowPickerClass().getName());
}
}
diff --git a/shadowapi/Android.bp b/shadowapi/Android.bp
new file mode 100644
index 000000000..0ab69a230
--- /dev/null
+++ b/shadowapi/Android.bp
@@ -0,0 +1,45 @@
+//#############################################
+// Compile Robolectric shadowapi
+//#############################################
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "external_robolectric_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["external_robolectric_license"],
+}
+
+java_library_host {
+ name: "Robolectric_shadowapi_upstream",
+ libs: [
+ "jsr305",
+ ],
+ static_libs: [
+ "Robolectric_utils_upstream",
+ "Robolectric_annotations_upstream",
+ ],
+ srcs: ["src/main/java/**/*.java"],
+ openjdk9: {
+ javacflags: [
+ "--add-opens=java.base/java.lang=ALL-UNNAMED",
+ ],
+ },
+}
+
+//#############################################
+// Compile Robolectric shadowapi tests
+//#############################################
+java_test_host {
+ name: "Robolectric_shadowapi_tests_upstream",
+ srcs: ["src/test/java/**/*.java"],
+ static_libs: [
+ "Robolectric_shadowapi_upstream",
+ "hamcrest",
+ "guava",
+ "junit",
+ "truth-prebuilt",
+ ],
+ test_suites: ["general-tests"],
+}
diff --git a/shadows/framework/Android.bp b/shadows/framework/Android.bp
new file mode 100644
index 000000000..04ba8ff71
--- /dev/null
+++ b/shadows/framework/Android.bp
@@ -0,0 +1,68 @@
+//#############################################
+// Compile Robolectric shadows framework
+//#############################################
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "external_robolectric_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["external_robolectric_license"],
+}
+
+java_library_host {
+ name: "Robolectric_shadows_framework_upstream",
+ srcs: [
+ "src/main/java/**/*.java",
+ "src/main/java/**/*.kt",
+ ],
+ java_resource_dirs: ["src/main/resources"],
+ javacflags: [
+ "-Aorg.robolectric.annotation.processing.shadowPackage=org.robolectric",
+ "-Aorg.robolectric.annotation.processing.sdkCheckMode=ERROR",
+ // Uncomment the below to debug annotation processors not firing.
+ //"-verbose",
+ //"-XprintRounds",
+ //"-XprintProcessorInfo",
+ //"-Xlint",
+ //"-J-verbose",
+ ],
+ libs: [
+ "Robolectric_annotations_upstream",
+ "Robolectric_nativeruntime_upstream",
+ "Robolectric_resources_upstream",
+ "Robolectric_pluginapi_upstream",
+ "Robolectric_sandbox_upstream",
+ "Robolectric_shadowapi_upstream",
+ "Robolectric_utils_upstream",
+ "Robolectric_utils_reflector_upstream",
+ "auto_value_annotations",
+ //jetpack
+ //"androidx.annotation_annotation-nodeps",
+ "jsr305",
+ "icu4j",
+
+ "robolectric-accessibility-test-framework-2.1",
+ "robolectric-javax.annotation-api-1.2",
+ //"hamcrest-library",
+ //"hamcrest",
+ //"stub-annotations",
+ "robolectric-sqlite4java-0.282",
+ "asm-commons-9.2",
+ "guava",
+ "asm-tree-9.2",
+ "asm-9.2",
+ //standard tools
+ "error_prone_annotations",
+ //"grpc-java-netty-shaded",
+ // aar files that make up android and jetpack
+ "robolectric-host-android_all_upstream",
+ ],
+ plugins: [
+ "auto_value_plugin_1.9",
+ "auto_value_builder_plugin_1.9",
+ "Robolectric_processor_upstream",
+ ],
+
+}
diff --git a/shadows/framework/build.gradle b/shadows/framework/build.gradle
index 21160b61a..cd95bb106 100644
--- a/shadows/framework/build.gradle
+++ b/shadows/framework/build.gradle
@@ -55,7 +55,7 @@ dependencies {
compileOnly(AndroidSdk.MAX_SDK.coordinates) { force = true }
api "com.ibm.icu:icu4j:70.1"
api "androidx.annotation:annotation:1.1.0"
- api "com.google.auto.value:auto-value-annotations:1.9"
+ api "com.google.auto.value:auto-value-annotations:1.10"
annotationProcessor "com.google.auto.value:auto-value:1.9"
sqlite4java "com.almworks.sqlite4java:libsqlite4java-osx:$sqlite4javaVersion"
diff --git a/shadows/framework/src/main/java/android/media/Session2Token.java b/shadows/framework/src/main/java/android/media/Session2Token.java
deleted file mode 100644
index 4a321e7b2..000000000
--- a/shadows/framework/src/main/java/android/media/Session2Token.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package android.media;
-
-/**
- * Temporary replacement for class missing in Android Q Preview 1.
- *
- * TODO: Remove for Q Preview 2.
- */
-public class Session2Token {
-
-}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/GraphicsShadowPicker.java b/shadows/framework/src/main/java/org/robolectric/shadows/GraphicsShadowPicker.java
new file mode 100644
index 000000000..8916375dc
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/GraphicsShadowPicker.java
@@ -0,0 +1,32 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.GraphicsMode;
+import org.robolectric.annotation.GraphicsMode.Mode;
+import org.robolectric.config.ConfigurationRegistry;
+import org.robolectric.shadow.api.ShadowPicker;
+
+/** A {@link ShadowPicker} that selects between shadows given the Graphics mode. */
+public class GraphicsShadowPicker<T> implements ShadowPicker<T> {
+
+ private final Class<? extends T> legacyShadowClass;
+ private final Class<? extends T> nativeShadowClass;
+
+ public GraphicsShadowPicker(
+ Class<? extends T> legacyShadowClass, Class<? extends T> nativeShadowClass) {
+ this.legacyShadowClass = legacyShadowClass;
+ this.nativeShadowClass = nativeShadowClass;
+ }
+
+ @Override
+ public Class<? extends T> pickShadowClass() {
+ if (RuntimeEnvironment.getApiLevel() >= O
+ && ConfigurationRegistry.get(GraphicsMode.Mode.class) == Mode.NATIVE) {
+ return nativeShadowClass;
+ } else {
+ return legacyShadowClass;
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ImageUtil.java b/shadows/framework/src/main/java/org/robolectric/shadows/ImageUtil.java
index 75b495744..c9a723ca2 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ImageUtil.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ImageUtil.java
@@ -26,7 +26,6 @@ import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
-import org.robolectric.Shadows;
import org.robolectric.shadow.api.Shadow;
public class ImageUtil {
@@ -117,7 +116,7 @@ public class ImageUtil {
if (srcWidth <= 0 || srcHeight <= 0 || dstWidth <= 0 || dstHeight <= 0) {
return false;
}
- BufferedImage before = ((ShadowBitmap) Shadow.extract(src)).getBufferedImage();
+ BufferedImage before = ((ShadowLegacyBitmap) Shadow.extract(src)).getBufferedImage();
if (before == null || before.getColorModel() == null) {
return false;
}
@@ -129,7 +128,7 @@ public class ImageUtil {
filter ? VALUE_INTERPOLATION_BILINEAR : VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
graphics2D.drawImage(before, 0, 0, dstWidth, dstHeight, 0, 0, srcWidth, srcHeight, null);
graphics2D.dispose();
- ((ShadowBitmap) Shadow.extract(dst)).setBufferedImage(after);
+ ((ShadowLegacyBitmap) Shadow.extract(dst)).setBufferedImage(after);
return true;
}
@@ -156,7 +155,8 @@ public class ImageUtil {
int width = realBitmap.getWidth();
int height = realBitmap.getHeight();
boolean needAlphaChannel = needAlphaChannel(format);
- BufferedImage bufferedImage = Shadows.shadowOf(realBitmap).getBufferedImage();
+ BufferedImage bufferedImage =
+ ((ShadowLegacyBitmap) Shadow.extract(realBitmap)).getBufferedImage();
if (bufferedImage == null) {
bufferedImage =
new BufferedImage(
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ResponderLocationBuilder.java b/shadows/framework/src/main/java/org/robolectric/shadows/ResponderLocationBuilder.java
new file mode 100644
index 000000000..794e75d31
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ResponderLocationBuilder.java
@@ -0,0 +1,211 @@
+package org.robolectric.shadows;
+
+import static org.robolectric.util.reflector.Reflector.reflector;
+
+import android.net.wifi.rtt.ResponderLocation;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.util.reflector.Accessor;
+import org.robolectric.util.reflector.ForType;
+
+/** Builder for {@link ResponderLocation} */
+@SuppressWarnings("CanIgnoreReturnValueSuggester")
+public class ResponderLocationBuilder {
+ // LCI Subelement LCI state
+ private Double altitude;
+ private Double altitudeUncertainty;
+ private Integer altitudeType;
+ private Double latitudeDegrees;
+ private Double latitudeUncertainty;
+ private Double longitudeDegrees;
+ private Double longitudeUncertainty;
+ private Integer datum;
+ private Integer lciVersion;
+ private Boolean lciRegisteredLocationAgreement;
+
+ // LCI Subelement Z state
+ private Double heightAboveFloorMeters;
+ private Double heightAboveFloorUncertaintyMeters;
+ private Integer expectedToMove;
+ private Double floorNumber;
+
+ private ResponderLocationBuilder() {}
+
+ public static ResponderLocationBuilder newBuilder() {
+ return new ResponderLocationBuilder();
+ }
+
+ public ResponderLocationBuilder setAltitude(double altitude) {
+ this.altitude = altitude;
+ return this;
+ }
+
+ public ResponderLocationBuilder setAltitudeUncertainty(double altitudeUncertainty) {
+ this.altitudeUncertainty = altitudeUncertainty;
+ return this;
+ }
+
+ public ResponderLocationBuilder setAltitudeType(int altitudeType) {
+ this.altitudeType = altitudeType;
+ return this;
+ }
+
+ public ResponderLocationBuilder setLatitude(double latitudeDegrees) {
+ this.latitudeDegrees = latitudeDegrees;
+ return this;
+ }
+
+ public ResponderLocationBuilder setLatitudeUncertainty(double latitudeUncertainty) {
+ this.latitudeUncertainty = latitudeUncertainty;
+ return this;
+ }
+
+ public ResponderLocationBuilder setLongitude(double longitudeDegrees) {
+ this.longitudeDegrees = longitudeDegrees;
+ return this;
+ }
+
+ public ResponderLocationBuilder setLongitudeUncertainty(double longitudeUncertainty) {
+ this.longitudeUncertainty = longitudeUncertainty;
+ return this;
+ }
+
+ public ResponderLocationBuilder setDatum(int datum) {
+ this.datum = datum;
+ return this;
+ }
+
+ public ResponderLocationBuilder setLciVersion(int lciVersion) {
+ this.lciVersion = lciVersion;
+ return this;
+ }
+
+ public ResponderLocationBuilder setLciRegisteredLocationAgreement(
+ Boolean lciRegisteredLocationAgreement) {
+ this.lciRegisteredLocationAgreement = lciRegisteredLocationAgreement;
+ return this;
+ }
+
+ public ResponderLocationBuilder setHeightAboveFloorMeters(double heightAboveFloorMeters) {
+ this.heightAboveFloorMeters = heightAboveFloorMeters;
+ return this;
+ }
+
+ public ResponderLocationBuilder setHeightAboveFloorUncertaintyMeters(
+ double heightAboveFloorUncertaintyMeters) {
+ this.heightAboveFloorUncertaintyMeters = heightAboveFloorUncertaintyMeters;
+ return this;
+ }
+
+ public ResponderLocationBuilder setExpectedToMove(int expectedToMove) {
+ this.expectedToMove = expectedToMove;
+ return this;
+ }
+
+ public ResponderLocationBuilder setFloorNumber(double floorNumber) {
+ this.floorNumber = floorNumber;
+ return this;
+ }
+
+ public ResponderLocation build() {
+ ResponderLocation result = Shadow.newInstanceOf(ResponderLocation.class);
+
+ ResponderLocationReflector locationResponderReflector =
+ reflector(ResponderLocationReflector.class, result);
+
+ locationResponderReflector.setAltitude(this.altitude == null ? 0 : this.altitude);
+ locationResponderReflector.setAltitudeType(this.altitudeType == null ? 0 : this.altitudeType);
+ locationResponderReflector.setAltitudeUncertainty(
+ this.altitudeUncertainty == null ? 0 : this.altitudeUncertainty);
+ locationResponderReflector.setLatitude(this.latitudeDegrees == null ? 0 : this.latitudeDegrees);
+ locationResponderReflector.setLatitudeUncertainty(
+ this.latitudeUncertainty == null ? 0 : this.latitudeUncertainty);
+ locationResponderReflector.setLongitude(
+ this.longitudeDegrees == null ? 0 : this.longitudeDegrees);
+ locationResponderReflector.setLongitudeUncertainty(
+ this.longitudeUncertainty == null ? 0 : this.longitudeUncertainty);
+ locationResponderReflector.setDatum(this.datum == null ? 0 : this.datum);
+ locationResponderReflector.setLciVersion(this.lciVersion == null ? 0 : this.lciVersion);
+ locationResponderReflector.setLciRegisteredLocationAgreement(
+ this.lciRegisteredLocationAgreement != null && this.lciRegisteredLocationAgreement);
+ locationResponderReflector.setHeightAboveFloorMeters(
+ this.heightAboveFloorMeters == null ? 0 : this.heightAboveFloorMeters);
+ locationResponderReflector.setHeightAboveFloorUncertaintyMeters(
+ this.heightAboveFloorUncertaintyMeters == null
+ ? 0
+ : this.heightAboveFloorUncertaintyMeters);
+ locationResponderReflector.setExpectedToMove(
+ this.expectedToMove == null ? 0 : this.expectedToMove);
+ locationResponderReflector.setFloorNumber(this.floorNumber == null ? 0 : this.floorNumber);
+
+ locationResponderReflector.setIsLciValid(
+ this.altitude != null
+ && this.latitudeDegrees != null
+ && this.latitudeUncertainty != null
+ && this.longitudeDegrees != null
+ && this.longitudeUncertainty != null
+ && this.datum != null
+ && this.lciVersion != null
+ && this.lciRegisteredLocationAgreement != null
+ && this.altitudeType != null);
+
+ locationResponderReflector.setIsZValid(
+ this.heightAboveFloorMeters != null
+ && this.floorNumber != null
+ && this.expectedToMove != null
+ && this.heightAboveFloorUncertaintyMeters != null);
+
+ return result;
+ }
+
+ @ForType(ResponderLocation.class)
+ interface ResponderLocationReflector {
+
+ @Accessor("mAltitude")
+ void setAltitude(double altitude);
+
+ @Accessor("mAltitudeUncertainty")
+ void setAltitudeUncertainty(double altitudeUncertainty);
+
+ @Accessor("mAltitudeType")
+ void setAltitudeType(int altitudeType);
+
+ @Accessor("mLatitude")
+ void setLatitude(double latitudeDegrees);
+
+ @Accessor("mLatitudeUncertainty")
+ void setLatitudeUncertainty(double latitudeUncertainty);
+
+ @Accessor("mLongitude")
+ void setLongitude(double longitudeDegrees);
+
+ @Accessor("mLongitudeUncertainty")
+ void setLongitudeUncertainty(double longitudeUncertainty);
+
+ @Accessor("mDatum")
+ void setDatum(int datum);
+
+ @Accessor("mLciVersion")
+ void setLciVersion(int lciVersion);
+
+ @Accessor("mLciRegisteredLocationAgreement")
+ void setLciRegisteredLocationAgreement(boolean lciRegisteredLocationAgreement);
+
+ @Accessor("mHeightAboveFloorMeters")
+ void setHeightAboveFloorMeters(double heightAboveFloorMeters);
+
+ @Accessor("mHeightAboveFloorUncertaintyMeters")
+ void setHeightAboveFloorUncertaintyMeters(double heightAboveFloorUncertaintyMeters);
+
+ @Accessor("mExpectedToMove")
+ void setExpectedToMove(int expectedToMove);
+
+ @Accessor("mFloorNumber")
+ void setFloorNumber(double floorNumber);
+
+ @Accessor("mIsLciValid")
+ void setIsLciValid(boolean isLciValid);
+
+ @Accessor("mIsZValid")
+ void setIsZValid(boolean isZValid);
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAbstractCursor.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAbstractCursor.java
deleted file mode 100644
index 24e384571..000000000
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAbstractCursor.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package org.robolectric.shadows;
-
-import android.database.AbstractCursor;
-import android.net.Uri;
-import org.robolectric.annotation.Implements;
-import org.robolectric.annotation.RealObject;
-import org.robolectric.util.ReflectionHelpers;
-
-@Implements(AbstractCursor.class)
-public class ShadowAbstractCursor {
-
- @RealObject
- private AbstractCursor realAbstractCursor;
-
- /**
- * Returns the Uri set by {@code setNotificationUri()}.
- *
- * @return Notification URI.
- */
- public Uri getNotificationUri_Compatibility() {
- return ReflectionHelpers.getField(realAbstractCursor, "mNotifyUri");
- }
-} \ No newline at end of file
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 bd9f509c1..a27d01c98 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityNodeInfo.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAccessibilityNodeInfo.java
@@ -416,7 +416,7 @@ public class ShadowAccessibilityNodeInfo {
if (this.traversalBefore != null) {
this.traversalBefore.recycle();
}
-
+
this.traversalBefore = obtain(info);
}
@@ -627,6 +627,7 @@ public class ShadowAccessibilityNodeInfo {
}
if (getApiLevel() >= P) {
newInfo.setTooltipText(realAccessibilityNodeInfo.getTooltipText());
+ newInfo.setPaneTitle(realAccessibilityNodeInfo.getPaneTitle());
}
return newInfo;
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 b43631c11..ea551d8c1 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAlarmManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAlarmManager.java
@@ -1,40 +1,53 @@
package org.robolectric.shadows;
import static android.app.AlarmManager.RTC_WAKEUP;
-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;
-import static android.os.Build.VERSION_CODES.S;
import static org.robolectric.util.reflector.Reflector.reflector;
-import android.annotation.TargetApi;
import android.app.AlarmManager;
import android.app.AlarmManager.AlarmClockInfo;
import android.app.AlarmManager.OnAlarmListener;
import android.app.PendingIntent;
-import android.content.Intent;
+import android.app.PendingIntent.CanceledException;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
import android.os.Handler;
-import java.util.Collections;
+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.google.common.collect.Iterables;
+import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
+import java.util.PriorityQueue;
import java.util.TimeZone;
-import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
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.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;
-@SuppressWarnings({"UnusedDeclaration"})
+/** Shadow for {@link android.app.AlarmManager}. */
@Implements(AlarmManager.class)
public class ShadowAlarmManager {
+ public static final long WINDOW_EXACT = 0;
+ public static final long WINDOW_HEURISTIC = -1;
+
private static final TimeZone DEFAULT_TIMEZONE = TimeZone.getDefault();
private static boolean canScheduleExactAlarms;
- private final List<ScheduledAlarm> scheduledAlarms = new CopyOnWriteArrayList<>();
+ private static boolean autoSchedule;
+
+ private final Handler schedulingHandler = new Handler(Looper.getMainLooper());
+
+ @GuardedBy("scheduledAlarms")
+ private final PriorityQueue<InternalScheduledAlarm> scheduledAlarms = new PriorityQueue<>();
@RealObject private AlarmManager realObject;
@@ -42,267 +55,605 @@ public class ShadowAlarmManager {
public static void reset() {
TimeZone.setDefault(DEFAULT_TIMEZONE);
canScheduleExactAlarms = false;
+ autoSchedule = false;
}
- @Implementation
- protected void setTimeZone(String timeZone) {
- // Do the real check first
- reflector(AlarmManagerReflector.class, realObject).setTimeZone(timeZone);
- // Then do the right side effect
- TimeZone.setDefault(TimeZone.getTimeZone(timeZone));
+ /**
+ * When set to true, automatically schedules alarms to fire at the appropriate time (with respect
+ * to the main Looper time) when they are set. This means that a test as below could be expected
+ * to pass:
+ *
+ * <pre>{@code
+ * shadowOf(alarmManager).setAutoSchedule(true);
+ * AlarmManager.OnAlarmListener listener = mock(AlarmManager.OnAlarmListener.class);
+ * alarmManager.setExact(
+ * ELAPSED_REALTIME_WAKEUP,
+ * SystemClock.elapsedRealtime() + 10,
+ * "tag",
+ * listener,
+ * new Handler(Looper.getMainLooper()));
+ * shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(10));
+ * verify(listener).onAlarm();
+ * }</pre>
+ *
+ * <p>Alarms are always scheduled with respect to the trigger/window start time - there is no
+ * emulation of alarms being reordered, rescheduled, or delayed, as might happen on a real device.
+ * If emulating this is necessary, see {@link #fireAlarm(ScheduledAlarm)}.
+ *
+ * <p>{@link AlarmManager.OnAlarmListener} alarms will be run on the correct Handler/Executor as
+ * specified when the alarm is set.
+ */
+ public static void setAutoSchedule(boolean autoSchedule) {
+ ShadowAlarmManager.autoSchedule = autoSchedule;
}
@Implementation
- protected void set(int type, long triggerAtTime, PendingIntent operation) {
- internalSet(type, triggerAtTime, 0L, operation, null);
+ protected void set(int type, long triggerAtMs, PendingIntent operation) {
+ setImpl(type, triggerAtMs, WINDOW_HEURISTIC, 0L, operation, null, null, false);
}
- @Implementation(minSdk = N)
+ @Implementation(minSdk = VERSION_CODES.N)
protected void set(
- int type, long triggerAtTime, String tag, OnAlarmListener listener, Handler targetHandler) {
- internalSet(type, triggerAtTime, listener, targetHandler);
+ int type,
+ long triggerAtMs,
+ @Nullable String tag,
+ OnAlarmListener listener,
+ @Nullable Handler handler) {
+ setImpl(
+ type,
+ triggerAtMs,
+ WINDOW_HEURISTIC,
+ 0L,
+ tag,
+ listener,
+ new HandlerExecutor(handler),
+ null,
+ false);
}
- @Implementation(minSdk = KITKAT)
- protected void setExact(int type, long triggerAtTime, PendingIntent operation) {
- internalSet(type, triggerAtTime, 0L, operation, null);
+ @Implementation
+ protected void setRepeating(
+ int type, long triggerAtMs, long intervalMs, PendingIntent operation) {
+ setImpl(type, triggerAtMs, WINDOW_HEURISTIC, intervalMs, operation, null, null, false);
}
- @Implementation(minSdk = N)
- protected void setExact(
- int type, long triggerAtTime, String tag, OnAlarmListener listener, Handler targetHandler) {
- internalSet(type, triggerAtTime, listener, targetHandler);
+ @Implementation(minSdk = VERSION_CODES.KITKAT)
+ protected void setWindow(
+ int type, long windowStartMs, long windowLengthMs, PendingIntent operation) {
+ setImpl(type, windowStartMs, windowLengthMs, 0L, operation, null, null, false);
}
- @Implementation(minSdk = KITKAT)
+ @Implementation(minSdk = VERSION_CODES.N)
protected void setWindow(
- int type, long windowStartMillis, long windowLengthMillis, PendingIntent operation) {
- internalSet(type, windowStartMillis, 0L, operation, null);
+ int type,
+ long windowStartMs,
+ long windowLengthMs,
+ @Nullable String tag,
+ OnAlarmListener listener,
+ @Nullable Handler handler) {
+ setImpl(
+ type,
+ windowStartMs,
+ windowLengthMs,
+ 0L,
+ tag,
+ listener,
+ new HandlerExecutor(handler),
+ null,
+ false);
+ }
+
+ @Implementation(minSdk = 34)
+ protected void setWindow(
+ int type,
+ long windowStartMs,
+ long windowLengthMs,
+ @Nullable String tag,
+ Executor executor,
+ OnAlarmListener listener) {
+ setImpl(type, windowStartMs, windowLengthMs, 0L, tag, listener, executor, null, false);
}
- @Implementation(minSdk = N)
+ @Implementation(minSdk = 34)
protected void setWindow(
int type,
- long windowStartMillis,
- long windowLengthMillis,
- String tag,
+ long windowStartMs,
+ long windowLengthMs,
+ @Nullable String tag,
+ Executor executor,
+ WorkSource workSource,
+ OnAlarmListener listener) {
+ setImpl(type, windowStartMs, windowLengthMs, 0L, tag, listener, executor, workSource, false);
+ }
+
+ @Implementation(minSdk = VERSION_CODES.S)
+ protected void setPrioritized(
+ int type,
+ long windowStartMs,
+ long windowLengthMs,
+ @Nullable String tag,
+ Executor executor,
+ OnAlarmListener listener) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(listener);
+ setImpl(type, windowStartMs, windowLengthMs, 0L, tag, listener, executor, null, true);
+ }
+
+ @Implementation(minSdk = VERSION_CODES.KITKAT)
+ protected void setExact(int type, long triggerAtMs, PendingIntent operation) {
+ setImpl(type, triggerAtMs, WINDOW_EXACT, 0L, operation, null, null, false);
+ }
+
+ @Implementation(minSdk = VERSION_CODES.N)
+ protected void setExact(
+ int type,
+ long triggerAtTime,
+ @Nullable String tag,
OnAlarmListener listener,
- Handler targetHandler) {
- internalSet(type, windowStartMillis, listener, targetHandler);
+ @Nullable Handler targetHandler) {
+ setImpl(
+ type,
+ triggerAtTime,
+ WINDOW_EXACT,
+ 0L,
+ tag,
+ listener,
+ new HandlerExecutor(targetHandler),
+ null,
+ false);
+ }
+
+ @RequiresApi(VERSION_CODES.LOLLIPOP)
+ @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+ protected void setAlarmClock(AlarmClockInfo info, PendingIntent operation) {
+ setImpl(RTC_WAKEUP, info.getTriggerTime(), WINDOW_EXACT, 0L, operation, null, info, true);
}
- @Implementation(minSdk = M)
- protected void setAndAllowWhileIdle(int type, long triggerAtTime, PendingIntent operation) {
- internalSet(type, triggerAtTime, 0L, operation, null, true);
+ @Implementation(minSdk = VERSION_CODES.KITKAT)
+ protected void set(
+ int type,
+ long triggerAtMs,
+ long windowLengthMs,
+ long intervalMs,
+ PendingIntent operation,
+ @Nullable WorkSource workSource) {
+ setImpl(type, triggerAtMs, windowLengthMs, intervalMs, operation, workSource, null, false);
}
- @Implementation(minSdk = M)
- protected void setExactAndAllowWhileIdle(int type, long triggerAtTime, PendingIntent operation) {
- internalSet(type, triggerAtTime, 0L, operation, null, true);
+ @Implementation(minSdk = VERSION_CODES.N)
+ protected void set(
+ int type,
+ long triggerAtMs,
+ long windowLengthMs,
+ long intervalMs,
+ @Nullable String tag,
+ OnAlarmListener listener,
+ @Nullable Handler targetHandler,
+ @Nullable WorkSource workSource) {
+ setImpl(
+ type,
+ triggerAtMs,
+ windowLengthMs,
+ intervalMs,
+ tag,
+ listener,
+ new HandlerExecutor(targetHandler),
+ workSource,
+ false);
}
- @Implementation
- protected void setRepeating(
- int type, long triggerAtTime, long interval, PendingIntent operation) {
- internalSet(type, triggerAtTime, interval, operation, null);
+ @Implementation(minSdk = VERSION_CODES.N)
+ protected void set(
+ int type,
+ long triggerAtMs,
+ long windowLengthMs,
+ long intervalMs,
+ OnAlarmListener listener,
+ @Nullable Handler targetHandler,
+ @Nullable WorkSource workSource) {
+ setImpl(
+ type,
+ triggerAtMs,
+ windowLengthMs,
+ intervalMs,
+ null,
+ listener,
+ new HandlerExecutor(targetHandler),
+ workSource,
+ false);
+ }
+
+ @Implementation(minSdk = VERSION_CODES.S)
+ protected void setExact(
+ int type,
+ long triggerAtMs,
+ @Nullable String tag,
+ Executor executor,
+ WorkSource workSource,
+ OnAlarmListener listener) {
+ Objects.requireNonNull(workSource);
+ setImpl(type, triggerAtMs, WINDOW_EXACT, 0L, tag, listener, executor, workSource, false);
}
@Implementation
protected void setInexactRepeating(
- int type, long triggerAtMillis, long intervalMillis, PendingIntent operation) {
- internalSet(type, triggerAtMillis, intervalMillis, operation, null);
+ int type, long triggerAtMs, long intervalMillis, PendingIntent operation) {
+ setImpl(type, triggerAtMs, WINDOW_HEURISTIC, intervalMillis, operation, null, null, false);
}
- @Implementation(minSdk = LOLLIPOP)
- protected void setAlarmClock(AlarmClockInfo info, PendingIntent operation) {
- internalSet(RTC_WAKEUP, info.getTriggerTime(), 0L, operation, info.getShowIntent());
+ @Implementation(minSdk = VERSION_CODES.M)
+ protected void setAndAllowWhileIdle(int type, long triggerAtMs, PendingIntent operation) {
+ setImpl(type, triggerAtMs, WINDOW_HEURISTIC, 0L, operation, null, null, true);
}
- @Implementation(minSdk = LOLLIPOP)
- protected AlarmClockInfo getNextAlarmClock() {
- for (ScheduledAlarm scheduledAlarm : scheduledAlarms) {
- AlarmClockInfo alarmClockInfo = scheduledAlarm.getAlarmClockInfo();
- if (alarmClockInfo != null) {
- return alarmClockInfo;
+ @Implementation(minSdk = VERSION_CODES.M)
+ protected void setExactAndAllowWhileIdle(int type, long triggerAtMs, PendingIntent operation) {
+ setImpl(type, triggerAtMs, WINDOW_EXACT, 0L, operation, null, null, true);
+ }
+
+ @Implementation(minSdk = 34)
+ protected void setExactAndAllowWhileIdle(
+ int type,
+ long triggerAtMs,
+ @Nullable String tag,
+ Executor executor,
+ @Nullable WorkSource workSource,
+ OnAlarmListener listener) {
+ setImpl(type, triggerAtMs, WINDOW_EXACT, 0L, tag, listener, executor, workSource, true);
+ }
+
+ @Implementation
+ protected void cancel(PendingIntent operation) {
+ synchronized (scheduledAlarms) {
+ Iterables.removeIf(
+ scheduledAlarms,
+ alarm -> {
+ if (operation.equals(alarm.operation)) {
+ alarm.deschedule();
+ return true;
+ }
+ return false;
+ });
+ }
+ }
+
+ @Implementation(minSdk = VERSION_CODES.N)
+ protected void cancel(OnAlarmListener listener) {
+ synchronized (scheduledAlarms) {
+ Iterables.removeIf(
+ scheduledAlarms,
+ alarm -> {
+ if (listener.equals(alarm.onAlarmListener)) {
+ alarm.deschedule();
+ return true;
+ }
+ return false;
+ });
+ }
+ }
+
+ @Implementation(minSdk = 34)
+ protected void cancelAll() {
+ synchronized (scheduledAlarms) {
+ for (InternalScheduledAlarm alarm : scheduledAlarms) {
+ alarm.deschedule();
}
+ scheduledAlarms.clear();
}
- return null;
}
- private void internalSet(
- int type,
- long triggerAtTime,
- long interval,
- PendingIntent operation,
- PendingIntent showIntent) {
- cancel(operation);
+ @Implementation
+ protected void setTimeZone(String timeZone) {
+ // Do the real check first
+ reflector(AlarmManagerReflector.class, realObject).setTimeZone(timeZone);
+ // Then do the right side effect
+ TimeZone.setDefault(TimeZone.getTimeZone(timeZone));
+ }
+
+ @Implementation(minSdk = VERSION_CODES.S)
+ protected boolean canScheduleExactAlarms() {
+ return canScheduleExactAlarms;
+ }
+
+ @RequiresApi(VERSION_CODES.LOLLIPOP)
+ @Implementation(minSdk = VERSION_CODES.LOLLIPOP)
+ @Nullable
+ protected AlarmClockInfo getNextAlarmClock() {
synchronized (scheduledAlarms) {
- scheduledAlarms.add(new ScheduledAlarm(type, triggerAtTime, interval, operation, showIntent));
- Collections.sort(scheduledAlarms);
+ for (ScheduledAlarm scheduledAlarm : scheduledAlarms) {
+ AlarmClockInfo alarmClockInfo = scheduledAlarm.getAlarmClockInfo();
+ if (alarmClockInfo != null) {
+ return alarmClockInfo;
+ }
+ }
+ return null;
}
}
- private void internalSet(
+ private void setImpl(
int type,
- long triggerAtTime,
- long interval,
+ long triggerAtMs,
+ long windowLengthMs,
+ long intervalMs,
PendingIntent operation,
- PendingIntent showIntent,
+ @Nullable WorkSource workSource,
+ @Nullable Object alarmClockInfo,
boolean allowWhileIdle) {
- cancel(operation);
synchronized (scheduledAlarms) {
+ cancel(operation);
scheduledAlarms.add(
- new ScheduledAlarm(type, triggerAtTime, interval, operation, showIntent, allowWhileIdle));
- Collections.sort(scheduledAlarms);
+ new InternalScheduledAlarm(
+ type,
+ triggerAtMs,
+ windowLengthMs,
+ intervalMs,
+ operation,
+ workSource,
+ alarmClockInfo,
+ allowWhileIdle)
+ .schedule());
}
}
- private void internalSet(
- int type, long triggerAtTime, OnAlarmListener listener, Handler handler) {
- cancel(listener);
+ private void setImpl(
+ int type,
+ long triggerAtMs,
+ long windowLengthMs,
+ long intervalMs,
+ @Nullable String tag,
+ OnAlarmListener listener,
+ Executor executor,
+ @Nullable WorkSource workSource,
+ boolean allowWhileIdle) {
synchronized (scheduledAlarms) {
- scheduledAlarms.add(new ScheduledAlarm(type, triggerAtTime, 0L, listener, handler));
- Collections.sort(scheduledAlarms);
+ cancel(listener);
+ scheduledAlarms.add(
+ new InternalScheduledAlarm(
+ type,
+ triggerAtMs,
+ windowLengthMs,
+ intervalMs,
+ tag,
+ listener,
+ executor,
+ workSource,
+ null,
+ allowWhileIdle)
+ .schedule());
}
}
- /** @return the next scheduled alarm after consuming it */
+ /**
+ * Returns the earliest scheduled alarm and removes it from the list of scheduled alarms.
+ *
+ * @deprecated Prefer to use {@link ShadowAlarmManager#setAutoSchedule(boolean)} in combination
+ * with incrementing time to actually run alarms and test their side-effects.
+ */
+ @Deprecated
+ @Nullable
public ScheduledAlarm getNextScheduledAlarm() {
- if (scheduledAlarms.isEmpty()) {
- return null;
- } else {
- return scheduledAlarms.remove(0);
+ synchronized (scheduledAlarms) {
+ InternalScheduledAlarm alarm = scheduledAlarms.poll();
+ if (alarm != null) {
+ alarm.deschedule();
+ }
+ return alarm;
}
}
- /** @return the most recently scheduled alarm without consuming it */
+ /** Returns the earliest scheduled alarm. */
+ @Nullable
public ScheduledAlarm peekNextScheduledAlarm() {
- if (scheduledAlarms.isEmpty()) {
- return null;
- } else {
- return scheduledAlarms.get(0);
+ synchronized (scheduledAlarms) {
+ return scheduledAlarms.peek();
}
}
- /** @return all scheduled alarms */
+ /** Returns a list of all scheduled alarms, ordered from earliest time to latest time. */
public List<ScheduledAlarm> getScheduledAlarms() {
- return scheduledAlarms;
- }
-
- @Implementation
- protected void cancel(PendingIntent operation) {
- ShadowPendingIntent shadowPendingIntent = Shadow.extract(operation);
- final Intent toRemove = shadowPendingIntent.getSavedIntent();
- final int requestCode = shadowPendingIntent.getRequestCode();
- for (ScheduledAlarm scheduledAlarm : scheduledAlarms) {
- if (scheduledAlarm.operation != null) {
- ShadowPendingIntent scheduledShadowPendingIntent = Shadow.extract(scheduledAlarm.operation);
- final Intent scheduledIntent = scheduledShadowPendingIntent.getSavedIntent();
- final int scheduledRequestCode = scheduledShadowPendingIntent.getRequestCode();
- if (scheduledIntent.filterEquals(toRemove) && scheduledRequestCode == requestCode) {
- scheduledAlarms.remove(scheduledAlarm);
- break;
- }
- }
+ synchronized (scheduledAlarms) {
+ return new ArrayList<>(scheduledAlarms);
}
}
- @Implementation(minSdk = N)
- protected void cancel(OnAlarmListener listener) {
- for (ScheduledAlarm scheduledAlarm : scheduledAlarms) {
- if (scheduledAlarm.onAlarmListener != null) {
- if (scheduledAlarm.onAlarmListener.equals(listener)) {
- scheduledAlarms.remove(scheduledAlarm);
- break;
- }
+ /**
+ * Immediately removes the given alarm from the list of scheduled alarms (and then reschedules it
+ * in the case of a repeating alarm) and fires it. The given alarm must on the list of scheduled
+ * alarms prior to being fired.
+ *
+ * <p>Generally prefer to use {@link ShadowAlarmManager#setAutoSchedule(boolean)} in combination
+ * with advancing time on the main Looper in order to test alarms - however this method can be
+ * useful to emulate rescheduled, reordered, or delayed alarms, as may happen on a real device.
+ */
+ public void fireAlarm(ScheduledAlarm alarm) {
+ synchronized (scheduledAlarms) {
+ if (!scheduledAlarms.contains(alarm)) {
+ throw new IllegalArgumentException();
}
- }
- }
- /** Returns the schedule exact alarm state set by {@link #setCanScheduleExactAlarms}. */
- @Implementation(minSdk = S)
- protected boolean canScheduleExactAlarms() {
- return canScheduleExactAlarms;
+ ((InternalScheduledAlarm) alarm).deschedule();
+ ((InternalScheduledAlarm) alarm).run();
+ }
}
/**
- * Sets the schedule exact alarm state reported by {@link AlarmManager#canScheduleExactAlarms},
+ * Sets the schedule exact alarm state reported by {@link AlarmManager#canScheduleExactAlarms()},
* but has no effect otherwise.
*/
public static void setCanScheduleExactAlarms(boolean scheduleExactAlarms) {
canScheduleExactAlarms = scheduleExactAlarms;
}
- /** Container object to hold a PendingIntent and parameters describing when to send it. */
+ /** Represents a set alarm. */
public static class ScheduledAlarm implements Comparable<ScheduledAlarm> {
- public final int type;
- public final long triggerAtTime;
- public final long interval;
- public final PendingIntent operation;
- public final boolean allowWhileIdle;
-
- // A non-null showIntent implies this alarm has a user interface. (i.e. in an alarm clock app)
- public final PendingIntent showIntent;
-
- public final OnAlarmListener onAlarmListener;
- public final Handler handler;
-
+ @Deprecated public final int type;
+ @Deprecated public final long triggerAtTime;
+ private final long windowLengthMs;
+ @Deprecated public final long interval;
+ @Nullable private final String tag;
+ @Deprecated @Nullable public final PendingIntent operation;
+ @Deprecated @Nullable public final OnAlarmListener onAlarmListener;
+ @Deprecated @Nullable public final Executor executor;
+ @Nullable private final WorkSource workSource;
+ @Nullable private final Object alarmClockInfo;
+ @Deprecated public final boolean allowWhileIdle;
+
+ @Deprecated @Nullable public final PendingIntent showIntent;
+ @Deprecated @Nullable public final Handler handler;
+
+ @Deprecated
public ScheduledAlarm(
- int type, long triggerAtTime, PendingIntent operation, PendingIntent showIntent) {
- this(type, triggerAtTime, 0, operation, showIntent);
+ int type, long triggerAtMs, PendingIntent operation, PendingIntent showIntent) {
+ this(type, triggerAtMs, 0, operation, showIntent);
}
+ @Deprecated
public ScheduledAlarm(
int type,
- long triggerAtTime,
- long interval,
+ long triggerAtMs,
+ long intervalMs,
PendingIntent operation,
PendingIntent showIntent) {
- this(type, triggerAtTime, interval, operation, showIntent, null, null, false);
+ this(type, triggerAtMs, intervalMs, operation, showIntent, false);
}
+ @Deprecated
public ScheduledAlarm(
int type,
- long triggerAtTime,
- long interval,
+ long triggerAtMs,
+ long intervalMs,
PendingIntent operation,
PendingIntent showIntent,
boolean allowWhileIdle) {
- this(type, triggerAtTime, interval, operation, showIntent, null, null, allowWhileIdle);
+ this(
+ type,
+ triggerAtMs,
+ intervalMs,
+ WINDOW_HEURISTIC,
+ operation,
+ null,
+ VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && showIntent != null
+ ? new AlarmClockInfo(triggerAtMs, showIntent)
+ : null,
+ allowWhileIdle);
}
- private ScheduledAlarm(
+ protected ScheduledAlarm(
int type,
- long triggerAtTime,
- long interval,
- OnAlarmListener onAlarmListener,
- Handler handler) {
- this(type, triggerAtTime, interval, null, null, onAlarmListener, handler, false);
+ long triggerAtMs,
+ long windowLengthMs,
+ long intervalMs,
+ PendingIntent operation,
+ @Nullable WorkSource workSource,
+ @Nullable Object alarmClockInfo,
+ boolean allowWhileIdle) {
+ this.type = type;
+ this.triggerAtTime = triggerAtMs;
+ this.windowLengthMs = windowLengthMs;
+ this.interval = intervalMs;
+ this.tag = null;
+ this.operation = Objects.requireNonNull(operation);
+ this.onAlarmListener = null;
+ this.executor = null;
+ this.workSource = workSource;
+ this.alarmClockInfo = alarmClockInfo;
+ this.allowWhileIdle = allowWhileIdle;
+
+ this.handler = null;
+ if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && alarmClockInfo != null) {
+ this.showIntent = ((AlarmClockInfo) alarmClockInfo).getShowIntent();
+ } else {
+ this.showIntent = null;
+ }
}
- private ScheduledAlarm(
+ protected ScheduledAlarm(
int type,
- long triggerAtTime,
- long interval,
- PendingIntent operation,
- PendingIntent showIntent,
- OnAlarmListener onAlarmListener,
- Handler handler,
+ long triggerAtMs,
+ long windowLengthMs,
+ long intervalMs,
+ @Nullable String tag,
+ OnAlarmListener listener,
+ Executor executor,
+ @Nullable WorkSource workSource,
+ @Nullable Object alarmClockInfo,
boolean allowWhileIdle) {
this.type = type;
- this.triggerAtTime = triggerAtTime;
- this.operation = operation;
- this.interval = interval;
- this.showIntent = showIntent;
- this.onAlarmListener = onAlarmListener;
- this.handler = handler;
+ this.triggerAtTime = triggerAtMs;
+ this.windowLengthMs = windowLengthMs;
+ this.interval = intervalMs;
+ this.tag = tag;
+ this.operation = null;
+ this.onAlarmListener = Objects.requireNonNull(listener);
+ this.executor = Objects.requireNonNull(executor);
+ this.workSource = workSource;
+ this.alarmClockInfo = alarmClockInfo;
this.allowWhileIdle = allowWhileIdle;
+
+ if (executor instanceof HandlerExecutor) {
+ this.handler = ((HandlerExecutor) executor).handler;
+ } else {
+ this.handler = null;
+ }
+ if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && alarmClockInfo != null) {
+ this.showIntent = ((AlarmClockInfo) alarmClockInfo).getShowIntent();
+ } else {
+ this.showIntent = null;
+ }
}
- @TargetApi(LOLLIPOP)
+ protected ScheduledAlarm(long triggerAtMs, ScheduledAlarm alarm) {
+ this.type = alarm.type;
+ this.triggerAtTime = triggerAtMs;
+ this.windowLengthMs = alarm.windowLengthMs;
+ this.interval = alarm.interval;
+ this.tag = alarm.tag;
+ this.operation = alarm.operation;
+ this.onAlarmListener = alarm.onAlarmListener;
+ this.executor = alarm.executor;
+ this.workSource = alarm.workSource;
+ this.alarmClockInfo = alarm.alarmClockInfo;
+ this.allowWhileIdle = alarm.allowWhileIdle;
+
+ this.handler = alarm.handler;
+ this.showIntent = alarm.showIntent;
+ }
+
+ public int getType() {
+ return type;
+ }
+
+ public long getTriggerAtMs() {
+ return triggerAtTime;
+ }
+
+ public long getWindowLengthMs() {
+ return windowLengthMs;
+ }
+
+ public long getIntervalMs() {
+ return interval;
+ }
+
+ @Nullable
+ public String getTag() {
+ return tag;
+ }
+
+ @Nullable
+ public WorkSource getWorkSource() {
+ return workSource;
+ }
+
+ @RequiresApi(VERSION_CODES.LOLLIPOP)
+ @Nullable
public AlarmClockInfo getAlarmClockInfo() {
- return showIntent == null ? null : new AlarmClockInfo(triggerAtTime, showIntent);
+ return (AlarmClockInfo) alarmClockInfo;
+ }
+
+ public boolean isAllowWhileIdle() {
+ return allowWhileIdle;
}
@Override
@@ -311,6 +662,119 @@ public class ShadowAlarmManager {
}
}
+ // wrapper class created because we can't modify ScheduledAlarm without breaking compatibility
+ private class InternalScheduledAlarm extends ScheduledAlarm implements Runnable {
+
+ InternalScheduledAlarm(
+ int type,
+ long triggerAtMs,
+ long windowLengthMs,
+ long intervalMs,
+ PendingIntent operation,
+ @Nullable WorkSource workSource,
+ @Nullable Object alarmClockInfo,
+ boolean allowWhileIdle) {
+ super(
+ type,
+ triggerAtMs,
+ windowLengthMs,
+ intervalMs,
+ operation,
+ workSource,
+ alarmClockInfo,
+ allowWhileIdle);
+ }
+
+ InternalScheduledAlarm(
+ int type,
+ long triggerAtMs,
+ long windowLengthMs,
+ long intervalMs,
+ @Nullable String tag,
+ OnAlarmListener listener,
+ Executor executor,
+ @Nullable WorkSource workSource,
+ @Nullable Object alarmClockInfo,
+ boolean allowWhileIdle) {
+ super(
+ type,
+ triggerAtMs,
+ windowLengthMs,
+ intervalMs,
+ tag,
+ listener,
+ executor,
+ workSource,
+ alarmClockInfo,
+ allowWhileIdle);
+ }
+
+ InternalScheduledAlarm(long triggerAtMs, InternalScheduledAlarm alarm) {
+ super(triggerAtMs, alarm);
+ }
+
+ InternalScheduledAlarm schedule() {
+ if (autoSchedule) {
+ schedulingHandler.postDelayed(this, triggerAtTime - SystemClock.elapsedRealtime());
+ }
+ return this;
+ }
+
+ void deschedule() {
+ schedulingHandler.removeCallbacks(this);
+ }
+
+ @Override
+ public void run() {
+ Executor executor;
+ if (operation != null) {
+ executor = Runnable::run;
+ } else {
+ executor = Objects.requireNonNull(this.executor);
+ }
+
+ executor.execute(
+ () -> {
+ synchronized (scheduledAlarms) {
+ if (!scheduledAlarms.remove(this)) {
+ return;
+ }
+ if (interval > 0) {
+ scheduledAlarms.add(
+ new InternalScheduledAlarm(triggerAtTime + interval, this).schedule());
+ }
+ }
+ if (operation != null) {
+ try {
+ operation.send();
+ } catch (CanceledException e) {
+ // only necessary in case this is a repeated alarm and we've already rescheduled
+ cancel(operation);
+ }
+ } else if (VERSION.SDK_INT >= VERSION_CODES.N) {
+ Objects.requireNonNull(onAlarmListener).onAlarm();
+ } else {
+ throw new IllegalStateException();
+ }
+ });
+ }
+ }
+
+ private static final class HandlerExecutor implements Executor {
+ private final Handler handler;
+
+ HandlerExecutor(@Nullable Handler handler) {
+ this.handler = handler != null ? handler : new Handler(Looper.getMainLooper());
+ }
+
+ @Override
+ public void execute(Runnable command) {
+ if (!handler.post(command)) {
+ throw new RejectedExecutionException(handler + " is shutting down");
+ }
+ }
+ }
+
@ForType(AlarmManager.class)
interface AlarmManagerReflector {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAnimationUtils.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAnimationUtils.java
index 8666c6b8e..10e293758 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAnimationUtils.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAnimationUtils.java
@@ -9,7 +9,6 @@ import android.view.animation.LinearInterpolator;
import android.view.animation.TranslateAnimation;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
-import org.robolectric.shadow.api.Shadow;
@SuppressWarnings({"UnusedDeclaration"})
@Implements(AnimationUtils.class)
@@ -24,8 +23,6 @@ public class ShadowAnimationUtils {
protected static LayoutAnimationController loadLayoutAnimation(Context context, int id) {
Animation anim = new TranslateAnimation(0, 0, 30, 0);
LayoutAnimationController layoutAnim = new LayoutAnimationController(anim);
- ShadowLayoutAnimationController shadowLayoutAnimationController = Shadow.extract(layoutAnim);
- shadowLayoutAnimationController.setLoadedFromResourceId(id);
return layoutAnim;
}
}
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 cf93b9e9c..a8a0847a0 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAppWidgetManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAppWidgetManager.java
@@ -55,6 +55,10 @@ public class ShadowAppWidgetManager {
private Multimap<UserHandle, AppWidgetProviderInfo> installedProvidersForProfile =
HashMultimap.create();
+ // AppWidgetProvider is enabled if at least one widget is active. `isWidgetsEnabled` should be set
+ // to false if the last widget is removed (when removing widgets is implemented).
+ private boolean isWidgetsEnabled = false;
+
@Implementation(maxSdk = KITKAT)
protected void __constructor__(Context context) {
this.context = context;
@@ -307,6 +311,15 @@ public class ShadowAppWidgetManager {
widgetInfo.view = widgetInfo.lastRemoteViews.apply(context, new AppWidgetHostView(context));
}
+ private void enableWidgetsIfNecessary(Class<? extends AppWidgetProvider> appWidgetProviderClass) {
+ if (!isWidgetsEnabled) {
+ isWidgetsEnabled = true;
+ AppWidgetProvider appWidgetProvider =
+ ReflectionHelpers.callConstructor(appWidgetProviderClass);
+ appWidgetProvider.onReceive(context, new Intent(AppWidgetManager.ACTION_APPWIDGET_ENABLED));
+ }
+ }
+
/**
* Creates a widget by inflating its layout.
*
@@ -345,7 +358,12 @@ public class ShadowAppWidgetManager {
newWidgetIds[i] = myWidgetId;
}
- appWidgetProvider.onUpdate(context, realAppWidgetManager, newWidgetIds);
+ // Enable widgets if we are creating the first widget.
+ enableWidgetsIfNecessary(appWidgetProviderClass);
+
+ Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, newWidgetIds);
+ appWidgetProvider.onReceive(context, intent);
return newWidgetIds;
}
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 544d1999c..ae8dfb894 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplicationPackageManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowApplicationPackageManager.java
@@ -67,6 +67,7 @@ import android.content.pm.ModuleInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.ApplicationInfoFlags;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManager.OnPermissionsChangedListener;
import android.content.pm.PackageManager.PackageInfoFlags;
@@ -140,6 +141,15 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager {
@Implementation
public List<PackageInfo> getInstalledPackages(int flags) {
+ return getInstalledPackages((long) flags);
+ }
+
+ @Implementation(minSdk = TIRAMISU)
+ protected List<PackageInfo> getInstalledPackages(Object flags) {
+ return getInstalledPackages(((PackageInfoFlags) flags).getValue());
+ }
+
+ private List<PackageInfo> getInstalledPackages(long flags) {
List<PackageInfo> result = new ArrayList<>();
synchronized (lock) {
Set<String> packageNames = null;
@@ -429,6 +439,16 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager {
@Implementation
protected PackageInfo getPackageInfo(String packageName, int flags) throws NameNotFoundException {
+ return getPackageInfo(packageName, (long) flags);
+ }
+
+ @Implementation(minSdk = TIRAMISU)
+ protected PackageInfo getPackageInfo(Object packageName, Object flags)
+ throws NameNotFoundException {
+ return getPackageInfo((String) packageName, ((PackageInfoFlags) flags).getValue());
+ }
+
+ private PackageInfo getPackageInfo(String packageName, long flags) throws NameNotFoundException {
synchronized (lock) {
PackageInfo info = packageInfos.get(packageName);
if (info == null
@@ -494,7 +514,7 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager {
}
private <T extends ComponentInfo> T[] applyFlagsToComponentInfoList(
- T[] components, int flags, int activationFlag, Function<T, T> copyConstructor) {
+ T[] components, long flags, int activationFlag, Function<T, T> copyConstructor) {
if (components == null || (flags & activationFlag) == 0) {
return null;
}
@@ -536,7 +556,7 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager {
|| (VERSION.SDK_INT >= VERSION_CODES.KITKAT && resolveInfo.providerInfo != null);
}
- private static boolean isFlagSet(int flags, int matchFlag) {
+ private static boolean isFlagSet(long flags, long matchFlag) {
return (flags & matchFlag) == matchFlag;
}
@@ -859,6 +879,11 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager {
ActivityInfo::new);
}
+ @Implementation(minSdk = TIRAMISU)
+ protected List<ResolveInfo> queryBroadcastReceivers(Object intent, @NonNull Object flags) {
+ return queryBroadcastReceivers((Intent) intent, (int) ((ResolveInfoFlags) flags).getValue());
+ }
+
private static int matchIntentFilter(Intent intent, IntentFilter intentFilter) {
return intentFilter.match(
intent.getAction(),
@@ -891,7 +916,7 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager {
*
* @throws NameNotFoundException when component is filtered out by a flag
*/
- private void applyFlagsToComponentInfo(ComponentInfo componentInfo, int flags)
+ private void applyFlagsToComponentInfo(ComponentInfo componentInfo, long flags)
throws NameNotFoundException {
componentInfo.name = (componentInfo.name == null) ? "" : componentInfo.name;
ApplicationInfo applicationInfo = componentInfo.applicationInfo;
@@ -975,6 +1000,15 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager {
@Implementation
protected List<ApplicationInfo> getInstalledApplications(int flags) {
+ return getInstalledApplications((long) flags);
+ }
+
+ @Implementation(minSdk = TIRAMISU)
+ protected List<ApplicationInfo> getInstalledApplications(Object flags) {
+ return getInstalledApplications(((ApplicationInfoFlags) flags).getValue());
+ }
+
+ private List<ApplicationInfo> getInstalledApplications(long flags) {
List<PackageInfo> packageInfos = getInstalledPackages(flags);
List<ApplicationInfo> result = new ArrayList<>(packageInfos.size());
@@ -1305,6 +1339,11 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager {
return uid;
}
+ @Implementation(minSdk = TIRAMISU)
+ protected Object getPackageUid(Object packageName, Object flags) throws NameNotFoundException {
+ return getPackageUid((String) packageName, (int) ((PackageInfoFlags) flags).getValue());
+ }
+
@Implementation(minSdk = N)
protected int getPackageUidAsUser(String packageName, int userId) throws NameNotFoundException {
return 0;
@@ -1354,7 +1393,7 @@ public class ShadowApplicationPackageManager extends ShadowPackageManager {
return packageInfo.applicationInfo;
}
- private void applyFlagsToApplicationInfo(@Nullable ApplicationInfo appInfo, int flags)
+ private void applyFlagsToApplicationInfo(@Nullable ApplicationInfo appInfo, long flags)
throws NameNotFoundException {
if (appInfo == null) {
return;
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 183e9f38d..223435110 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAudioManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowAudioManager.java
@@ -14,6 +14,7 @@ import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.TargetApi;
import android.media.AudioAttributes;
+import android.media.AudioDeviceCallback;
import android.media.AudioDeviceInfo;
import android.media.AudioFormat;
import android.media.AudioManager;
@@ -74,6 +75,7 @@ public class ShadowAudioManager {
new HashSet<>();
private final HashSet<AudioManager.AudioPlaybackCallback> audioPlaybackCallbacks =
new HashSet<>();
+ private final HashSet<AudioDeviceCallback> audioDeviceCallbacks = new HashSet<>();
private int ringerMode = AudioManager.RINGER_MODE_NORMAL;
private int mode = AudioManager.MODE_NORMAL;
private boolean bluetoothA2dpOn;
@@ -418,12 +420,123 @@ public class ShadowAudioManager {
defaultDevicesForAttributes = 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
+ * AudioDeviceCallback} is done.
+ *
+ * <p>To add/remove devices one by one and trigger notifications for the list of {@link
+ * AudioDeviceCallback} please use one of the following methods {@link
+ * #addInputDevice(AudioDeviceInfo, boolean)}, {@link #removeInputDevice(AudioDeviceInfo,
+ * boolean)}.
+ */
public void setInputDevices(List<AudioDeviceInfo> inputDevices) {
- this.inputDevices = inputDevices;
+ this.inputDevices = new ArrayList<>(inputDevices);
}
+ /**
+ * Sets the list of connected output devices represented by {@link AudioDeviceInfo}.
+ *
+ * <p>The previous list of output devices is replaced and no notifications of the list of {@link
+ * AudioDeviceCallback} is done.
+ *
+ * <p>To add/remove devices one by one and trigger notifications for the list of {@link
+ * AudioDeviceCallback} please use one of the following methods {@link
+ * #addOutputDevice(AudioDeviceInfo, boolean)}, {@link #removeOutputDevice(AudioDeviceInfo,
+ * boolean)}.
+ */
public void setOutputDevices(List<AudioDeviceInfo> outputDevices) {
- this.outputDevices = outputDevices;
+ this.outputDevices = new ArrayList<>(outputDevices);
+ }
+
+ /**
+ * Adds an input {@link AudioDeviceInfo} and notifies the list of {@link AudioDeviceCallback} if
+ * the device was not present before and indicated by {@code notifyAudioDeviceCallbacks}.
+ */
+ public void addInputDevice(AudioDeviceInfo inputDevice, boolean notifyAudioDeviceCallbacks) {
+ boolean changed =
+ !this.inputDevices.contains(inputDevice) && this.inputDevices.add(inputDevice);
+ if (changed && notifyAudioDeviceCallbacks) {
+ notifyAudioDeviceCallbacks(ImmutableList.of(inputDevice), /* added= */ true);
+ }
+ }
+
+ /**
+ * Removes an input {@link AudioDeviceInfo} and notifies the list of {@link AudioDeviceCallback}
+ * if the device was present before and indicated by {@code notifyAudioDeviceCallbacks}.
+ */
+ public void removeInputDevice(AudioDeviceInfo inputDevice, boolean notifyAudioDeviceCallbacks) {
+ boolean changed = this.inputDevices.remove(inputDevice);
+ if (changed && notifyAudioDeviceCallbacks) {
+ notifyAudioDeviceCallbacks(ImmutableList.of(inputDevice), /* added= */ false);
+ }
+ }
+
+ /**
+ * Adds an output {@link AudioDeviceInfo} and notifies the list of {@link AudioDeviceCallback} if
+ * the device was not present before and indicated by {@code notifyAudioDeviceCallbacks}.
+ */
+ public void addOutputDevice(AudioDeviceInfo outputDevice, boolean notifyAudioDeviceCallbacks) {
+ boolean changed =
+ !this.outputDevices.contains(outputDevice) && this.outputDevices.add(outputDevice);
+ if (changed && notifyAudioDeviceCallbacks) {
+ notifyAudioDeviceCallbacks(ImmutableList.of(outputDevice), /* added= */ true);
+ }
+ }
+
+ /**
+ * Removes an output {@link AudioDeviceInfo} and notifies the list of {@link AudioDeviceCallback}
+ * if the device was present before and indicated by {@code notifyAudioDeviceCallbacks}.
+ */
+ public void removeOutputDevice(AudioDeviceInfo outputDevice, boolean notifyAudioDeviceCallbacks) {
+ boolean changed = this.outputDevices.remove(outputDevice);
+ if (changed && notifyAudioDeviceCallbacks) {
+ notifyAudioDeviceCallbacks(ImmutableList.of(outputDevice), /* added= */ false);
+ }
+ }
+
+ /**
+ * Registers an {@link AudioDeviceCallback} object to receive notifications of changes to the set
+ * of connected audio devices.
+ *
+ * <p>The {@code handler} is ignored.
+ *
+ * @see #addInputDevice(AudioDeviceInfo, boolean)
+ * @see #addOutputDevice(AudioDeviceInfo, boolean)
+ * @see #removeInputDevice(AudioDeviceInfo, boolean)
+ * @see #removeOutputDevice(AudioDeviceInfo, boolean)
+ */
+ @Implementation(minSdk = M)
+ protected void registerAudioDeviceCallback(AudioDeviceCallback callback, Handler handler) {
+ audioDeviceCallbacks.add(callback);
+ // indicate currently available devices as added, similarly to MSG_DEVICES_CALLBACK_REGISTERED
+ callback.onAudioDevicesAdded(getDevices(AudioManager.GET_DEVICES_ALL));
+ }
+
+ /**
+ * Unregisters an {@link AudioDeviceCallback} object which has been previously registered to
+ * receive notifications of changes to the set of connected audio devices.
+ *
+ * @see #addInputDevice(AudioDeviceInfo, boolean)
+ * @see #addOutputDevice(AudioDeviceInfo, boolean)
+ * @see #removeInputDevice(AudioDeviceInfo, boolean)
+ * @see #removeOutputDevice(AudioDeviceInfo, boolean)
+ */
+ @Implementation(minSdk = M)
+ protected void unregisterAudioDeviceCallback(AudioDeviceCallback callback) {
+ audioDeviceCallbacks.remove(callback);
+ }
+
+ private void notifyAudioDeviceCallbacks(List<AudioDeviceInfo> devices, boolean added) {
+ AudioDeviceInfo[] devicesArray = devices.toArray(new AudioDeviceInfo[0]);
+ for (AudioDeviceCallback callback : audioDeviceCallbacks) {
+ if (added) {
+ callback.onAudioDevicesAdded(devicesArray);
+ } else {
+ callback.onAudioDevicesRemoved(devicesArray);
+ }
+ }
}
private List<AudioDeviceInfo> getInputDevices() {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBackdropFrameRenderer.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBackdropFrameRenderer.java
index 2beaab0fb..38243e4a0 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBackdropFrameRenderer.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBackdropFrameRenderer.java
@@ -11,7 +11,6 @@ import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.util.reflector.Accessor;
-import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;
/** Shadow for {@link BackdropFrameRenderer} */
@@ -49,7 +48,6 @@ public class ShadowBackdropFrameRenderer {
@ForType(BackdropFrameRenderer.class)
interface BackdropFrameRendererReflector {
- @Direct
void releaseRenderer();
@Accessor("mRenderer")
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 1b358395b..74133081c 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBitmap.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBitmap.java
@@ -1,80 +1,15 @@
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;
-import static android.os.Build.VERSION_CODES.S;
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-import static java.lang.Integer.max;
-import static java.lang.Integer.min;
-
import android.graphics.Bitmap;
-import android.graphics.ColorSpace;
import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.os.Build;
-import android.os.Parcel;
-import android.util.DisplayMetrics;
-import java.awt.Color;
-import java.awt.Graphics2D;
-import java.awt.geom.Rectangle2D;
-import java.awt.image.BufferedImage;
-import java.awt.image.ColorModel;
-import java.awt.image.DataBufferInt;
-import java.awt.image.WritableRaster;
-import java.io.FileDescriptor;
import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.Buffer;
-import java.nio.ByteBuffer;
-import java.nio.IntBuffer;
-import java.util.Arrays;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.Shadows;
-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;
-
-@SuppressWarnings({"UnusedDeclaration"})
-@Implements(Bitmap.class)
-public class ShadowBitmap {
- /** Number of bytes used internally to represent each pixel */
- private static final int INTERNAL_BYTES_PER_PIXEL = 4;
+import org.robolectric.shadows.ShadowBitmap.Picker;
- int createdFromResId = -1;
- String createdFromPath;
- InputStream createdFromStream;
- FileDescriptor createdFromFileDescriptor;
- byte[] createdFromBytes;
- @RealObject private Bitmap realBitmap;
- private Bitmap createdFromBitmap;
- private Bitmap scaledFromBitmap;
- private int createdFromX = -1;
- private int createdFromY = -1;
- private int createdFromWidth = -1;
- private int createdFromHeight = -1;
- private int[] createdFromColors;
- private Matrix createdFromMatrix;
- private boolean createdFromFilter;
-
- private int width;
- private int height;
- private BufferedImage bufferedImage;
- private Bitmap.Config config;
- private boolean mutable = true;
- private String description = "";
- private boolean recycled = false;
- private boolean hasMipMap;
- private boolean requestPremultiplied = true;
- private boolean hasAlpha;
- private ColorSpace colorSpace;
+/** Base class for {@link Bitmap} shadows. */
+@Implements(value = Bitmap.class, shadowPicker = Picker.class)
+public abstract class ShadowBitmap {
/**
* Returns a textual representation of the appearance of the object.
@@ -87,264 +22,13 @@ public class ShadowBitmap {
return shadowBitmap.getDescription();
}
- @Implementation
- protected static Bitmap createBitmap(int width, int height, Bitmap.Config config) {
- return createBitmap((DisplayMetrics) null, width, height, config);
- }
-
- @Implementation(minSdk = JELLY_BEAN_MR1)
- 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)
- protected static Bitmap createBitmap(
- DisplayMetrics displayMetrics,
- int width,
- int height,
- Bitmap.Config config,
- boolean hasAlpha) {
- if (width <= 0 || height <= 0) {
- throw new IllegalArgumentException("width and height must be > 0");
- }
- checkNotNull(config);
- Bitmap scaledBitmap = ReflectionHelpers.callConstructor(Bitmap.class);
- ShadowBitmap shadowBitmap = Shadow.extract(scaledBitmap);
- shadowBitmap.setDescription("Bitmap (" + width + " x " + height + ")");
-
- shadowBitmap.width = width;
- shadowBitmap.height = height;
- shadowBitmap.config = config;
- shadowBitmap.hasAlpha = hasAlpha;
- shadowBitmap.setMutable(true);
- if (displayMetrics != null) {
- scaledBitmap.setDensity(displayMetrics.densityDpi);
- }
- shadowBitmap.bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
- if (RuntimeEnvironment.getApiLevel() >= O) {
- shadowBitmap.colorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
- }
- return scaledBitmap;
- }
-
- @Implementation(minSdk = O)
- protected static Bitmap createBitmap(
- int width, int height, Bitmap.Config config, boolean hasAlpha, ColorSpace colorSpace) {
- checkArgument(colorSpace != null || config == Bitmap.Config.ALPHA_8);
- Bitmap bitmap = createBitmap(null, width, height, config, hasAlpha);
- ShadowBitmap shadowBitmap = Shadows.shadowOf(bitmap);
- shadowBitmap.colorSpace = colorSpace;
- return bitmap;
- }
-
- @Implementation
- protected static Bitmap createBitmap(
- Bitmap src, int x, int y, int width, int height, Matrix matrix, boolean filter) {
- if (x == 0
- && y == 0
- && width == src.getWidth()
- && height == src.getHeight()
- && (matrix == null || matrix.isIdentity())) {
- return src; // Return the original.
- }
-
- if (x + width > src.getWidth()) {
- throw new IllegalArgumentException("x + width must be <= bitmap.width()");
- }
- if (y + height > src.getHeight()) {
- throw new IllegalArgumentException("y + height must be <= bitmap.height()");
- }
-
- Bitmap newBitmap = ReflectionHelpers.callConstructor(Bitmap.class);
- ShadowBitmap shadowNewBitmap = Shadow.extract(newBitmap);
-
- ShadowBitmap shadowSrcBitmap = Shadow.extract(src);
- shadowNewBitmap.appendDescription(shadowSrcBitmap.getDescription());
- shadowNewBitmap.appendDescription(" at (" + x + "," + y + ")");
- shadowNewBitmap.appendDescription(" with width " + width + " and height " + height);
-
- shadowNewBitmap.createdFromBitmap = src;
- shadowNewBitmap.createdFromX = x;
- shadowNewBitmap.createdFromY = y;
- shadowNewBitmap.createdFromWidth = width;
- shadowNewBitmap.createdFromHeight = height;
- shadowNewBitmap.createdFromMatrix = matrix;
- shadowNewBitmap.createdFromFilter = filter;
- shadowNewBitmap.config = src.getConfig();
- if (matrix != null) {
- ShadowMatrix shadowMatrix = Shadow.extract(matrix);
- shadowNewBitmap.appendDescription(" using matrix " + shadowMatrix.getDescription());
-
- // Adjust width and height by using the matrix.
- RectF mappedRect = new RectF();
- matrix.mapRect(mappedRect, new RectF(0, 0, width, height));
- width = Math.round(mappedRect.width());
- height = Math.round(mappedRect.height());
- }
- if (filter) {
- shadowNewBitmap.appendDescription(" with filter");
- }
-
- // updated if matrix is non-null
- shadowNewBitmap.width = width;
- shadowNewBitmap.height = height;
- shadowNewBitmap.setMutable(true);
- newBitmap.setDensity(src.getDensity());
- if ((matrix == null || matrix.isIdentity()) && shadowSrcBitmap.bufferedImage != null) {
- // Only simple cases are supported for setting image data to the new Bitmap.
- shadowNewBitmap.bufferedImage =
- shadowSrcBitmap.bufferedImage.getSubimage(x, y, width, height);
- }
- if (RuntimeEnvironment.getApiLevel() >= O) {
- shadowNewBitmap.colorSpace = shadowSrcBitmap.colorSpace;
- }
- return newBitmap;
- }
-
- @Implementation
- protected static Bitmap createBitmap(
- int[] colors, int offset, int stride, int width, int height, Bitmap.Config config) {
- return createBitmap(null, colors, offset, stride, width, height, config);
- }
-
- @Implementation(minSdk = JELLY_BEAN_MR1)
- protected static Bitmap createBitmap(
- DisplayMetrics displayMetrics,
- int[] colors,
- int offset,
- int stride,
- int width,
- int height,
- Bitmap.Config config) {
- if (width <= 0) {
- throw new IllegalArgumentException("width must be > 0");
- }
- if (height <= 0) {
- throw new IllegalArgumentException("height must be > 0");
- }
- if (Math.abs(stride) < width) {
- throw new IllegalArgumentException("abs(stride) must be >= width");
- }
- checkNotNull(config);
- int lastScanline = offset + (height - 1) * stride;
- int length = colors.length;
- if (offset < 0
- || (offset + width > length)
- || lastScanline < 0
- || (lastScanline + width > length)) {
- throw new ArrayIndexOutOfBoundsException();
- }
-
- BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
- bufferedImage.setRGB(0, 0, width, height, colors, offset, stride);
- Bitmap bitmap = createBitmap(bufferedImage, width, height, config);
- ShadowBitmap shadowBitmap = Shadow.extract(bitmap);
- shadowBitmap.setMutable(false);
- shadowBitmap.createdFromColors = colors;
- if (displayMetrics != null) {
- bitmap.setDensity(displayMetrics.densityDpi);
- }
- if (RuntimeEnvironment.getApiLevel() >= O) {
- shadowBitmap.colorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
- }
- return bitmap;
- }
-
- private static Bitmap createBitmap(
- BufferedImage bufferedImage, int width, int height, Bitmap.Config config) {
- Bitmap newBitmap = Bitmap.createBitmap(width, height, config);
- ShadowBitmap shadowBitmap = Shadow.extract(newBitmap);
- shadowBitmap.bufferedImage = bufferedImage;
- return newBitmap;
- }
-
- @Implementation
- protected static Bitmap createScaledBitmap(
- Bitmap src, int dstWidth, int dstHeight, boolean filter) {
- if (dstWidth == src.getWidth() && dstHeight == src.getHeight() && !filter) {
- return src; // Return the original.
- }
- if (dstWidth <= 0 || dstHeight <= 0) {
- throw new IllegalArgumentException("width and height must be > 0");
- }
- Bitmap scaledBitmap = ReflectionHelpers.callConstructor(Bitmap.class);
- ShadowBitmap shadowBitmap = Shadow.extract(scaledBitmap);
-
- ShadowBitmap shadowSrcBitmap = Shadow.extract(src);
- shadowBitmap.appendDescription(shadowSrcBitmap.getDescription());
- shadowBitmap.appendDescription(" scaled to " + dstWidth + " x " + dstHeight);
- if (filter) {
- shadowBitmap.appendDescription(" with filter " + filter);
- }
-
- shadowBitmap.createdFromBitmap = src;
- shadowBitmap.scaledFromBitmap = src;
- shadowBitmap.createdFromFilter = filter;
- shadowBitmap.width = dstWidth;
- shadowBitmap.height = dstHeight;
- shadowBitmap.config = src.getConfig();
- shadowBitmap.mutable = true;
- if (!ImageUtil.scaledBitmap(src, scaledBitmap, filter)) {
- shadowBitmap.bufferedImage =
- new BufferedImage(dstWidth, dstHeight, BufferedImage.TYPE_INT_ARGB);
- shadowBitmap.setPixelsInternal(
- new int[shadowBitmap.getHeight() * shadowBitmap.getWidth()],
- 0,
- 0,
- 0,
- 0,
- shadowBitmap.getWidth(),
- shadowBitmap.getHeight());
- }
- if (RuntimeEnvironment.getApiLevel() >= O) {
- shadowBitmap.colorSpace = shadowSrcBitmap.colorSpace;
- }
- return scaledBitmap;
- }
-
- @Implementation
- protected static Bitmap nativeCreateFromParcel(Parcel p) {
- int parceledWidth = p.readInt();
- int parceledHeight = p.readInt();
- Bitmap.Config parceledConfig = (Bitmap.Config) p.readSerializable();
-
- int[] parceledColors = new int[parceledHeight * parceledWidth];
- p.readIntArray(parceledColors);
-
- return createBitmap(
- parceledColors, 0, parceledWidth, parceledWidth, parceledHeight, parceledConfig);
- }
-
- static int getBytesPerPixel(Bitmap.Config config) {
- if (config == null) {
- throw new NullPointerException("Bitmap config was null.");
- }
- switch (config) {
- case RGBA_F16:
- return 8;
- case ARGB_8888:
- case HARDWARE:
- return 4;
- case RGB_565:
- case ARGB_4444:
- return 2;
- case ALPHA_8:
- return 1;
- default:
- throw new IllegalArgumentException("Unknown bitmap config: " + config);
- }
- }
-
/**
* Reference to original Bitmap from which this Bitmap was created. {@code null} if this Bitmap
* was not copied from another instance.
*
* @return Original Bitmap from which this Bitmap was created.
*/
- public Bitmap getCreatedFromBitmap() {
- return createdFromBitmap;
- }
+ public abstract Bitmap getCreatedFromBitmap();
/**
* Resource ID from which this Bitmap was created. {@code 0} if this Bitmap was not created from a
@@ -352,9 +36,7 @@ public class ShadowBitmap {
*
* @return Resource ID from which this Bitmap was created.
*/
- public int getCreatedFromResId() {
- return createdFromResId;
- }
+ public abstract int getCreatedFromResId();
/**
* Path from which this Bitmap was created. {@code null} if this Bitmap was not create from a
@@ -362,9 +44,7 @@ public class ShadowBitmap {
*
* @return Path from which this Bitmap was created.
*/
- public String getCreatedFromPath() {
- return createdFromPath;
- }
+ public abstract String getCreatedFromPath();
/**
* {@link InputStream} from which this Bitmap was created. {@code null} if this Bitmap was not
@@ -372,9 +52,7 @@ public class ShadowBitmap {
*
* @return InputStream from which this Bitmap was created.
*/
- public InputStream getCreatedFromStream() {
- return createdFromStream;
- }
+ public abstract InputStream getCreatedFromStream();
/**
* Bytes from which this Bitmap was created. {@code null} if this Bitmap was not created from
@@ -382,27 +60,21 @@ public class ShadowBitmap {
*
* @return Bytes from which this Bitmap was created.
*/
- public byte[] getCreatedFromBytes() {
- return createdFromBytes;
- }
+ public abstract byte[] getCreatedFromBytes();
/**
* Horizontal offset within {@link #getCreatedFromBitmap()} of this Bitmap's content, or -1.
*
* @return Horizontal offset within {@link #getCreatedFromBitmap()}.
*/
- public int getCreatedFromX() {
- return createdFromX;
- }
+ public abstract int getCreatedFromX();
/**
* Vertical offset within {@link #getCreatedFromBitmap()} of this Bitmap's content, or -1.
*
* @return Vertical offset within {@link #getCreatedFromBitmap()} of this Bitmap's content, or -1.
*/
- public int getCreatedFromY() {
- return createdFromY;
- }
+ public abstract int getCreatedFromY();
/**
* Width from {@link #getCreatedFromX()} within {@link #getCreatedFromBitmap()} of this Bitmap's
@@ -411,9 +83,7 @@ public class ShadowBitmap {
* @return Width from {@link #getCreatedFromX()} within {@link #getCreatedFromBitmap()} of this
* Bitmap's content, or -1.
*/
- public int getCreatedFromWidth() {
- return createdFromWidth;
- }
+ public abstract int getCreatedFromWidth();
/**
* Height from {@link #getCreatedFromX()} within {@link #getCreatedFromBitmap()} of this Bitmap's
@@ -422,9 +92,7 @@ public class ShadowBitmap {
* @return Height from {@link #getCreatedFromX()} within {@link #getCreatedFromBitmap()} of this
* Bitmap's content, or -1.
*/
- public int getCreatedFromHeight() {
- return createdFromHeight;
- }
+ public abstract int getCreatedFromHeight();
/**
* Color array from which this Bitmap was created. {@code null} if this Bitmap was not created
@@ -432,487 +100,34 @@ public class ShadowBitmap {
*
* @return Color array from which this Bitmap was created.
*/
- public int[] getCreatedFromColors() {
- return createdFromColors;
- }
+ public abstract int[] getCreatedFromColors();
/**
* Matrix from which this Bitmap's content was transformed, or {@code null}.
*
* @return Matrix from which this Bitmap's content was transformed, or {@code null}.
*/
- public Matrix getCreatedFromMatrix() {
- return createdFromMatrix;
- }
+ public abstract Matrix getCreatedFromMatrix();
/**
* {@code true} if this Bitmap was created with filtering.
*
* @return {@code true} if this Bitmap was created with filtering.
*/
- public boolean getCreatedFromFilter() {
- return createdFromFilter;
- }
-
- @Implementation(minSdk = S)
- public Bitmap asShared() {
- setMutable(false);
- return realBitmap;
- }
-
- @Implementation
- protected boolean compress(Bitmap.CompressFormat format, int quality, OutputStream stream) {
- appendDescription(" compressed as " + format + " with quality " + quality);
- return ImageUtil.writeToStream(realBitmap, format, quality, stream);
- }
-
- @Implementation
- protected void setPixels(
- int[] pixels, int offset, int stride, int x, int y, int width, int height) {
- checkBitmapMutable();
- setPixelsInternal(pixels, offset, stride, x, y, width, height);
- }
-
- void setPixelsInternal(
- int[] pixels, int offset, int stride, int x, int y, int width, int height) {
- if (bufferedImage == null) {
- bufferedImage = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
- }
- bufferedImage.setRGB(x, y, width, height, pixels, offset, stride);
- }
-
- @Implementation
- protected int getPixel(int x, int y) {
- internalCheckPixelAccess(x, y);
- if (bufferedImage != null) {
- // Note that getPixel() returns a non-premultiplied ARGB value; if
- // config is RGB_565, our return value will likely be more precise than
- // on a physical device, since it needs to map each color component from
- // 5 or 6 bits to 8 bits.
- return bufferedImage.getRGB(x, y);
- } else {
- return 0;
- }
- }
-
- @Implementation
- protected void setPixel(int x, int y, int color) {
- checkBitmapMutable();
- internalCheckPixelAccess(x, y);
- if (bufferedImage == null) {
- bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
- }
- bufferedImage.setRGB(x, y, color);
- }
-
- /**
- * Note that this method will return a RuntimeException unless: - {@code pixels} has the same
- * length as the number of pixels of the bitmap. - {@code x = 0} - {@code y = 0} - {@code width}
- * and {@code height} height match the current bitmap's dimensions.
- */
- @Implementation
- protected void getPixels(
- int[] pixels, int offset, int stride, int x, int y, int width, int height) {
- bufferedImage.getRGB(x, y, width, height, pixels, offset, stride);
- }
-
- @Implementation
- protected int getRowBytes() {
- return getBytesPerPixel(config) * getWidth();
- }
-
- @Implementation
- protected int getByteCount() {
- return getRowBytes() * getHeight();
- }
-
- @Implementation
- protected void recycle() {
- recycled = true;
- }
-
- @Implementation
- protected final boolean isRecycled() {
- return recycled;
- }
-
- @Implementation
- protected Bitmap copy(Bitmap.Config config, boolean isMutable) {
- Bitmap newBitmap = ReflectionHelpers.callConstructor(Bitmap.class);
- ShadowBitmap shadowBitmap = Shadow.extract(newBitmap);
- shadowBitmap.createdFromBitmap = realBitmap;
- shadowBitmap.config = config;
- shadowBitmap.mutable = isMutable;
- shadowBitmap.height = getHeight();
- shadowBitmap.width = getWidth();
- if (bufferedImage != null) {
- ColorModel cm = bufferedImage.getColorModel();
- WritableRaster raster =
- bufferedImage.copyData(bufferedImage.getRaster().createCompatibleWritableRaster());
- shadowBitmap.bufferedImage = new BufferedImage(cm, raster, false, null);
- }
- return newBitmap;
- }
-
- @Implementation(minSdk = KITKAT)
- protected final int getAllocationByteCount() {
- return getRowBytes() * getHeight();
- }
-
- @Implementation
- protected final Bitmap.Config getConfig() {
- return config;
- }
-
- @Implementation(minSdk = KITKAT)
- protected void setConfig(Bitmap.Config config) {
- this.config = config;
- }
-
- @Implementation
- protected final boolean isMutable() {
- return mutable;
- }
-
- public void setMutable(boolean mutable) {
- this.mutable = mutable;
- }
-
- public void appendDescription(String s) {
- description += s;
- }
-
- public String getDescription() {
- return description;
- }
-
- public void setDescription(String s) {
- description = s;
- }
-
- @Implementation
- protected final boolean hasAlpha() {
- return hasAlpha && config != Bitmap.Config.RGB_565;
- }
-
- @Implementation
- protected void setHasAlpha(boolean hasAlpha) {
- this.hasAlpha = hasAlpha;
- }
-
- @Implementation
- protected Bitmap extractAlpha() {
- WritableRaster raster = bufferedImage.getAlphaRaster();
- BufferedImage alphaImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
- alphaImage.getAlphaRaster().setRect(raster);
- return createBitmap(alphaImage, getWidth(), getHeight(), Bitmap.Config.ALPHA_8);
- }
-
- /**
- * This shadow implementation ignores the given paint and offsetXY and simply calls {@link
- * #extractAlpha()}.
- */
- @Implementation
- protected Bitmap extractAlpha(Paint paint, int[] offsetXY) {
- return extractAlpha();
- }
-
- @Implementation(minSdk = JELLY_BEAN_MR1)
- protected final boolean hasMipMap() {
- return hasMipMap;
- }
-
- @Implementation(minSdk = JELLY_BEAN_MR1)
- protected final void setHasMipMap(boolean hasMipMap) {
- this.hasMipMap = hasMipMap;
- }
-
- @Implementation
- protected int getWidth() {
- return width;
- }
-
- @Implementation(minSdk = KITKAT)
- protected void setWidth(int width) {
- this.width = width;
- }
-
- @Implementation
- protected int getHeight() {
- return height;
- }
-
- @Implementation(minSdk = KITKAT)
- protected void setHeight(int height) {
- this.height = height;
- }
+ public abstract boolean getCreatedFromFilter();
- @Implementation
- protected int getGenerationId() {
- return 0;
- }
+ public abstract void setMutable(boolean mutable);
- @Implementation(minSdk = M)
- protected Bitmap createAshmemBitmap() {
- return realBitmap;
- }
+ public abstract void appendDescription(String s);
- @Implementation
- protected void eraseColor(int color) {
- if (bufferedImage != null) {
- int[] pixels = ((DataBufferInt) bufferedImage.getRaster().getDataBuffer()).getData();
- Arrays.fill(pixels, color);
- }
- setDescription(String.format("Bitmap (%d, %d)", width, height));
- if (color != 0) {
- appendDescription(String.format(" erased with 0x%08x", color));
- }
- }
-
- @Implementation
- protected void writeToParcel(Parcel p, int flags) {
- p.writeInt(width);
- p.writeInt(height);
- p.writeSerializable(config);
- int[] pixels = new int[width * height];
- getPixels(pixels, 0, width, 0, 0, width, height);
- p.writeIntArray(pixels);
- }
+ public abstract String getDescription();
- @Implementation
- protected void copyPixelsFromBuffer(Buffer dst) {
- if (isRecycled()) {
- throw new IllegalStateException("Can't call copyPixelsFromBuffer() on a recycled bitmap");
- }
-
- // See the related comment in #copyPixelsToBuffer(Buffer).
- if (getBytesPerPixel(config) != INTERNAL_BYTES_PER_PIXEL) {
- throw new RuntimeException(
- "Not implemented: only Bitmaps with "
- + INTERNAL_BYTES_PER_PIXEL
- + " bytes per pixel are supported");
- }
- if (!(dst instanceof ByteBuffer) && !(dst instanceof IntBuffer)) {
- throw new RuntimeException("Not implemented: unsupported Buffer subclass");
- }
-
- ByteBuffer byteBuffer = null;
- IntBuffer intBuffer;
- if (dst instanceof IntBuffer) {
- intBuffer = (IntBuffer) dst;
- } else {
- byteBuffer = (ByteBuffer) dst;
- intBuffer = byteBuffer.asIntBuffer();
- }
-
- if (intBuffer.remaining() < (width * height)) {
- throw new RuntimeException("Buffer not large enough for pixels");
- }
-
- int[] colors = new int[width * height];
- intBuffer.get(colors);
- if (byteBuffer != null) {
- byteBuffer.position(byteBuffer.position() + intBuffer.position() * INTERNAL_BYTES_PER_PIXEL);
- }
- int[] pixels = ((DataBufferInt) bufferedImage.getRaster().getDataBuffer()).getData();
- System.arraycopy(colors, 0, pixels, 0, pixels.length);
- }
-
- @Implementation
- protected void copyPixelsToBuffer(Buffer dst) {
- // Ensure that the Bitmap uses 4 bytes per pixel, since we always use 4 bytes per pixels
- // internally. Clients of this API probably expect that the buffer size must be >=
- // getByteCount(), but if we don't enforce this restriction then for RGB_4444 and other
- // configs that value would be smaller then the buffer size we actually need.
- if (getBytesPerPixel(config) != INTERNAL_BYTES_PER_PIXEL) {
- throw new RuntimeException(
- "Not implemented: only Bitmaps with "
- + INTERNAL_BYTES_PER_PIXEL
- + " bytes per pixel are supported");
- }
+ public abstract void setDescription(String s);
- if (!(dst instanceof ByteBuffer) && !(dst instanceof IntBuffer)) {
- throw new RuntimeException("Not implemented: unsupported Buffer subclass");
+ /** Shadow picker for {@link Bitmap}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(ShadowLegacyBitmap.class, ShadowNativeBitmap.class);
}
- int[] pixels = ((DataBufferInt) bufferedImage.getRaster().getDataBuffer()).getData();
- if (dst instanceof ByteBuffer) {
- IntBuffer intBuffer = ((ByteBuffer) dst).asIntBuffer();
- intBuffer.put(pixels);
- dst.position(intBuffer.position() * 4);
- } else if (dst instanceof IntBuffer) {
- ((IntBuffer) dst).put(pixels);
- }
- }
-
- @Implementation(minSdk = KITKAT)
- 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");
- }
-
- // This should throw if the resulting allocation size is greater than the initial allocation
- // size of our Bitmap, but we don't keep track of that information reliably, so we're forced to
- // assume that our original dimensions and config are large enough to fit the new dimensions and
- // config
- this.width = width;
- this.height = height;
- this.config = config;
- bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
- }
-
- @Implementation(minSdk = KITKAT)
- protected boolean isPremultiplied() {
- return requestPremultiplied && hasAlpha();
- }
-
- @Implementation(minSdk = KITKAT)
- protected void setPremultiplied(boolean isPremultiplied) {
- this.requestPremultiplied = isPremultiplied;
- }
-
- @Implementation(minSdk = O)
- protected ColorSpace getColorSpace() {
- return colorSpace;
- }
-
- @Implementation(minSdk = Q)
- protected void setColorSpace(ColorSpace colorSpace) {
- this.colorSpace = checkNotNull(colorSpace);
- }
-
- @Implementation
- protected boolean sameAs(Bitmap other) {
- if (other == null) {
- return false;
- }
- ShadowBitmap shadowOtherBitmap = Shadow.extract(other);
- if (this.width != shadowOtherBitmap.width || this.height != shadowOtherBitmap.height) {
- return false;
- }
- if (this.config != shadowOtherBitmap.config) {
- return false;
- }
-
- if (bufferedImage == null && shadowOtherBitmap.bufferedImage != null) {
- return false;
- } else if (bufferedImage != null && shadowOtherBitmap.bufferedImage == null) {
- return false;
- } else if (bufferedImage != null && shadowOtherBitmap.bufferedImage != null) {
- int[] pixels = ((DataBufferInt) bufferedImage.getData().getDataBuffer()).getData();
- int[] otherPixels =
- ((DataBufferInt) shadowOtherBitmap.bufferedImage.getData().getDataBuffer()).getData();
- if (!Arrays.equals(pixels, otherPixels)) {
- return false;
- }
- }
- // When Bitmap.createScaledBitmap is called, the colors array is cleared, so we need a basic
- // way to detect if two scaled bitmaps are the same.
- if (scaledFromBitmap != null && shadowOtherBitmap.scaledFromBitmap != null) {
- return scaledFromBitmap.sameAs(shadowOtherBitmap.scaledFromBitmap);
- }
- return true;
- }
-
- public void setCreatedFromResId(int resId, String description) {
- this.createdFromResId = resId;
- appendDescription(" for resource:" + description);
- }
-
- private void checkBitmapMutable() {
- if (isRecycled()) {
- throw new IllegalStateException("Can't call setPixel() on a recycled bitmap");
- } else if (!isMutable()) {
- throw new IllegalStateException("Bitmap is immutable");
- }
- }
-
- private void internalCheckPixelAccess(int x, int y) {
- if (x < 0) {
- throw new IllegalArgumentException("x must be >= 0");
- }
- if (y < 0) {
- throw new IllegalArgumentException("y must be >= 0");
- }
- if (x >= getWidth()) {
- throw new IllegalArgumentException("x must be < bitmap.width()");
- }
- if (y >= getHeight()) {
- throw new IllegalArgumentException("y must be < bitmap.height()");
- }
- }
-
- void drawRect(Rect r, Paint paint) {
- if (bufferedImage == null) {
- return;
- }
- int[] pixels = ((DataBufferInt) bufferedImage.getRaster().getDataBuffer()).getData();
-
- Rect toDraw =
- new Rect(
- max(0, r.left), max(0, r.top), min(getWidth(), r.right), min(getHeight(), r.bottom));
- if (toDraw.left == 0 && toDraw.top == 0 && toDraw.right == getWidth()) {
- Arrays.fill(pixels, 0, getWidth() * toDraw.bottom, paint.getColor());
- return;
- }
- for (int y = toDraw.top; y < toDraw.bottom; y++) {
- Arrays.fill(
- pixels, y * getWidth() + toDraw.left, y * getWidth() + toDraw.right, paint.getColor());
- }
- }
-
- void drawRect(RectF r, Paint paint) {
- if (bufferedImage == null) {
- return;
- }
-
- Graphics2D graphics2D = bufferedImage.createGraphics();
- Rectangle2D r2d = new Rectangle2D.Float(r.left, r.top, r.right - r.left, r.bottom - r.top);
- graphics2D.setColor(new Color(paint.getColor()));
- graphics2D.draw(r2d);
- graphics2D.dispose();
- }
-
- void drawBitmap(Bitmap source, int left, int top) {
- ShadowBitmap shadowSource = Shadows.shadowOf(source);
- if (bufferedImage == null || shadowSource.bufferedImage == null) {
- // pixel data not available, so there's nothing we can do
- return;
- }
-
- int[] pixels = ((DataBufferInt) bufferedImage.getRaster().getDataBuffer()).getData();
- int[] sourcePixels =
- ((DataBufferInt) shadowSource.bufferedImage.getRaster().getDataBuffer()).getData();
-
- // fast path
- if (left == 0 && top == 0 && getWidth() == source.getWidth()) {
- int size = min(getWidth() * getHeight(), source.getWidth() * source.getHeight());
- System.arraycopy(sourcePixels, 0, pixels, 0, size);
- return;
- }
- // slower (row-by-row) path
- int startSourceY = max(0, -top);
- int startSourceX = max(0, -left);
- int startY = max(0, top);
- int startX = max(0, left);
- int endY = min(getHeight(), top + source.getHeight());
- int endX = min(getWidth(), left + source.getWidth());
- int lenY = endY - startY;
- int lenX = endX - startX;
- for (int y = 0; y < lenY; y++) {
- System.arraycopy(
- sourcePixels,
- (startSourceY + y) * source.getWidth() + startSourceX,
- pixels,
- (startY + y) * getWidth() + startX,
- lenX);
- }
- }
-
- BufferedImage getBufferedImage() {
- return bufferedImage;
- }
-
- void setBufferedImage(BufferedImage bufferedImage) {
- this.bufferedImage = bufferedImage;
}
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBitmapDrawable.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBitmapDrawable.java
index de499e9e0..3bdf7abf6 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBitmapDrawable.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBitmapDrawable.java
@@ -42,8 +42,8 @@ public class ShadowBitmapDrawable extends ShadowDrawable {
protected void setCreatedFromResId(int createdFromResId, String resourceName) {
super.setCreatedFromResId(createdFromResId, resourceName);
Bitmap bitmap = realBitmapDrawable.getBitmap();
- if (bitmap != null && Shadow.extract(bitmap) instanceof ShadowBitmap) {
- ShadowBitmap shadowBitmap = Shadow.extract(bitmap);
+ if (bitmap != null && Shadow.extract(bitmap) instanceof ShadowLegacyBitmap) {
+ ShadowLegacyBitmap shadowBitmap = Shadow.extract(bitmap);
if (shadowBitmap.createdFromResId == -1) {
shadowBitmap.setCreatedFromResId(createdFromResId, resourceName);
}
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 33bc8fd4b..fd2c084a5 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBitmapFactory.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBitmapFactory.java
@@ -85,7 +85,7 @@ public class ShadowBitmapFactory {
return null;
}
Bitmap bitmap = create("resource:" + resourceName, options, image);
- ShadowBitmap shadowBitmap = Shadow.extract(bitmap);
+ ShadowLegacyBitmap shadowBitmap = Shadow.extract(bitmap);
shadowBitmap.createdFromResId = id;
return bitmap;
}
@@ -116,7 +116,7 @@ public class ShadowBitmapFactory {
return null;
}
Bitmap bitmap = create("file:" + pathName, options, image);
- ShadowBitmap shadowBitmap = Shadow.extract(bitmap);
+ ShadowLegacyBitmap shadowBitmap = Shadow.extract(bitmap);
shadowBitmap.createdFromPath = pathName;
return bitmap;
}
@@ -143,7 +143,7 @@ public class ShadowBitmapFactory {
return null;
}
Bitmap bitmap = create("fd:" + fd, null, outPadding, opts, null, image);
- ShadowBitmap shadowBitmap = Shadow.extract(bitmap);
+ ShadowLegacyBitmap shadowBitmap = Shadow.extract(bitmap);
shadowBitmap.createdFromFileDescriptor = fd;
return bitmap;
}
@@ -189,7 +189,7 @@ public class ShadowBitmapFactory {
Bitmap bitmap = create(name, null, outPadding, opts, null, image);
ReflectionHelpers.callInstanceMethod(
bitmap, "setNinePatchChunk", ClassParameter.from(byte[].class, ninePatchChunk));
- ShadowBitmap shadowBitmap = Shadow.extract(bitmap);
+ ShadowLegacyBitmap shadowBitmap = Shadow.extract(bitmap);
shadowBitmap.createdFromStream = is;
if (image != null && opts != null) {
@@ -222,7 +222,7 @@ public class ShadowBitmapFactory {
return null;
}
Bitmap bitmap = create(desc, data, null, opts, null, image);
- ShadowBitmap shadowBitmap = Shadow.extract(bitmap);
+ ShadowLegacyBitmap shadowBitmap = Shadow.extract(bitmap);
shadowBitmap.createdFromBytes = data;
return bitmap;
}
@@ -242,7 +242,7 @@ public class ShadowBitmapFactory {
final Point widthAndHeightOverride,
final RobolectricBufferedImage image) {
Bitmap bitmap = Shadow.newInstanceOf(Bitmap.class);
- ShadowBitmap shadowBitmap = Shadow.extract(bitmap);
+ ShadowLegacyBitmap shadowBitmap = Shadow.extract(bitmap);
shadowBitmap.appendDescription(name == null ? "Bitmap" : "Bitmap for " + name);
Bitmap.Config config;
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 863458509..f8725068e 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothAdapter.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothAdapter.java
@@ -30,11 +30,14 @@ import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.ParcelUuid;
import android.provider.Settings;
+import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.time.Duration;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
@@ -99,6 +102,8 @@ public class ShadowBluetoothAdapter {
private final Map<Integer, BluetoothProfile> profileProxies = new HashMap<>();
private final ConcurrentMap<UUID, BackgroundRfcommServerEntry> backgroundRfcommServers =
new ConcurrentHashMap<>();
+ private final Map<Integer, List<BluetoothProfile.ServiceListener>>
+ bluetoothProfileServiceListeners = new HashMap<>();
@Resetter
public static void reset() {
@@ -528,6 +533,13 @@ public class ShadowBluetoothAdapter {
return false;
} else {
listener.onServiceConnected(profile, proxy);
+ List<BluetoothProfile.ServiceListener> profileListeners =
+ bluetoothProfileServiceListeners.get(profile);
+ if (profileListeners != null) {
+ profileListeners.add(listener);
+ } else {
+ bluetoothProfileServiceListeners.put(profile, new ArrayList<>(ImmutableList.of(listener)));
+ }
return true;
}
}
@@ -548,6 +560,13 @@ public class ShadowBluetoothAdapter {
if (proxy != null && proxy.equals(profileProxies.get(profile))) {
profileProxies.remove(profile);
+ List<BluetoothProfile.ServiceListener> profileListeners =
+ bluetoothProfileServiceListeners.remove(profile);
+ if (profileListeners != null) {
+ for (BluetoothProfile.ServiceListener listener : profileListeners) {
+ listener.onServiceDisconnected(profile);
+ }
+ }
}
}
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 95c75476e..d7ed1cad0 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothDevice.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothDevice.java
@@ -7,6 +7,7 @@ import static android.os.Build.VERSION_CODES.M;
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 org.robolectric.util.reflector.Reflector.reflector;
import android.annotation.IntRange;
@@ -16,10 +17,10 @@ import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothSocket;
+import android.bluetooth.BluetoothStatusCodes;
import android.bluetooth.IBluetooth;
import android.content.Context;
import android.os.Build.VERSION;
-import android.os.Build.VERSION_CODES;
import android.os.Handler;
import android.os.ParcelUuid;
import java.io.IOException;
@@ -39,7 +40,8 @@ import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;
import org.robolectric.util.reflector.Static;
-@Implements(BluetoothDevice.class)
+/** Shadow for {@link BluetoothDevice}. */
+@Implements(value = BluetoothDevice.class, looseSignatures = true)
public class ShadowBluetoothDevice {
@Deprecated // Prefer {@link android.bluetooth.BluetoothAdapter#getRemoteDevice}
public static BluetoothDevice newInstance(String address) {
@@ -103,8 +105,14 @@ public class ShadowBluetoothDevice {
*
* @param alias alias name.
*/
- public void setAlias(String alias) {
- this.alias = alias;
+ @Implementation
+ public Object setAlias(Object alias) {
+ this.alias = (String) alias;
+ if (RuntimeEnvironment.getApiLevel() >= S) {
+ return BluetoothStatusCodes.SUCCESS;
+ } else {
+ return true;
+ }
}
/**
@@ -438,7 +446,7 @@ public class ShadowBluetoothDevice {
private void checkForBluetoothConnectPermission() {
if (shouldThrowSecurityExceptions
- && VERSION.SDK_INT >= VERSION_CODES.S
+ && VERSION.SDK_INT >= S
&& !checkPermission(android.Manifest.permission.BLUETOOTH_CONNECT)) {
throw new SecurityException("Bluetooth connect permission required.");
}
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 5f5fd8807..737df1fb8 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothGatt.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothGatt.java
@@ -10,15 +10,41 @@ import android.annotation.SuppressLint;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothProfile;
import android.content.Context;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.RealObject;
+import org.robolectric.annotation.ReflectorObject;
import org.robolectric.shadow.api.Shadow;
+import org.robolectric.util.PerfStatsCollector;
+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)
public class ShadowBluetoothGatt {
+
+ private static final String NULL_CALLBACK_MSG = "BluetoothGattCallback can not be null.";
+
private BluetoothGattCallback bluetoothGattCallback;
+ private int connectionPriority = BluetoothGatt.CONNECTION_PRIORITY_BALANCED;
+ private boolean isConnected = false;
+ private boolean isClosed = false;
+ private byte[] writtenBytes;
+ private byte[] readBytes;
+ private final Set<BluetoothGattService> discoverableServices = new HashSet<>();
+ private final ArrayList<BluetoothGattService> services = new ArrayList<>();
+
+ @RealObject private BluetoothGatt realBluetoothGatt;
+ @ReflectorObject protected BluetoothGattReflector bluetoothGattReflector;
@SuppressLint("PrivateApi")
@SuppressWarnings("unchecked")
@@ -77,27 +103,204 @@ public class ShadowBluetoothGatt {
new Class<?>[] {Context.class, iBluetoothGattClass, BluetoothDevice.class},
new Object[] {RuntimeEnvironment.getApplication(), null, device});
}
+
+ PerfStatsCollector.getInstance().incrementCount("constructShadowBluetoothGatt");
return bluetoothGatt;
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
- /* package */ BluetoothGattCallback getGattCallback() {
- return bluetoothGattCallback;
+ /**
+ * Connect to a remote device, and performs a {@link
+ * BluetoothGattCallback#onConnectionStateChange} if a {@link BluetoothGattCallback} has been set
+ * by {@link ShadowBluetoothGatt#setGattCallback}
+ *
+ * @return true, if a {@link BluetoothGattCallback} has been set by {@link
+ * ShadowBluetoothGatt#setGattCallback}
+ */
+ @Implementation(minSdk = JELLY_BEAN_MR2)
+ protected boolean connect() {
+ if (this.getGattCallback() != null) {
+ this.isConnected = true;
+ this.getGattCallback()
+ .onConnectionStateChange(
+ this.realBluetoothGatt, BluetoothGatt.GATT_SUCCESS, BluetoothProfile.STATE_CONNECTED);
+ return true;
+ }
+ return false;
}
- /* package */ void setGattCallback(BluetoothGattCallback bluetoothGattCallback) {
- this.bluetoothGattCallback = bluetoothGattCallback;
+ /**
+ * Disconnects an established connection, or cancels a connection attempt currently in progress.
+ */
+ @Implementation(minSdk = JELLY_BEAN_MR2)
+ protected void disconnect() {
+ bluetoothGattReflector.disconnect();
+ if (this.getGattCallback() != null && this.isConnected) {
+ this.getGattCallback()
+ .onConnectionStateChange(
+ this.realBluetoothGatt,
+ BluetoothGatt.GATT_SUCCESS,
+ BluetoothProfile.STATE_DISCONNECTED);
+ }
+ this.isConnected = false;
+ }
+
+ /** Close this Bluetooth GATT client. */
+ @Implementation(minSdk = JELLY_BEAN_MR2)
+ protected void close() {
+ bluetoothGattReflector.close();
+ this.isClosed = true;
+ this.isConnected = false;
}
/**
- * Overrides behavior of {@link BluetoothGatt#connect()} to always return true.
+ * Request a connection parameter update.
*
- * @return true, unconditionally
+ * @param priority Request a specific connection priority. Must be one of {@link
+ * BluetoothGatt#CONNECTION_PRIORITY_BALANCED}, {@link BluetoothGatt#CONNECTION_PRIORITY_HIGH}
+ * or {@link BluetoothGatt#CONNECTION_PRIORITY_LOW_POWER}.
+ * @return true if operation is successful.
+ * @throws IllegalArgumentException If the parameters are outside of their specified range.
*/
- @Implementation(minSdk = JELLY_BEAN_MR2)
- protected boolean connect() {
+ @Implementation(minSdk = O)
+ protected boolean requestConnectionPriority(int priority) {
+ if (priority == BluetoothGatt.CONNECTION_PRIORITY_HIGH
+ || priority == BluetoothGatt.CONNECTION_PRIORITY_BALANCED
+ || priority == BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER) {
+ this.connectionPriority = priority;
+ return true;
+ }
+ throw new IllegalArgumentException("connection priority not within valid range");
+ }
+
+ /**
+ * Overrides {@link BluetoothGatt#discoverServices} to always return false unless there are
+ * discoverable services made available by {@link ShadowBluetoothGatt#addDiscoverableService}
+ *
+ * @return true if discoverable service is available and callback response is possible
+ */
+ @Implementation(minSdk = O)
+ protected boolean discoverServices() {
+ this.services.clear();
+ if (!this.discoverableServices.isEmpty()) {
+ this.services.addAll(this.discoverableServices);
+
+ if (this.getGattCallback() != null) {
+ this.getGattCallback()
+ .onServicesDiscovered(this.realBluetoothGatt, BluetoothGatt.GATT_SUCCESS);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Overrides {@link BluetoothGatt#getServices} to always return a list of services discovered.
+ *
+ * @return list of services that have been discovered through {@link
+ * ShadowBluetoothGatt#discoverServices}, empty if none.
+ */
+ @Implementation(minSdk = O)
+ protected List<BluetoothGattService> getServices() {
+ return new ArrayList<>(this.services);
+ }
+
+ /**
+ * Reads bytes from incoming characteristic if properties are valid and callback is set. Callback
+ * responds with {@link BluetoothGattCallback#onCharacteristicWrite} and returns true when
+ * successful.
+ *
+ * @param characteristic Characteristic to read
+ * @return true, if the read operation was initiated successfully
+ * @throws IllegalStateException if a {@link BluetoothGattCallback} has not been set by {@link
+ * ShadowBluetoothGatt#setGattCallback}
+ */
+ public boolean writeIncomingCharacteristic(BluetoothGattCharacteristic characteristic) {
+ if (this.getGattCallback() == null) {
+ throw new IllegalStateException(NULL_CALLBACK_MSG);
+ }
+ if (characteristic.getService() == null
+ || ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) == 0
+ && (characteristic.getProperties()
+ & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE)
+ == 0)) {
+ return false;
+ }
+ this.writtenBytes = characteristic.getValue();
+ this.bluetoothGattCallback.onCharacteristicWrite(
+ this.realBluetoothGatt, characteristic, BluetoothGatt.GATT_SUCCESS);
+ return true;
+ }
+
+ /**
+ * Writes bytes from incoming characteristic if properties are valid and callback is set. Callback
+ * responds with BluetoothGattCallback#onCharacteristicRead and returns true when successful.
+ *
+ * @param characteristic Characteristic to read
+ * @return true, if the read operation was initiated successfully
+ * @throws IllegalStateException if a {@link BluetoothGattCallback} has not been set by {@link
+ * ShadowBluetoothGatt#setGattCallback}
+ */
+ public boolean readIncomingCharacteristic(BluetoothGattCharacteristic characteristic) {
+ if (this.getGattCallback() == null) {
+ throw new IllegalStateException(NULL_CALLBACK_MSG);
+ }
+ if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) == 0
+ || characteristic.getService() == null) {
+ return false;
+ }
+
+ this.readBytes = characteristic.getValue();
+ this.bluetoothGattCallback.onCharacteristicRead(
+ this.realBluetoothGatt, characteristic, BluetoothGatt.GATT_SUCCESS);
return true;
}
+
+ public void addDiscoverableService(BluetoothGattService service) {
+ this.discoverableServices.add(service);
+ }
+
+ public void removeDiscoverableService(BluetoothGattService service) {
+ this.discoverableServices.remove(service);
+ }
+
+ public BluetoothGattCallback getGattCallback() {
+ return this.bluetoothGattCallback;
+ }
+
+ public void setGattCallback(BluetoothGattCallback bluetoothGattCallback) {
+ this.bluetoothGattCallback = bluetoothGattCallback;
+ }
+
+ public boolean isConnected() {
+ return this.isConnected;
+ }
+
+ public boolean isClosed() {
+ return this.isClosed;
+ }
+
+ public int getConnectionPriority() {
+ return this.connectionPriority;
+ }
+
+ public byte[] getLatestWrittenBytes() {
+ return this.writtenBytes;
+ }
+
+ public byte[] getLatestReadBytes() {
+ return this.readBytes;
+ }
+
+ @ForType(BluetoothGatt.class)
+ private interface BluetoothGattReflector {
+
+ @Direct
+ void disconnect();
+
+ @Direct
+ void close();
+ }
}
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 51dde02aa..54b96265b 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothHeadset.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowBluetoothHeadset.java
@@ -2,6 +2,7 @@ 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 android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
@@ -23,6 +24,7 @@ public class ShadowBluetoothHeadset {
private final List<BluetoothDevice> connectedDevices = new ArrayList<>();
private boolean allowsSendVendorSpecificResultCode = true;
private BluetoothDevice activeBluetoothDevice;
+ private boolean isVoiceRecognitionSupported = true;
/**
* Overrides behavior of {@link getConnectedDevices}. Returns list of devices that is set up by
@@ -130,6 +132,27 @@ public class ShadowBluetoothHeadset {
}
/**
+ * Sets whether the headset supports voice recognition.
+ *
+ * <p>By default voice recognition is supported.
+ *
+ * @see #isVoiceRecognitionSupported(BluetoothDevice)
+ */
+ public void setVoiceRecognitionSupported(boolean supported) {
+ isVoiceRecognitionSupported = supported;
+ }
+
+ /**
+ * Checks whether the headset supports voice recognition.
+ *
+ * @see #setVoiceRecognitionSupported(boolean)
+ */
+ @Implementation(minSdk = S)
+ protected boolean isVoiceRecognitionSupported(BluetoothDevice device) {
+ return isVoiceRecognitionSupported;
+ }
+
+ /**
* Affects the behavior of {@link BluetoothHeadset#sendVendorSpecificResultCode}
*
* @param allowsSendVendorSpecificResultCode can be set to 'false' to simulate the situation where
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCamcorderProfile.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCamcorderProfile.java
new file mode 100644
index 000000000..8576f70a3
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCamcorderProfile.java
@@ -0,0 +1,66 @@
+package org.robolectric.shadows;
+
+import android.media.CamcorderProfile;
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.Table;
+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;
+
+/** Shadow of the CamcorderProfile that allows the caller to add custom profile settings. */
+@Implements(CamcorderProfile.class)
+public class ShadowCamcorderProfile {
+
+ private static final Table<Integer, Integer, CamcorderProfile> profiles = HashBasedTable.create();
+
+ public static void addProfile(int cameraId, int quality, CamcorderProfile profile) {
+ profiles.put(cameraId, quality, profile);
+ }
+
+ @Resetter
+ public static void reset() {
+ profiles.clear();
+ }
+
+ public static CamcorderProfile createProfile(
+ int duration,
+ int quality,
+ int fileFormat,
+ int videoCodec,
+ int videoBitRate,
+ int videoFrameRate,
+ int videoWidth,
+ int videoHeight,
+ int audioCodec,
+ int audioBitRate,
+ int audioSampleRate,
+ int audioChannels) {
+ // CamcorderProfile doesn't have a public constructor. To construct we need to use reflection.
+ return ReflectionHelpers.callConstructor(
+ CamcorderProfile.class,
+ ClassParameter.from(int.class, duration),
+ ClassParameter.from(int.class, quality),
+ ClassParameter.from(int.class, fileFormat),
+ ClassParameter.from(int.class, videoCodec),
+ ClassParameter.from(int.class, videoBitRate),
+ ClassParameter.from(int.class, videoFrameRate),
+ ClassParameter.from(int.class, videoWidth),
+ ClassParameter.from(int.class, videoHeight),
+ ClassParameter.from(int.class, audioCodec),
+ ClassParameter.from(int.class, audioBitRate),
+ ClassParameter.from(int.class, audioSampleRate),
+ ClassParameter.from(int.class, audioChannels));
+ }
+
+ @Implementation
+ protected static boolean hasProfile(int cameraId, int quality) {
+ return profiles.contains(cameraId, quality);
+ }
+
+ @Implementation
+ protected static CamcorderProfile get(int cameraId, int quality) {
+ return profiles.get(cameraId, quality);
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCanvas.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCanvas.java
index 310b610c9..f45737b2f 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCanvas.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCanvas.java
@@ -1,582 +1,80 @@
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;
-import static android.os.Build.VERSION_CODES.M;
-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;
-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.Bitmap;
import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
-import android.graphics.Rect;
import android.graphics.RectF;
-import com.google.common.base.Preconditions;
-import java.util.ArrayList;
-import java.util.List;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.Shadows;
-import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
-import org.robolectric.annotation.RealObject;
-import org.robolectric.annotation.ReflectorObject;
-import org.robolectric.annotation.Resetter;
-import org.robolectric.res.android.NativeObjRegistry;
import org.robolectric.shadow.api.Shadow;
-import org.robolectric.util.ReflectionHelpers;
-import org.robolectric.util.reflector.Direct;
-import org.robolectric.util.reflector.ForType;
-
-/**
- * Broken. This implementation is very specific to the application for which it was developed. Todo:
- * Reimplement. Consider using the same strategy of collecting a history of draw events and
- * providing methods for writing queries based on type, number, and order of events.
- */
-@SuppressWarnings({"UnusedDeclaration"})
-@Implements(Canvas.class)
-public class ShadowCanvas {
- private static final NativeObjRegistry<NativeCanvas> nativeObjectRegistry =
- new NativeObjRegistry<>(NativeCanvas.class);
-
- @RealObject protected Canvas realCanvas;
- @ReflectorObject protected CanvasReflector canvasReflector;
-
- private final List<RoundRectPaintHistoryEvent> roundRectPaintEvents = new ArrayList<>();
- private List<PathPaintHistoryEvent> pathPaintEvents = new ArrayList<>();
- private List<CirclePaintHistoryEvent> circlePaintEvents = new ArrayList<>();
- private List<ArcPaintHistoryEvent> arcPaintEvents = new ArrayList<>();
- private List<RectPaintHistoryEvent> rectPaintEvents = new ArrayList<>();
- private List<LinePaintHistoryEvent> linePaintEvents = new ArrayList<>();
- private List<OvalPaintHistoryEvent> ovalPaintEvents = new ArrayList<>();
- private List<TextHistoryEvent> drawnTextEventHistory = new ArrayList<>();
- private Paint drawnPaint;
- private Bitmap targetBitmap = ReflectionHelpers.callConstructor(Bitmap.class);
- private float translateX;
- private float translateY;
- private float scaleX = 1;
- private float scaleY = 1;
- private int height;
- private int width;
-
- /**
- * Returns a textual representation of the appearance of the object.
- *
- * @param canvas the canvas to visualize
- * @return The textual representation of the appearance of the object.
- */
- public static String visualize(Canvas canvas) {
- ShadowCanvas shadowCanvas = Shadow.extract(canvas);
- return shadowCanvas.getDescription();
- }
-
- @Implementation
- protected void __constructor__(Bitmap bitmap) {
- canvasReflector.__constructor__(bitmap);
- this.targetBitmap = bitmap;
- }
-
- private long getNativeId() {
- return RuntimeEnvironment.getApiLevel() <= KITKAT_WATCH
- ? (int) ReflectionHelpers.getField(realCanvas, "mNativeCanvas")
- : realCanvas.getNativeCanvasWrapper();
- }
-
- private NativeCanvas getNativeCanvas() {
- return nativeObjectRegistry.getNativeObject(getNativeId());
- }
-
- public void appendDescription(String s) {
- ShadowBitmap shadowBitmap = Shadow.extract(targetBitmap);
- shadowBitmap.appendDescription(s);
- }
-
- public String getDescription() {
- ShadowBitmap shadowBitmap = Shadow.extract(targetBitmap);
- return shadowBitmap.getDescription();
- }
-
- @Implementation
- protected void setBitmap(Bitmap bitmap) {
- targetBitmap = bitmap;
- }
-
- @Implementation
- protected void drawText(String text, float x, float y, Paint paint) {
- drawnTextEventHistory.add(new TextHistoryEvent(x, y, paint, text));
- }
-
- @Implementation
- protected void drawText(CharSequence text, int start, int end, float x, float y, Paint paint) {
- drawnTextEventHistory.add(
- new TextHistoryEvent(x, y, paint, text.subSequence(start, end).toString()));
- }
-
- @Implementation
- protected void drawText(char[] text, int index, int count, float x, float y, Paint paint) {
- drawnTextEventHistory.add(new TextHistoryEvent(x, y, paint, new String(text, index, count)));
- }
-
- @Implementation
- protected void drawText(String text, int start, int end, float x, float y, Paint paint) {
- drawnTextEventHistory.add(new TextHistoryEvent(x, y, paint, text.substring(start, end)));
- }
-
- @Implementation
- protected void translate(float x, float y) {
- this.translateX = x;
- this.translateY = y;
- }
-
- @Implementation
- protected void scale(float sx, float sy) {
- this.scaleX = sx;
- this.scaleY = sy;
- }
-
- @Implementation
- protected void scale(float sx, float sy, float px, float py) {
- this.scaleX = sx;
- this.scaleY = sy;
- }
-
- @Implementation
- protected void drawPaint(Paint paint) {
- drawnPaint = paint;
- }
-
- @Implementation
- protected void drawColor(int color) {
- appendDescription("draw color " + color);
- }
-
- @Implementation
- protected void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) {
- describeBitmap(bitmap, paint);
-
- int x = (int) (left + translateX);
- int y = (int) (top + translateY);
- if (x != 0 || y != 0) {
- appendDescription(" at (" + x + "," + y + ")");
- }
-
- if (scaleX != 1 && scaleY != 1) {
- appendDescription(" scaled by (" + scaleX + "," + scaleY + ")");
- }
-
- if (bitmap != null && targetBitmap != null) {
- ShadowBitmap shadowTargetBitmap = Shadows.shadowOf(targetBitmap);
- shadowTargetBitmap.drawBitmap(bitmap, (int) left, (int) top);
- }
- }
-
- @Implementation
- protected void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) {
- describeBitmap(bitmap, paint);
-
- StringBuilder descriptionBuilder = new StringBuilder();
- if (dst != null) {
- descriptionBuilder
- .append(" at (")
- .append(dst.left)
- .append(",")
- .append(dst.top)
- .append(") with height=")
- .append(dst.height())
- .append(" and width=")
- .append(dst.width());
- }
-
- if (src != null) {
- descriptionBuilder.append(" taken from ").append(src.toString());
- }
- appendDescription(descriptionBuilder.toString());
- }
-
- @Implementation
- protected void drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint) {
- describeBitmap(bitmap, paint);
-
- StringBuilder descriptionBuilder = new StringBuilder();
- if (dst != null) {
- descriptionBuilder
- .append(" at (")
- .append(dst.left)
- .append(",")
- .append(dst.top)
- .append(") with height=")
- .append(dst.height())
- .append(" and width=")
- .append(dst.width());
- }
-
- if (src != null) {
- descriptionBuilder.append(" taken from ").append(src.toString());
- }
- appendDescription(descriptionBuilder.toString());
- }
-
- @Implementation
- protected void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) {
- describeBitmap(bitmap, paint);
-
- ShadowMatrix shadowMatrix = Shadow.extract(matrix);
- appendDescription(" transformed by " + shadowMatrix.getDescription());
- }
-
- @Implementation
- protected void drawPath(Path path, Paint paint) {
- pathPaintEvents.add(new PathPaintHistoryEvent(new Path(path), new Paint(paint)));
-
- separateLines();
- ShadowPath shadowPath = Shadow.extract(path);
- appendDescription("Path " + shadowPath.getPoints().toString());
- }
-
- @Implementation
- protected void drawCircle(float cx, float cy, float radius, Paint paint) {
- circlePaintEvents.add(new CirclePaintHistoryEvent(cx, cy, radius, paint));
- }
-
- @Implementation
- protected void drawArc(
- RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint) {
- arcPaintEvents.add(new ArcPaintHistoryEvent(oval, startAngle, sweepAngle, useCenter, paint));
- }
-
- @Implementation
- protected void drawRect(float left, float top, float right, float bottom, Paint paint) {
- rectPaintEvents.add(new RectPaintHistoryEvent(left, top, right, bottom, paint));
-
- if (targetBitmap != null) {
- ShadowBitmap shadowTargetBitmap = Shadows.shadowOf(targetBitmap);
- shadowTargetBitmap.drawRect(new RectF(left, top, right, bottom), paint);
- }
- }
-
- @Implementation
- protected void drawRect(Rect r, Paint paint) {
- rectPaintEvents.add(new RectPaintHistoryEvent(r.left, r.top, r.right, r.bottom, paint));
-
- if (targetBitmap != null) {
- ShadowBitmap shadowTargetBitmap = Shadows.shadowOf(targetBitmap);
- shadowTargetBitmap.drawRect(r, paint);
- }
- }
-
- @Implementation
- protected void drawRoundRect(RectF rect, float rx, float ry, Paint paint) {
- roundRectPaintEvents.add(
- new RoundRectPaintHistoryEvent(
- rect.left, rect.top, rect.right, rect.bottom, rx, ry, paint));
- }
-
- @Implementation
- protected void drawLine(float startX, float startY, float stopX, float stopY, Paint paint) {
- linePaintEvents.add(new LinePaintHistoryEvent(startX, startY, stopX, stopY, paint));
- }
-
- @Implementation
- protected void drawOval(RectF oval, Paint paint) {
- ovalPaintEvents.add(new OvalPaintHistoryEvent(oval, paint));
- }
-
- private void describeBitmap(Bitmap bitmap, Paint paint) {
- separateLines();
-
- ShadowBitmap shadowBitmap = Shadow.extract(bitmap);
- appendDescription(shadowBitmap.getDescription());
-
- if (paint != null) {
- ColorFilter colorFilter = paint.getColorFilter();
- if (colorFilter != null) {
- appendDescription(" with " + colorFilter.getClass().getSimpleName());
- }
- }
- }
-
- private void separateLines() {
- if (getDescription().length() != 0) {
- appendDescription("\n");
- }
- }
+import org.robolectric.shadows.ShadowCanvas.Picker;
- public int getPathPaintHistoryCount() {
- return pathPaintEvents.size();
- }
+/** Base class for {@link Canvas} shadow classes. Mainly contains public shadow API signatures. */
+@Implements(value = Canvas.class, shadowPicker = Picker.class)
+public abstract class ShadowCanvas {
- public int getCirclePaintHistoryCount() {
- return circlePaintEvents.size();
- }
-
- public int getArcPaintHistoryCount() {
- return arcPaintEvents.size();
- }
-
- public boolean hasDrawnPath() {
- return getPathPaintHistoryCount() > 0;
- }
-
- public boolean hasDrawnCircle() {
- return circlePaintEvents.size() > 0;
- }
-
- public Paint getDrawnPathPaint(int i) {
- return pathPaintEvents.get(i).pathPaint;
- }
-
- public Path getDrawnPath(int i) {
- return pathPaintEvents.get(i).drawnPath;
- }
-
- public CirclePaintHistoryEvent getDrawnCircle(int i) {
- return circlePaintEvents.get(i);
- }
-
- public ArcPaintHistoryEvent getDrawnArc(int i) {
- return arcPaintEvents.get(i);
- }
-
- public void resetCanvasHistory() {
- drawnTextEventHistory.clear();
- pathPaintEvents.clear();
- circlePaintEvents.clear();
- rectPaintEvents.clear();
- roundRectPaintEvents.clear();
- linePaintEvents.clear();
- ovalPaintEvents.clear();
- ShadowBitmap shadowBitmap = Shadow.extract(targetBitmap);
- shadowBitmap.setDescription("");
- }
-
- public Paint getDrawnPaint() {
- return drawnPaint;
- }
-
- public void setHeight(int height) {
- this.height = height;
- }
-
- public void setWidth(int width) {
- this.width = width;
- }
-
- @Implementation
- protected int getWidth() {
- if (width == 0) {
- return targetBitmap.getWidth();
+ public static String visualize(Canvas canvas) {
+ if (Shadow.extract(canvas) instanceof ShadowLegacyCanvas) {
+ ShadowCanvas shadowCanvas = Shadow.extract(canvas);
+ return shadowCanvas.getDescription();
+ } else {
+ throw new UnsupportedOperationException(
+ "ShadowCanvas.visualize is only supported in legacy Canvas");
}
- return width;
}
- @Implementation
- protected int getHeight() {
- if (height == 0) {
- return targetBitmap.getHeight();
- }
- return height;
- }
+ public abstract void appendDescription(String s);
- @Implementation
- protected boolean getClipBounds(Rect bounds) {
- Preconditions.checkNotNull(bounds);
- if (targetBitmap == null) {
- return false;
- }
- bounds.set(0, 0, targetBitmap.getWidth(), targetBitmap.getHeight());
- return !bounds.isEmpty();
- }
+ public abstract String getDescription();
- public TextHistoryEvent getDrawnTextEvent(int i) {
- return drawnTextEventHistory.get(i);
- }
+ public abstract int getPathPaintHistoryCount();
- public int getTextHistoryCount() {
- return drawnTextEventHistory.size();
- }
+ public abstract int getCirclePaintHistoryCount();
- public RectPaintHistoryEvent getDrawnRect(int i) {
- return rectPaintEvents.get(i);
- }
+ public abstract int getArcPaintHistoryCount();
- public RectPaintHistoryEvent getLastDrawnRect() {
- return rectPaintEvents.get(rectPaintEvents.size() - 1);
- }
+ public abstract boolean hasDrawnPath();
- public int getRectPaintHistoryCount() {
- return rectPaintEvents.size();
- }
+ public abstract boolean hasDrawnCircle();
- public RoundRectPaintHistoryEvent getDrawnRoundRect(int i) {
- return roundRectPaintEvents.get(i);
- }
+ public abstract Paint getDrawnPathPaint(int i);
- public RoundRectPaintHistoryEvent getLastDrawnRoundRect() {
- return roundRectPaintEvents.get(roundRectPaintEvents.size() - 1);
- }
+ public abstract Path getDrawnPath(int i);
- public int getRoundRectPaintHistoryCount() {
- return roundRectPaintEvents.size();
- }
+ public abstract CirclePaintHistoryEvent getDrawnCircle(int i);
- public LinePaintHistoryEvent getDrawnLine(int i) {
- return linePaintEvents.get(i);
- }
+ public abstract ArcPaintHistoryEvent getDrawnArc(int i);
- public int getLinePaintHistoryCount() {
- return linePaintEvents.size();
- }
+ public abstract void resetCanvasHistory();
- public int getOvalPaintHistoryCount() {
- return ovalPaintEvents.size();
- }
+ public abstract Paint getDrawnPaint();
- public OvalPaintHistoryEvent getDrawnOval(int i) {
- return ovalPaintEvents.get(i);
- }
+ public abstract void setHeight(int height);
- @Implementation(maxSdk = N_MR1)
- protected int save() {
- return getNativeCanvas().save();
- }
+ public abstract void setWidth(int width);
- @Implementation(maxSdk = N_MR1)
- protected void restore() {
- getNativeCanvas().restore();
- }
+ public abstract TextHistoryEvent getDrawnTextEvent(int i);
- @Implementation(maxSdk = N_MR1)
- protected int getSaveCount() {
- return getNativeCanvas().getSaveCount();
- }
+ public abstract int getTextHistoryCount();
- @Implementation(maxSdk = N_MR1)
- protected void restoreToCount(int saveCount) {
- getNativeCanvas().restoreToCount(saveCount);
- }
+ public abstract RectPaintHistoryEvent getDrawnRect(int i);
- @Implementation(minSdk = KITKAT)
- protected void release() {
- nativeObjectRegistry.unregister(getNativeId());
- canvasReflector.release();
- }
+ public abstract RectPaintHistoryEvent getLastDrawnRect();
- @Implementation(maxSdk = KITKAT_WATCH)
- protected static int initRaster(int bitmapHandle) {
- return (int) nativeObjectRegistry.register(new NativeCanvas());
- }
+ public abstract int getRectPaintHistoryCount();
- @Implementation(minSdk = LOLLIPOP, maxSdk = LOLLIPOP_MR1)
- protected static long initRaster(long bitmapHandle) {
- return nativeObjectRegistry.register(new NativeCanvas());
- }
+ public abstract RoundRectPaintHistoryEvent getDrawnRoundRect(int i);
- @Implementation(minSdk = M, maxSdk = N_MR1)
- protected static long initRaster(Bitmap bitmap) {
- return nativeObjectRegistry.register(new NativeCanvas());
- }
+ public abstract RoundRectPaintHistoryEvent getLastDrawnRoundRect();
- @Implementation(minSdk = O, maxSdk = P)
- protected static long nInitRaster(Bitmap bitmap) {
- return nativeObjectRegistry.register(new NativeCanvas());
- }
-
- @Implementation(minSdk = Q)
- protected static long nInitRaster(long bitmapHandle) {
- return nativeObjectRegistry.register(new NativeCanvas());
- }
-
- @Implementation(minSdk = O)
- protected static int nGetSaveCount(long canvasHandle) {
- return nativeObjectRegistry.getNativeObject(canvasHandle).getSaveCount();
- }
-
- @Implementation(minSdk = O)
- protected static int nSave(long canvasHandle, int saveFlags) {
- return nativeObjectRegistry.getNativeObject(canvasHandle).save();
- }
-
- @Implementation(maxSdk = KITKAT_WATCH)
- protected static int native_saveLayer(int nativeCanvas, RectF bounds, int paint, int layerFlags) {
- return nativeObjectRegistry.getNativeObject(nativeCanvas).save();
- }
-
- @Implementation(maxSdk = KITKAT_WATCH)
- protected static int native_saveLayer(
- int nativeCanvas, float l, float t, float r, float b, int paint, int layerFlags) {
- return nativeObjectRegistry.getNativeObject(nativeCanvas).save();
- }
+ public abstract int getRoundRectPaintHistoryCount();
- @Implementation(minSdk = LOLLIPOP, maxSdk = N_MR1)
- protected static int native_saveLayer(
- long nativeCanvas, float l, float t, float r, float b, long nativePaint, int layerFlags) {
- return nativeObjectRegistry.getNativeObject(nativeCanvas).save();
- }
-
- @Implementation(minSdk = O, maxSdk = R)
- protected static int nSaveLayer(
- long nativeCanvas, float l, float t, float r, float b, long nativePaint, int layerFlags) {
- return nativeObjectRegistry.getNativeObject(nativeCanvas).save();
- }
+ public abstract LinePaintHistoryEvent getDrawnLine(int i);
- @Implementation(minSdk = S)
- protected static int nSaveLayer(
- long nativeCanvas, float l, float t, float r, float b, long nativePaint) {
- return nativeObjectRegistry.getNativeObject(nativeCanvas).save();
- }
+ public abstract int getLinePaintHistoryCount();
- @Implementation(maxSdk = KITKAT_WATCH)
- protected static int native_saveLayerAlpha(
- int nativeCanvas, RectF bounds, int alpha, int layerFlags) {
- return nativeObjectRegistry.getNativeObject(nativeCanvas).save();
- }
+ public abstract int getOvalPaintHistoryCount();
- @Implementation(maxSdk = KITKAT_WATCH)
- protected static int native_saveLayerAlpha(
- int nativeCanvas, float l, float t, float r, float b, int alpha, int layerFlags) {
- return nativeObjectRegistry.getNativeObject(nativeCanvas).save();
- }
-
- @Implementation(minSdk = LOLLIPOP, maxSdk = N_MR1)
- protected static int native_saveLayerAlpha(
- long nativeCanvas, float l, float t, float r, float b, int alpha, int layerFlags) {
- return nativeObjectRegistry.getNativeObject(nativeCanvas).save();
- }
-
- @Implementation(minSdk = O, maxSdk = R)
- protected static int nSaveLayerAlpha(
- long nativeCanvas, float l, float t, float r, float b, int alpha, int layerFlags) {
- return nativeObjectRegistry.getNativeObject(nativeCanvas).save();
- }
-
- @Implementation(minSdk = S)
- protected static int nSaveLayerAlpha(
- long nativeCanvas, float l, float t, float r, float b, int alpha) {
- return nativeObjectRegistry.getNativeObject(nativeCanvas).save();
- }
-
- @Implementation(minSdk = O)
- protected static boolean nRestore(long canvasHandle) {
- return nativeObjectRegistry.getNativeObject(canvasHandle).restore();
- }
-
- @Implementation(minSdk = O)
- protected static void nRestoreToCount(long canvasHandle, int saveCount) {
- nativeObjectRegistry.getNativeObject(canvasHandle).restoreToCount(saveCount);
- }
-
- @Resetter
- public static void reset() {
- nativeObjectRegistry.clear();
- }
+ public abstract OvalPaintHistoryEvent getDrawnOval(int i);
public static class LinePaintHistoryEvent {
public Paint paint;
@@ -585,8 +83,7 @@ public class ShadowCanvas {
public float stopX;
public float stopY;
- private LinePaintHistoryEvent(
- float startX, float startY, float stopX, float stopY, Paint paint) {
+ LinePaintHistoryEvent(float startX, float startY, float stopX, float stopY, Paint paint) {
this.paint = new Paint(paint);
this.paint.setColor(paint.getColor());
this.paint.setStrokeWidth(paint.getStrokeWidth());
@@ -601,7 +98,7 @@ public class ShadowCanvas {
public final RectF oval;
public final Paint paint;
- private OvalPaintHistoryEvent(RectF oval, Paint paint) {
+ OvalPaintHistoryEvent(RectF oval, Paint paint) {
this.oval = new RectF(oval);
this.paint = new Paint(paint);
this.paint.setColor(paint.getColor());
@@ -617,7 +114,7 @@ public class ShadowCanvas {
public final float right;
public final float bottom;
- private RectPaintHistoryEvent(float left, float top, float right, float bottom, Paint paint) {
+ RectPaintHistoryEvent(float left, float top, float right, float bottom, Paint paint) {
this.rect = new RectF(left, top, right, bottom);
this.paint = new Paint(paint);
this.paint.setColor(paint.getColor());
@@ -642,7 +139,7 @@ public class ShadowCanvas {
public final float rx;
public final float ry;
- private RoundRectPaintHistoryEvent(
+ RoundRectPaintHistoryEvent(
float left, float top, float right, float bottom, float rx, float ry, Paint paint) {
this.rect = new RectF(left, top, right, bottom);
this.paint = new Paint(paint);
@@ -659,23 +156,13 @@ public class ShadowCanvas {
}
}
- private static class PathPaintHistoryEvent {
- private final Path drawnPath;
- private final Paint pathPaint;
-
- PathPaintHistoryEvent(Path drawnPath, Paint pathPaint) {
- this.drawnPath = drawnPath;
- this.pathPaint = pathPaint;
- }
- }
-
public static class CirclePaintHistoryEvent {
public final float centerX;
public final float centerY;
public final float radius;
public final Paint paint;
- private CirclePaintHistoryEvent(float centerX, float centerY, float radius, Paint paint) {
+ CirclePaintHistoryEvent(float centerX, float centerY, float radius, Paint paint) {
this.centerX = centerX;
this.centerY = centerY;
this.radius = radius;
@@ -706,7 +193,7 @@ public class ShadowCanvas {
public final Paint paint;
public final String text;
- private TextHistoryEvent(float x, float y, Paint paint, String text) {
+ TextHistoryEvent(float x, float y, Paint paint, String text) {
this.x = x;
this.y = y;
this.paint = paint;
@@ -714,40 +201,10 @@ public class ShadowCanvas {
}
}
- @SuppressWarnings("MemberName")
- @ForType(Canvas.class)
- private interface CanvasReflector {
- @Direct
- void __constructor__(Bitmap bitmap);
-
- @Direct
- void release();
- }
-
- private static class NativeCanvas {
- private int saveCount = 1;
-
- int save() {
- return saveCount++;
- }
-
- boolean restore() {
- if (saveCount > 1) {
- saveCount--;
- return true;
- } else {
- return false;
- }
- }
-
- int getSaveCount() {
- return saveCount;
- }
-
- void restoreToCount(int saveCount) {
- if (saveCount > 0) {
- this.saveCount = saveCount;
- }
+ /** Shadow picker for {@link Canvas}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(ShadowLegacyCanvas.class, ShadowNativeCanvas.class);
}
}
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCarrierConfigManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCarrierConfigManager.java
index 8a1721212..39b942857 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCarrierConfigManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowCarrierConfigManager.java
@@ -16,6 +16,7 @@ public class ShadowCarrierConfigManager {
private final HashMap<Integer, PersistableBundle> bundles = new HashMap<>();
private final HashMap<Integer, PersistableBundle> overrideBundles = new HashMap<>();
+ private boolean readPhoneStatePermission = true;
/**
* Returns {@link android.os.PersistableBundle} previously set by {@link #overrideConfig} or
@@ -24,6 +25,7 @@ public class ShadowCarrierConfigManager {
*/
@Implementation
public PersistableBundle getConfigForSubId(int subId) {
+ checkReadPhoneStatePermission();
if (overrideBundles.containsKey(subId) && overrideBundles.get(subId) != null) {
return overrideBundles.get(subId);
}
@@ -33,6 +35,10 @@ public class ShadowCarrierConfigManager {
return new PersistableBundle();
}
+ public void setReadPhoneStatePermission(boolean readPhoneStatePermission) {
+ this.readPhoneStatePermission = readPhoneStatePermission;
+ }
+
/**
* Sets that the {@code config} PersistableBundle for a particular {@code subId}; controls the
* return value of {@link CarrierConfigManager#getConfigForSubId()}.
@@ -52,4 +58,10 @@ public class ShadowCarrierConfigManager {
protected void overrideConfig(int subId, @Nullable PersistableBundle config) {
overrideBundles.put(subId, config);
}
+
+ private void checkReadPhoneStatePermission() {
+ if (!readPhoneStatePermission) {
+ throw new SecurityException();
+ }
+ }
}
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 59e275eff..a824e9004 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,7 @@
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;
import android.content.ContentProvider;
@@ -10,14 +11,17 @@ import org.robolectric.annotation.RealObject;
import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;
-@Implements(ContentProvider.class)
+/** Shadow for {@link ContentProvider}. */
+@Implements(value = ContentProvider.class, looseSignatures = true)
public class ShadowContentProvider {
@RealObject private ContentProvider realContentProvider;
private String callingPackage;
- public void setCallingPackage(String callingPackage) {
- this.callingPackage = callingPackage;
+ @Implementation(minSdk = Q, maxSdk = Q)
+ public Object setCallingPackage(Object callingPackage) {
+ this.callingPackage = (String) callingPackage;
+ return callingPackage;
}
@Implementation(minSdk = KITKAT)
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 98adb47cf..0b86844e6 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentResolver.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowContentResolver.java
@@ -73,20 +73,20 @@ public class ShadowContentResolver {
@RealObject ContentResolver realContentResolver;
private BaseCursor cursor;
- private final List<Statement> statements = new CopyOnWriteArrayList<>();
- private final List<InsertStatement> insertStatements = new CopyOnWriteArrayList<>();
- private final List<UpdateStatement> updateStatements = new CopyOnWriteArrayList<>();
- private final List<DeleteStatement> deleteStatements = new CopyOnWriteArrayList<>();
- private List<NotifiedUri> notifiedUris = new ArrayList<>();
- private Map<Uri, BaseCursor> uriCursorMap = new HashMap<>();
- private Map<Uri, Supplier<InputStream>> inputStreamMap = new HashMap<>();
- private Map<Uri, Supplier<OutputStream>> outputStreamMap = new HashMap<>();
- private final Map<String, List<ContentProviderOperation>> contentProviderOperations =
+ private static final List<Statement> statements = new CopyOnWriteArrayList<>();
+ private static final List<InsertStatement> insertStatements = new CopyOnWriteArrayList<>();
+ private static final List<UpdateStatement> updateStatements = new CopyOnWriteArrayList<>();
+ private static final List<DeleteStatement> deleteStatements = new CopyOnWriteArrayList<>();
+ private static final List<NotifiedUri> notifiedUris = new ArrayList<>();
+ private static final Map<Uri, BaseCursor> uriCursorMap = new HashMap<>();
+ private static final Map<Uri, Supplier<InputStream>> inputStreamMap = new HashMap<>();
+ private static final Map<Uri, Supplier<OutputStream>> outputStreamMap = new HashMap<>();
+ private static final Map<String, List<ContentProviderOperation>> contentProviderOperations =
new HashMap<>();
- private ContentProviderResult[] contentProviderResults;
- private final List<UriPermission> uriPermissions = new ArrayList<>();
+ private static ContentProviderResult[] contentProviderResults;
+ private static final List<UriPermission> uriPermissions = new ArrayList<>();
- private final CopyOnWriteArrayList<ContentObserverEntry> contentObservers =
+ private static final CopyOnWriteArrayList<ContentObserverEntry> contentObservers =
new CopyOnWriteArrayList<>();
private static final Map<String, Map<Account, Status>> syncableAccounts = new HashMap<>();
@@ -98,6 +98,18 @@ public class ShadowContentResolver {
@Resetter
public static void reset() {
+ statements.clear();
+ insertStatements.clear();
+ updateStatements.clear();
+ deleteStatements.clear();
+ notifiedUris.clear();
+ uriCursorMap.clear();
+ inputStreamMap.clear();
+ outputStreamMap.clear();
+ contentProviderOperations.clear();
+ contentProviderResults = null;
+ uriPermissions.clear();
+ contentObservers.clear();
syncableAccounts.clear();
providers.clear();
masterSyncAutomatically = false;
@@ -788,7 +800,7 @@ public class ShadowContentResolver {
*/
@Deprecated
public void setCursor(Uri uri, BaseCursor cursorForUri) {
- this.uriCursorMap.put(uri, cursorForUri);
+ uriCursorMap.put(uri, cursorForUri);
}
/**
@@ -883,7 +895,7 @@ public class ShadowContentResolver {
@Deprecated
public void setContentProviderResult(ContentProviderResult[] contentProviderResults) {
- this.contentProviderResults = contentProviderResults;
+ ShadowContentResolver.contentProviderResults = contentProviderResults;
}
private final Map<Uri, RuntimeException> registerContentProviderExceptions = new HashMap<>();
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 c76888258..dbaa35301 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDevicePolicyManager.java
@@ -1,5 +1,8 @@
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;
@@ -128,6 +131,7 @@ public class ShadowDevicePolicyManager {
private final Map<ComponentName, CharSequence> longSupportMessageMap = new HashMap<>();
private final Set<ComponentName> componentsWithActivatedTokens = new HashSet<>();
private Collection<String> packagesToFailForSetApplicationHidden = Collections.emptySet();
+ private int lockTaskFeatures;
private final List<String> lockTaskPackages = new ArrayList<>();
private Context context;
private ApplicationPackageManager applicationPackageManager;
@@ -1236,6 +1240,31 @@ public class ShadowDevicePolicyManager {
return policyGrantedSet != null && policyGrantedSet.contains(usesPolicy);
}
+ @Implementation(minSdk = P)
+ protected int getLockTaskFeatures(ComponentName admin) {
+ Objects.requireNonNull(admin, "ComponentName is null");
+ enforceDeviceOwnerOrProfileOwner(admin);
+ return lockTaskFeatures;
+ }
+
+ @Implementation(minSdk = P)
+ protected void setLockTaskFeatures(ComponentName admin, int flags) {
+ Objects.requireNonNull(admin, "ComponentName is null");
+ enforceDeviceOwnerOrProfileOwner(admin);
+ // Throw if Overview is used without Home.
+ boolean hasHome = (flags & LOCK_TASK_FEATURE_HOME) != 0;
+ boolean hasOverview = (flags & LOCK_TASK_FEATURE_OVERVIEW) != 0;
+ Preconditions.checkArgument(
+ hasHome || !hasOverview,
+ "Cannot use LOCK_TASK_FEATURE_OVERVIEW without LOCK_TASK_FEATURE_HOME");
+ boolean hasNotification = (flags & LOCK_TASK_FEATURE_NOTIFICATIONS) != 0;
+ Preconditions.checkArgument(
+ hasHome || !hasNotification,
+ "Cannot use LOCK_TASK_FEATURE_NOTIFICATIONS without LOCK_TASK_FEATURE_HOME");
+
+ lockTaskFeatures = flags;
+ }
+
@Implementation(minSdk = LOLLIPOP)
protected void setLockTaskPackages(@NonNull ComponentName admin, String[] packages) {
enforceDeviceOwnerOrProfileOwner(admin);
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayListCanvas.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayListCanvas.java
index 71b2ca19d..02d2143ac 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayListCanvas.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowDisplayListCanvas.java
@@ -16,7 +16,7 @@ import org.robolectric.annotation.Implements;
isInAndroidSdk = false,
minSdk = M,
maxSdk = R)
-public class ShadowDisplayListCanvas extends ShadowCanvas {
+public class ShadowDisplayListCanvas extends ShadowLegacyCanvas {
@Implementation(minSdk = O, maxSdk = P)
protected static long nCreateDisplayListCanvas(long node, int width, int height) {
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 59dd70783..1675a025a 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowImsMmTelManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowImsMmTelManager.java
@@ -11,8 +11,9 @@ import android.telephony.SubscriptionManager;
import android.telephony.ims.ImsException;
import android.telephony.ims.ImsMmTelManager;
import android.telephony.ims.ImsMmTelManager.CapabilityCallback;
-import android.telephony.ims.ImsMmTelManager.RegistrationCallback;
import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.ImsRegistrationAttributes;
+import android.telephony.ims.RegistrationManager;
import android.telephony.ims.feature.MmTelFeature.MmTelCapabilities;
import android.telephony.ims.stub.ImsRegistrationImplBase;
import android.util.ArrayMap;
@@ -43,8 +44,10 @@ public class ShadowImsMmTelManager {
protected static final Map<Integer, ImsMmTelManager> existingInstances = new ArrayMap<>();
- private final Map<RegistrationCallback, Executor> registrationCallbackExecutorMap =
- new ArrayMap<>();
+ private final Map<ImsMmTelManager.RegistrationCallback, Executor>
+ registrationCallbackExecutorMap = new ArrayMap<>();
+ private final Map<RegistrationManager.RegistrationCallback, Executor>
+ registrationManagerCallbackExecutorMap = new ArrayMap<>();
private final Map<CapabilityCallback, Executor> capabilityCallbackExecutorMap = new ArrayMap<>();
private boolean imsAvailableOnDevice = true;
private MmTelCapabilities mmTelCapabilitiesAvailable =
@@ -70,7 +73,7 @@ public class ShadowImsMmTelManager {
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@Implementation
protected void registerImsRegistrationCallback(
- @NonNull @CallbackExecutor Executor executor, @NonNull RegistrationCallback c)
+ @NonNull @CallbackExecutor Executor executor, @NonNull ImsMmTelManager.RegistrationCallback c)
throws ImsException {
if (!imsAvailableOnDevice) {
throw new ImsException(
@@ -79,12 +82,41 @@ public class ShadowImsMmTelManager {
registrationCallbackExecutorMap.put(c, executor);
}
+ @RequiresPermission(
+ anyOf = {
+ android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+ android.Manifest.permission.READ_PRECISE_PHONE_STATE
+ })
+ @Implementation(minSdk = VERSION_CODES.R)
+ protected void registerImsRegistrationCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull RegistrationManager.RegistrationCallback c)
+ throws ImsException {
+ if (!imsAvailableOnDevice) {
+ throw new ImsException(
+ "IMS not available on device.", ImsException.CODE_ERROR_UNSUPPORTED_OPERATION);
+ }
+ registrationManagerCallbackExecutorMap.put(c, executor);
+ }
+
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@Implementation
- protected void unregisterImsRegistrationCallback(@NonNull RegistrationCallback c) {
+ protected void unregisterImsRegistrationCallback(
+ @NonNull ImsMmTelManager.RegistrationCallback c) {
registrationCallbackExecutorMap.remove(c);
}
+ @RequiresPermission(
+ anyOf = {
+ android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+ android.Manifest.permission.READ_PRECISE_PHONE_STATE
+ })
+ @Implementation(minSdk = VERSION_CODES.R)
+ protected void unregisterImsRegistrationCallback(
+ @NonNull RegistrationManager.RegistrationCallback c) {
+ registrationManagerCallbackExecutorMap.remove(c);
+ }
+
/**
* Triggers {@link RegistrationCallback#onRegistering(int)} for all registered {@link
* RegistrationCallback} callbacks.
@@ -92,10 +124,23 @@ public class ShadowImsMmTelManager {
* @see #registerImsRegistrationCallback(Executor, RegistrationCallback)
*/
public void setImsRegistering(int imsRegistrationTech) {
- for (Map.Entry<RegistrationCallback, Executor> entry :
+ for (Map.Entry<ImsMmTelManager.RegistrationCallback, Executor> entry :
registrationCallbackExecutorMap.entrySet()) {
entry.getValue().execute(() -> entry.getKey().onRegistering(imsRegistrationTech));
}
+
+ for (Map.Entry<RegistrationManager.RegistrationCallback, Executor> entry :
+ registrationManagerCallbackExecutorMap.entrySet()) {
+ entry.getValue().execute(() -> entry.getKey().onRegistering(imsRegistrationTech));
+ }
+ }
+
+ @RequiresApi(api = VERSION_CODES.S)
+ public void setImsRegistering(@NonNull ImsRegistrationAttributes attrs) {
+ for (Map.Entry<RegistrationManager.RegistrationCallback, Executor> entry :
+ registrationManagerCallbackExecutorMap.entrySet()) {
+ entry.getValue().execute(() -> entry.getKey().onRegistering(attrs));
+ }
}
/**
@@ -106,10 +151,23 @@ public class ShadowImsMmTelManager {
*/
public void setImsRegistered(int imsRegistrationTech) {
this.imsRegistrationTech = imsRegistrationTech;
- for (Map.Entry<RegistrationCallback, Executor> entry :
+ for (Map.Entry<ImsMmTelManager.RegistrationCallback, Executor> entry :
registrationCallbackExecutorMap.entrySet()) {
entry.getValue().execute(() -> entry.getKey().onRegistered(imsRegistrationTech));
}
+
+ for (Map.Entry<RegistrationManager.RegistrationCallback, Executor> entry :
+ registrationManagerCallbackExecutorMap.entrySet()) {
+ entry.getValue().execute(() -> entry.getKey().onRegistered(imsRegistrationTech));
+ }
+ }
+
+ @RequiresApi(api = VERSION_CODES.S)
+ public void setImsRegistered(@NonNull ImsRegistrationAttributes attrs) {
+ for (Map.Entry<RegistrationManager.RegistrationCallback, Executor> entry :
+ registrationManagerCallbackExecutorMap.entrySet()) {
+ entry.getValue().execute(() -> entry.getKey().onRegistered(attrs));
+ }
}
/**
@@ -120,10 +178,30 @@ public class ShadowImsMmTelManager {
*/
public void setImsUnregistered(@NonNull ImsReasonInfo imsReasonInfo) {
this.imsRegistrationTech = ImsRegistrationImplBase.REGISTRATION_TECH_NONE;
- for (Map.Entry<RegistrationCallback, Executor> entry :
+ for (Map.Entry<ImsMmTelManager.RegistrationCallback, Executor> entry :
registrationCallbackExecutorMap.entrySet()) {
entry.getValue().execute(() -> entry.getKey().onUnregistered(imsReasonInfo));
}
+
+ for (Map.Entry<RegistrationManager.RegistrationCallback, Executor> entry :
+ registrationManagerCallbackExecutorMap.entrySet()) {
+ entry.getValue().execute(() -> entry.getKey().onUnregistered(imsReasonInfo));
+ }
+ }
+
+ /**
+ * Triggers {@link RegistrationCallback#onTechnologyChangeFailed(int, ImsReasonInfo)} for all
+ * registered {@link RegistrationCallback} callbacks.
+ *
+ * @see #registerImsRegistrationCallback(Executor, RegistrationCallback)
+ */
+ public void setOnTechnologyChangeFailed(int imsRadioTech, @NonNull ImsReasonInfo imsReasonInfo) {
+ for (Map.Entry<RegistrationManager.RegistrationCallback, Executor> entry :
+ registrationManagerCallbackExecutorMap.entrySet()) {
+ entry
+ .getValue()
+ .execute(() -> entry.getKey().onTechnologyChangeFailed(imsRadioTech, imsReasonInfo));
+ }
}
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@@ -174,7 +252,6 @@ public class ShadowImsMmTelManager {
}
/** Returns only one instance per subscription id. */
- @RequiresApi(api = VERSION_CODES.Q)
@Implementation
protected static ImsMmTelManager createForSubscriptionId(int subId) {
if (!SubscriptionManager.isValidSubscriptionId(subId)) {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInsetsController.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInsetsController.java
new file mode 100644
index 000000000..1c47aaba2
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowInsetsController.java
@@ -0,0 +1,77 @@
+package org.robolectric.shadows;
+
+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;
+import org.robolectric.util.reflector.Direct;
+import org.robolectric.util.reflector.ForType;
+
+/** Intercepts calls to [InsetsController] to monitor system bars functionality (hide/show). */
+@Implements(value = InsetsController.class, minSdk = Build.VERSION_CODES.R, isInAndroidSdk = false)
+@RequiresApi(Build.VERSION_CODES.R)
+public class ShadowInsetsController {
+ @ReflectorObject private InsetsControllerReflector insetsControllerReflector;
+
+ /**
+ * Intercepts calls to [InsetsController.show] to detect requested changes to the system
+ * status/nav bar visibility.
+ */
+ @Implementation
+ protected void show(int types) {
+ if (hasStatusBarType(types)) {
+ ShadowViewRootImpl.setIsStatusBarVisible(true);
+ }
+
+ if (hasNavigationBarType(types)) {
+ ShadowViewRootImpl.setIsNavigationBarVisible(true);
+ }
+
+ insetsControllerReflector.show(types);
+ }
+
+ /**
+ * Intercepts calls to [InsetsController.hide] to detect requested changes to the system
+ * status/nav bar visibility.
+ */
+ @Implementation
+ public void hide(int types) {
+ if (hasStatusBarType(types)) {
+ ShadowViewRootImpl.setIsStatusBarVisible(false);
+ }
+
+ if (hasNavigationBarType(types)) {
+ ShadowViewRootImpl.setIsNavigationBarVisible(false);
+ }
+
+ insetsControllerReflector.hide(types);
+ }
+
+ /** Returns true if the given flags contain the mask for the system status bar. */
+ private boolean hasStatusBarType(int types) {
+ return hasTypeMask(types, WindowInsets.Type.statusBars());
+ }
+
+ /** Returns true if the given flags contain the mask for the system navigation bar. */
+ private boolean hasNavigationBarType(int types) {
+ return hasTypeMask(types, WindowInsets.Type.navigationBars());
+ }
+
+ /** Returns true if the given flags contains the requested type mask. */
+ private boolean hasTypeMask(int types, int typeMask) {
+ return (types & typeMask) == typeMask;
+ }
+
+ /** Reflector for [InsetsController] to use for direct (non-intercepted) calls. */
+ @ForType(InsetsController.class)
+ interface InsetsControllerReflector {
+ @Direct
+ void show(int types);
+
+ @Direct
+ void hide(int types);
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLayoutAnimationController.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLayoutAnimationController.java
deleted file mode 100644
index ec6650512..000000000
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLayoutAnimationController.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package org.robolectric.shadows;
-
-import android.view.animation.LayoutAnimationController;
-import org.robolectric.annotation.Implements;
-import org.robolectric.annotation.RealObject;
-
-@Implements(LayoutAnimationController.class)
-public class ShadowLayoutAnimationController {
- @RealObject
- private LayoutAnimationController realAnimation;
-
- private int loadedFromResourceId = -1;
-
- public void setLoadedFromResourceId(int loadedFromResourceId) {
- this.loadedFromResourceId = loadedFromResourceId;
- }
-
- public int getLoadedFromResourceId() {
- if (loadedFromResourceId == -1) {
- throw new IllegalStateException("not loaded from a resource");
- }
- return loadedFromResourceId;
- }
-}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyBitmap.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyBitmap.java
new file mode 100644
index 000000000..79b1cbc6b
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyBitmap.java
@@ -0,0 +1,922 @@
+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;
+import static android.os.Build.VERSION_CODES.S;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static java.lang.Integer.max;
+import static java.lang.Integer.min;
+
+import android.graphics.Bitmap;
+import android.graphics.ColorSpace;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Build;
+import android.os.Parcel;
+import android.util.DisplayMetrics;
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.awt.image.DataBufferInt;
+import java.awt.image.WritableRaster;
+import java.io.FileDescriptor;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+import java.nio.IntBuffer;
+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;
+
+@SuppressWarnings({"UnusedDeclaration"})
+@Implements(value = Bitmap.class, isInAndroidSdk = false)
+public class ShadowLegacyBitmap extends ShadowBitmap {
+ /** Number of bytes used internally to represent each pixel */
+ private static final int INTERNAL_BYTES_PER_PIXEL = 4;
+
+ int createdFromResId = -1;
+ String createdFromPath;
+ InputStream createdFromStream;
+ FileDescriptor createdFromFileDescriptor;
+ byte[] createdFromBytes;
+ @RealObject private Bitmap realBitmap;
+ private Bitmap createdFromBitmap;
+ private Bitmap scaledFromBitmap;
+ private int createdFromX = -1;
+ private int createdFromY = -1;
+ private int createdFromWidth = -1;
+ private int createdFromHeight = -1;
+ private int[] createdFromColors;
+ private Matrix createdFromMatrix;
+ private boolean createdFromFilter;
+
+ private int width;
+ private int height;
+ private BufferedImage bufferedImage;
+ private Bitmap.Config config;
+ private boolean mutable = true;
+ private String description = "";
+ private boolean recycled = false;
+ private boolean hasMipMap;
+ private boolean requestPremultiplied = true;
+ private boolean hasAlpha;
+ private ColorSpace colorSpace;
+
+ @Implementation
+ protected static Bitmap createBitmap(int width, int height, Bitmap.Config config) {
+ return createBitmap((DisplayMetrics) null, width, height, config);
+ }
+
+ @Implementation(minSdk = JELLY_BEAN_MR1)
+ 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)
+ protected static Bitmap createBitmap(
+ DisplayMetrics displayMetrics,
+ int width,
+ int height,
+ Bitmap.Config config,
+ boolean hasAlpha) {
+ if (width <= 0 || height <= 0) {
+ throw new IllegalArgumentException("width and height must be > 0");
+ }
+ checkNotNull(config);
+ Bitmap scaledBitmap = ReflectionHelpers.callConstructor(Bitmap.class);
+ ShadowLegacyBitmap shadowBitmap = Shadow.extract(scaledBitmap);
+ shadowBitmap.setDescription("Bitmap (" + width + " x " + height + ")");
+
+ shadowBitmap.width = width;
+ shadowBitmap.height = height;
+ shadowBitmap.config = config;
+ shadowBitmap.hasAlpha = hasAlpha;
+ shadowBitmap.setMutable(true);
+ if (displayMetrics != null) {
+ scaledBitmap.setDensity(displayMetrics.densityDpi);
+ }
+ shadowBitmap.bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+ if (RuntimeEnvironment.getApiLevel() >= O) {
+ shadowBitmap.colorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
+ }
+ return scaledBitmap;
+ }
+
+ @Implementation(minSdk = O)
+ protected static Bitmap createBitmap(
+ int width, int height, Bitmap.Config config, boolean hasAlpha, ColorSpace colorSpace) {
+ checkArgument(colorSpace != null || config == Bitmap.Config.ALPHA_8);
+ Bitmap bitmap = createBitmap(null, width, height, config, hasAlpha);
+ ShadowLegacyBitmap shadowBitmap = Shadow.extract(bitmap);
+ shadowBitmap.colorSpace = colorSpace;
+ return bitmap;
+ }
+
+ @Implementation
+ protected static Bitmap createBitmap(
+ Bitmap src, int x, int y, int width, int height, Matrix matrix, boolean filter) {
+ if (x == 0
+ && y == 0
+ && width == src.getWidth()
+ && height == src.getHeight()
+ && (matrix == null || matrix.isIdentity())) {
+ return src; // Return the original.
+ }
+
+ if (x + width > src.getWidth()) {
+ throw new IllegalArgumentException("x + width must be <= bitmap.width()");
+ }
+ if (y + height > src.getHeight()) {
+ throw new IllegalArgumentException("y + height must be <= bitmap.height()");
+ }
+
+ Bitmap newBitmap = ReflectionHelpers.callConstructor(Bitmap.class);
+ ShadowLegacyBitmap shadowNewBitmap = Shadow.extract(newBitmap);
+
+ ShadowLegacyBitmap shadowSrcBitmap = Shadow.extract(src);
+ shadowNewBitmap.appendDescription(shadowSrcBitmap.getDescription());
+ shadowNewBitmap.appendDescription(" at (" + x + "," + y + ")");
+ shadowNewBitmap.appendDescription(" with width " + width + " and height " + height);
+
+ shadowNewBitmap.createdFromBitmap = src;
+ shadowNewBitmap.createdFromX = x;
+ shadowNewBitmap.createdFromY = y;
+ shadowNewBitmap.createdFromWidth = width;
+ shadowNewBitmap.createdFromHeight = height;
+ shadowNewBitmap.createdFromMatrix = matrix;
+ shadowNewBitmap.createdFromFilter = filter;
+ shadowNewBitmap.config = src.getConfig();
+ if (matrix != null) {
+ ShadowMatrix shadowMatrix = Shadow.extract(matrix);
+ shadowNewBitmap.appendDescription(" using matrix " + shadowMatrix.getDescription());
+
+ // Adjust width and height by using the matrix.
+ RectF mappedRect = new RectF();
+ matrix.mapRect(mappedRect, new RectF(0, 0, width, height));
+ width = Math.round(mappedRect.width());
+ height = Math.round(mappedRect.height());
+ }
+ if (filter) {
+ shadowNewBitmap.appendDescription(" with filter");
+ }
+
+ // updated if matrix is non-null
+ shadowNewBitmap.width = width;
+ shadowNewBitmap.height = height;
+ shadowNewBitmap.setMutable(true);
+ newBitmap.setDensity(src.getDensity());
+ if ((matrix == null || matrix.isIdentity()) && shadowSrcBitmap.bufferedImage != null) {
+ // Only simple cases are supported for setting image data to the new Bitmap.
+ shadowNewBitmap.bufferedImage =
+ shadowSrcBitmap.bufferedImage.getSubimage(x, y, width, height);
+ }
+ if (RuntimeEnvironment.getApiLevel() >= O) {
+ shadowNewBitmap.colorSpace = shadowSrcBitmap.colorSpace;
+ }
+ return newBitmap;
+ }
+
+ @Implementation
+ protected static Bitmap createBitmap(
+ int[] colors, int offset, int stride, int width, int height, Bitmap.Config config) {
+ return createBitmap(null, colors, offset, stride, width, height, config);
+ }
+
+ @Implementation(minSdk = JELLY_BEAN_MR1)
+ protected static Bitmap createBitmap(
+ DisplayMetrics displayMetrics,
+ int[] colors,
+ int offset,
+ int stride,
+ int width,
+ int height,
+ Bitmap.Config config) {
+ if (width <= 0) {
+ throw new IllegalArgumentException("width must be > 0");
+ }
+ if (height <= 0) {
+ throw new IllegalArgumentException("height must be > 0");
+ }
+ if (Math.abs(stride) < width) {
+ throw new IllegalArgumentException("abs(stride) must be >= width");
+ }
+ checkNotNull(config);
+ int lastScanline = offset + (height - 1) * stride;
+ int length = colors.length;
+ if (offset < 0
+ || (offset + width > length)
+ || lastScanline < 0
+ || (lastScanline + width > length)) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+
+ BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+ bufferedImage.setRGB(0, 0, width, height, colors, offset, stride);
+ Bitmap bitmap = createBitmap(bufferedImage, width, height, config);
+ ShadowLegacyBitmap shadowBitmap = Shadow.extract(bitmap);
+ shadowBitmap.setMutable(false);
+ shadowBitmap.createdFromColors = colors;
+ if (displayMetrics != null) {
+ bitmap.setDensity(displayMetrics.densityDpi);
+ }
+ if (RuntimeEnvironment.getApiLevel() >= O) {
+ shadowBitmap.colorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
+ }
+ return bitmap;
+ }
+
+ private static Bitmap createBitmap(
+ BufferedImage bufferedImage, int width, int height, Bitmap.Config config) {
+ Bitmap newBitmap = Bitmap.createBitmap(width, height, config);
+ ShadowLegacyBitmap shadowBitmap = Shadow.extract(newBitmap);
+ shadowBitmap.bufferedImage = bufferedImage;
+ return newBitmap;
+ }
+
+ @Implementation
+ protected static Bitmap createScaledBitmap(
+ Bitmap src, int dstWidth, int dstHeight, boolean filter) {
+ if (dstWidth == src.getWidth() && dstHeight == src.getHeight() && !filter) {
+ return src; // Return the original.
+ }
+ if (dstWidth <= 0 || dstHeight <= 0) {
+ throw new IllegalArgumentException("width and height must be > 0");
+ }
+ Bitmap scaledBitmap = ReflectionHelpers.callConstructor(Bitmap.class);
+ ShadowLegacyBitmap shadowBitmap = Shadow.extract(scaledBitmap);
+
+ ShadowLegacyBitmap shadowSrcBitmap = Shadow.extract(src);
+ shadowBitmap.appendDescription(shadowSrcBitmap.getDescription());
+ shadowBitmap.appendDescription(" scaled to " + dstWidth + " x " + dstHeight);
+ if (filter) {
+ shadowBitmap.appendDescription(" with filter " + filter);
+ }
+
+ shadowBitmap.createdFromBitmap = src;
+ shadowBitmap.scaledFromBitmap = src;
+ shadowBitmap.createdFromFilter = filter;
+ shadowBitmap.width = dstWidth;
+ shadowBitmap.height = dstHeight;
+ shadowBitmap.config = src.getConfig();
+ shadowBitmap.mutable = true;
+ if (!ImageUtil.scaledBitmap(src, scaledBitmap, filter)) {
+ shadowBitmap.bufferedImage =
+ new BufferedImage(dstWidth, dstHeight, BufferedImage.TYPE_INT_ARGB);
+ shadowBitmap.setPixelsInternal(
+ new int[shadowBitmap.getHeight() * shadowBitmap.getWidth()],
+ 0,
+ 0,
+ 0,
+ 0,
+ shadowBitmap.getWidth(),
+ shadowBitmap.getHeight());
+ }
+ if (RuntimeEnvironment.getApiLevel() >= O) {
+ shadowBitmap.colorSpace = shadowSrcBitmap.colorSpace;
+ }
+ return scaledBitmap;
+ }
+
+ @Implementation
+ protected static Bitmap nativeCreateFromParcel(Parcel p) {
+ int parceledWidth = p.readInt();
+ int parceledHeight = p.readInt();
+ Bitmap.Config parceledConfig = (Bitmap.Config) p.readSerializable();
+
+ int[] parceledColors = new int[parceledHeight * parceledWidth];
+ p.readIntArray(parceledColors);
+
+ return createBitmap(
+ parceledColors, 0, parceledWidth, parceledWidth, parceledHeight, parceledConfig);
+ }
+
+ static int getBytesPerPixel(Bitmap.Config config) {
+ if (config == null) {
+ throw new NullPointerException("Bitmap config was null.");
+ }
+ switch (config) {
+ case RGBA_F16:
+ return 8;
+ case ARGB_8888:
+ case HARDWARE:
+ return 4;
+ case RGB_565:
+ case ARGB_4444:
+ return 2;
+ case ALPHA_8:
+ return 1;
+ default:
+ throw new IllegalArgumentException("Unknown bitmap config: " + config);
+ }
+ }
+
+ /**
+ * Reference to original Bitmap from which this Bitmap was created. {@code null} if this Bitmap
+ * was not copied from another instance.
+ *
+ * @return Original Bitmap from which this Bitmap was created.
+ */
+ @Override
+ public Bitmap getCreatedFromBitmap() {
+ return createdFromBitmap;
+ }
+
+ /**
+ * Resource ID from which this Bitmap was created. {@code 0} if this Bitmap was not created from a
+ * resource.
+ *
+ * @return Resource ID from which this Bitmap was created.
+ */
+ @Override
+ public int getCreatedFromResId() {
+ return createdFromResId;
+ }
+
+ /**
+ * Path from which this Bitmap was created. {@code null} if this Bitmap was not create from a
+ * path.
+ *
+ * @return Path from which this Bitmap was created.
+ */
+ @Override
+ public String getCreatedFromPath() {
+ return createdFromPath;
+ }
+
+ /**
+ * {@link InputStream} from which this Bitmap was created. {@code null} if this Bitmap was not
+ * created from a stream.
+ *
+ * @return InputStream from which this Bitmap was created.
+ */
+ @Override
+ public InputStream getCreatedFromStream() {
+ return createdFromStream;
+ }
+
+ /**
+ * Bytes from which this Bitmap was created. {@code null} if this Bitmap was not created from
+ * bytes.
+ *
+ * @return Bytes from which this Bitmap was created.
+ */
+ @Override
+ public byte[] getCreatedFromBytes() {
+ return createdFromBytes;
+ }
+
+ /**
+ * Horizontal offset within {@link #getCreatedFromBitmap()} of this Bitmap's content, or -1.
+ *
+ * @return Horizontal offset within {@link #getCreatedFromBitmap()}.
+ */
+ @Override
+ public int getCreatedFromX() {
+ return createdFromX;
+ }
+
+ /**
+ * Vertical offset within {@link #getCreatedFromBitmap()} of this Bitmap's content, or -1.
+ *
+ * @return Vertical offset within {@link #getCreatedFromBitmap()} of this Bitmap's content, or -1.
+ */
+ @Override
+ public int getCreatedFromY() {
+ return createdFromY;
+ }
+
+ /**
+ * Width from {@link #getCreatedFromX()} within {@link #getCreatedFromBitmap()} of this Bitmap's
+ * content, or -1.
+ *
+ * @return Width from {@link #getCreatedFromX()} within {@link #getCreatedFromBitmap()} of this
+ * Bitmap's content, or -1.
+ */
+ @Override
+ public int getCreatedFromWidth() {
+ return createdFromWidth;
+ }
+
+ /**
+ * Height from {@link #getCreatedFromX()} within {@link #getCreatedFromBitmap()} of this Bitmap's
+ * content, or -1.
+ *
+ * @return Height from {@link #getCreatedFromX()} within {@link #getCreatedFromBitmap()} of this
+ * Bitmap's content, or -1.
+ */
+ @Override
+ public int getCreatedFromHeight() {
+ return createdFromHeight;
+ }
+
+ /**
+ * Color array from which this Bitmap was created. {@code null} if this Bitmap was not created
+ * from a color array.
+ *
+ * @return Color array from which this Bitmap was created.
+ */
+ @Override
+ public int[] getCreatedFromColors() {
+ return createdFromColors;
+ }
+
+ /**
+ * Matrix from which this Bitmap's content was transformed, or {@code null}.
+ *
+ * @return Matrix from which this Bitmap's content was transformed, or {@code null}.
+ */
+ @Override
+ public Matrix getCreatedFromMatrix() {
+ return createdFromMatrix;
+ }
+
+ /**
+ * {@code true} if this Bitmap was created with filtering.
+ *
+ * @return {@code true} if this Bitmap was created with filtering.
+ */
+ @Override
+ public boolean getCreatedFromFilter() {
+ return createdFromFilter;
+ }
+
+ @Implementation(minSdk = S)
+ protected Bitmap asShared() {
+ setMutable(false);
+ return realBitmap;
+ }
+
+ @Implementation
+ protected boolean compress(Bitmap.CompressFormat format, int quality, OutputStream stream) {
+ appendDescription(" compressed as " + format + " with quality " + quality);
+ return ImageUtil.writeToStream(realBitmap, format, quality, stream);
+ }
+
+ @Implementation
+ protected void setPixels(
+ int[] pixels, int offset, int stride, int x, int y, int width, int height) {
+ checkBitmapMutable();
+ setPixelsInternal(pixels, offset, stride, x, y, width, height);
+ }
+
+ void setPixelsInternal(
+ int[] pixels, int offset, int stride, int x, int y, int width, int height) {
+ if (bufferedImage == null) {
+ bufferedImage = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
+ }
+ bufferedImage.setRGB(x, y, width, height, pixels, offset, stride);
+ }
+
+ @Implementation
+ protected int getPixel(int x, int y) {
+ internalCheckPixelAccess(x, y);
+ if (bufferedImage != null) {
+ // Note that getPixel() returns a non-premultiplied ARGB value; if
+ // config is RGB_565, our return value will likely be more precise than
+ // on a physical device, since it needs to map each color component from
+ // 5 or 6 bits to 8 bits.
+ return bufferedImage.getRGB(x, y);
+ } else {
+ return 0;
+ }
+ }
+
+ @Implementation
+ protected void setPixel(int x, int y, int color) {
+ checkBitmapMutable();
+ internalCheckPixelAccess(x, y);
+ if (bufferedImage == null) {
+ bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+ }
+ bufferedImage.setRGB(x, y, color);
+ }
+
+ /**
+ * Note that this method will return a RuntimeException unless: - {@code pixels} has the same
+ * length as the number of pixels of the bitmap. - {@code x = 0} - {@code y = 0} - {@code width}
+ * and {@code height} height match the current bitmap's dimensions.
+ */
+ @Implementation
+ protected void getPixels(
+ int[] pixels, int offset, int stride, int x, int y, int width, int height) {
+ bufferedImage.getRGB(x, y, width, height, pixels, offset, stride);
+ }
+
+ @Implementation
+ protected int getRowBytes() {
+ return getBytesPerPixel(config) * getWidth();
+ }
+
+ @Implementation
+ protected int getByteCount() {
+ return getRowBytes() * getHeight();
+ }
+
+ @Implementation
+ protected void recycle() {
+ recycled = true;
+ }
+
+ @Implementation
+ protected final boolean isRecycled() {
+ return recycled;
+ }
+
+ @Implementation
+ protected Bitmap copy(Bitmap.Config config, boolean isMutable) {
+ Bitmap newBitmap = ReflectionHelpers.callConstructor(Bitmap.class);
+ ShadowLegacyBitmap shadowBitmap = Shadow.extract(newBitmap);
+ shadowBitmap.createdFromBitmap = realBitmap;
+ shadowBitmap.config = config;
+ shadowBitmap.mutable = isMutable;
+ shadowBitmap.height = getHeight();
+ shadowBitmap.width = getWidth();
+ if (bufferedImage != null) {
+ ColorModel cm = bufferedImage.getColorModel();
+ WritableRaster raster =
+ bufferedImage.copyData(bufferedImage.getRaster().createCompatibleWritableRaster());
+ shadowBitmap.bufferedImage = new BufferedImage(cm, raster, false, null);
+ }
+ return newBitmap;
+ }
+
+ @Implementation(minSdk = KITKAT)
+ protected final int getAllocationByteCount() {
+ return getRowBytes() * getHeight();
+ }
+
+ @Implementation
+ protected final Bitmap.Config getConfig() {
+ return config;
+ }
+
+ @Implementation(minSdk = KITKAT)
+ protected void setConfig(Bitmap.Config config) {
+ this.config = config;
+ }
+
+ @Implementation
+ protected final boolean isMutable() {
+ return mutable;
+ }
+
+ @Override
+ public void setMutable(boolean mutable) {
+ this.mutable = mutable;
+ }
+
+ @Override
+ public void appendDescription(String s) {
+ description += s;
+ }
+
+ @Override
+ public String getDescription() {
+ return description;
+ }
+
+ @Override
+ public void setDescription(String s) {
+ description = s;
+ }
+
+ @Implementation
+ protected final boolean hasAlpha() {
+ return hasAlpha && config != Bitmap.Config.RGB_565;
+ }
+
+ @Implementation
+ protected void setHasAlpha(boolean hasAlpha) {
+ this.hasAlpha = hasAlpha;
+ }
+
+ @Implementation
+ protected Bitmap extractAlpha() {
+ WritableRaster raster = bufferedImage.getAlphaRaster();
+ BufferedImage alphaImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+ alphaImage.getAlphaRaster().setRect(raster);
+ return createBitmap(alphaImage, getWidth(), getHeight(), Bitmap.Config.ALPHA_8);
+ }
+
+ /**
+ * This shadow implementation ignores the given paint and offsetXY and simply calls {@link
+ * #extractAlpha()}.
+ */
+ @Implementation
+ protected Bitmap extractAlpha(Paint paint, int[] offsetXY) {
+ return extractAlpha();
+ }
+
+ @Implementation(minSdk = JELLY_BEAN_MR1)
+ protected final boolean hasMipMap() {
+ return hasMipMap;
+ }
+
+ @Implementation(minSdk = JELLY_BEAN_MR1)
+ protected final void setHasMipMap(boolean hasMipMap) {
+ this.hasMipMap = hasMipMap;
+ }
+
+ @Implementation
+ protected int getWidth() {
+ return width;
+ }
+
+ @Implementation(minSdk = KITKAT)
+ protected void setWidth(int width) {
+ this.width = width;
+ }
+
+ @Implementation
+ protected int getHeight() {
+ return height;
+ }
+
+ @Implementation(minSdk = KITKAT)
+ protected void setHeight(int height) {
+ this.height = height;
+ }
+
+ @Implementation
+ protected int getGenerationId() {
+ return 0;
+ }
+
+ @Implementation(minSdk = M)
+ protected Bitmap createAshmemBitmap() {
+ return realBitmap;
+ }
+
+ @Implementation
+ protected void eraseColor(int color) {
+ if (bufferedImage != null) {
+ int[] pixels = ((DataBufferInt) bufferedImage.getRaster().getDataBuffer()).getData();
+ Arrays.fill(pixels, color);
+ }
+ setDescription(String.format("Bitmap (%d, %d)", width, height));
+ if (color != 0) {
+ appendDescription(String.format(" erased with 0x%08x", color));
+ }
+ }
+
+ @Implementation
+ protected void writeToParcel(Parcel p, int flags) {
+ p.writeInt(width);
+ p.writeInt(height);
+ p.writeSerializable(config);
+ int[] pixels = new int[width * height];
+ getPixels(pixels, 0, width, 0, 0, width, height);
+ p.writeIntArray(pixels);
+ }
+
+ @Implementation
+ protected void copyPixelsFromBuffer(Buffer dst) {
+ if (isRecycled()) {
+ throw new IllegalStateException("Can't call copyPixelsFromBuffer() on a recycled bitmap");
+ }
+
+ // See the related comment in #copyPixelsToBuffer(Buffer).
+ if (getBytesPerPixel(config) != INTERNAL_BYTES_PER_PIXEL) {
+ throw new RuntimeException(
+ "Not implemented: only Bitmaps with "
+ + INTERNAL_BYTES_PER_PIXEL
+ + " bytes per pixel are supported");
+ }
+ if (!(dst instanceof ByteBuffer) && !(dst instanceof IntBuffer)) {
+ throw new RuntimeException("Not implemented: unsupported Buffer subclass");
+ }
+
+ ByteBuffer byteBuffer = null;
+ IntBuffer intBuffer;
+ if (dst instanceof IntBuffer) {
+ intBuffer = (IntBuffer) dst;
+ } else {
+ byteBuffer = (ByteBuffer) dst;
+ intBuffer = byteBuffer.asIntBuffer();
+ }
+
+ if (intBuffer.remaining() < (width * height)) {
+ throw new RuntimeException("Buffer not large enough for pixels");
+ }
+
+ int[] colors = new int[width * height];
+ intBuffer.get(colors);
+ if (byteBuffer != null) {
+ byteBuffer.position(byteBuffer.position() + intBuffer.position() * INTERNAL_BYTES_PER_PIXEL);
+ }
+ int[] pixels = ((DataBufferInt) bufferedImage.getRaster().getDataBuffer()).getData();
+ System.arraycopy(colors, 0, pixels, 0, pixels.length);
+ }
+
+ @Implementation
+ protected void copyPixelsToBuffer(Buffer dst) {
+ // Ensure that the Bitmap uses 4 bytes per pixel, since we always use 4 bytes per pixels
+ // internally. Clients of this API probably expect that the buffer size must be >=
+ // getByteCount(), but if we don't enforce this restriction then for RGB_4444 and other
+ // configs that value would be smaller then the buffer size we actually need.
+ if (getBytesPerPixel(config) != INTERNAL_BYTES_PER_PIXEL) {
+ throw new RuntimeException(
+ "Not implemented: only Bitmaps with "
+ + INTERNAL_BYTES_PER_PIXEL
+ + " bytes per pixel are supported");
+ }
+
+ if (!(dst instanceof ByteBuffer) && !(dst instanceof IntBuffer)) {
+ throw new RuntimeException("Not implemented: unsupported Buffer subclass");
+ }
+ int[] pixels = ((DataBufferInt) bufferedImage.getRaster().getDataBuffer()).getData();
+ if (dst instanceof ByteBuffer) {
+ IntBuffer intBuffer = ((ByteBuffer) dst).asIntBuffer();
+ intBuffer.put(pixels);
+ dst.position(intBuffer.position() * 4);
+ } else if (dst instanceof IntBuffer) {
+ ((IntBuffer) dst).put(pixels);
+ }
+ }
+
+ @Implementation(minSdk = KITKAT)
+ 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");
+ }
+
+ // This should throw if the resulting allocation size is greater than the initial allocation
+ // size of our Bitmap, but we don't keep track of that information reliably, so we're forced to
+ // assume that our original dimensions and config are large enough to fit the new dimensions and
+ // config
+ this.width = width;
+ this.height = height;
+ this.config = config;
+ bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+ }
+
+ @Implementation(minSdk = KITKAT)
+ protected boolean isPremultiplied() {
+ return requestPremultiplied && hasAlpha();
+ }
+
+ @Implementation(minSdk = KITKAT)
+ protected void setPremultiplied(boolean isPremultiplied) {
+ this.requestPremultiplied = isPremultiplied;
+ }
+
+ @Implementation(minSdk = O)
+ protected ColorSpace getColorSpace() {
+ return colorSpace;
+ }
+
+ @Implementation(minSdk = Q)
+ protected void setColorSpace(ColorSpace colorSpace) {
+ this.colorSpace = checkNotNull(colorSpace);
+ }
+
+ @Implementation
+ protected boolean sameAs(Bitmap other) {
+ if (other == null) {
+ return false;
+ }
+ ShadowLegacyBitmap shadowOtherBitmap = Shadow.extract(other);
+ if (this.width != shadowOtherBitmap.width || this.height != shadowOtherBitmap.height) {
+ return false;
+ }
+ if (this.config != shadowOtherBitmap.config) {
+ return false;
+ }
+
+ if (bufferedImage == null && shadowOtherBitmap.bufferedImage != null) {
+ return false;
+ } else if (bufferedImage != null && shadowOtherBitmap.bufferedImage == null) {
+ return false;
+ } else if (bufferedImage != null && shadowOtherBitmap.bufferedImage != null) {
+ int[] pixels = ((DataBufferInt) bufferedImage.getData().getDataBuffer()).getData();
+ int[] otherPixels =
+ ((DataBufferInt) shadowOtherBitmap.bufferedImage.getData().getDataBuffer()).getData();
+ if (!Arrays.equals(pixels, otherPixels)) {
+ return false;
+ }
+ }
+ // When Bitmap.createScaledBitmap is called, the colors array is cleared, so we need a basic
+ // way to detect if two scaled bitmaps are the same.
+ if (scaledFromBitmap != null && shadowOtherBitmap.scaledFromBitmap != null) {
+ return scaledFromBitmap.sameAs(shadowOtherBitmap.scaledFromBitmap);
+ }
+ return true;
+ }
+
+ void setCreatedFromResId(int resId, String description) {
+ this.createdFromResId = resId;
+ appendDescription(" for resource:" + description);
+ }
+
+ private void checkBitmapMutable() {
+ if (isRecycled()) {
+ throw new IllegalStateException("Can't call setPixel() on a recycled bitmap");
+ } else if (!isMutable()) {
+ throw new IllegalStateException("Bitmap is immutable");
+ }
+ }
+
+ private void internalCheckPixelAccess(int x, int y) {
+ if (x < 0) {
+ throw new IllegalArgumentException("x must be >= 0");
+ }
+ if (y < 0) {
+ throw new IllegalArgumentException("y must be >= 0");
+ }
+ if (x >= getWidth()) {
+ throw new IllegalArgumentException("x must be < bitmap.width()");
+ }
+ if (y >= getHeight()) {
+ throw new IllegalArgumentException("y must be < bitmap.height()");
+ }
+ }
+
+ void drawRect(Rect r, Paint paint) {
+ if (bufferedImage == null) {
+ return;
+ }
+ int[] pixels = ((DataBufferInt) bufferedImage.getRaster().getDataBuffer()).getData();
+
+ Rect toDraw =
+ new Rect(
+ max(0, r.left), max(0, r.top), min(getWidth(), r.right), min(getHeight(), r.bottom));
+ if (toDraw.left == 0 && toDraw.top == 0 && toDraw.right == getWidth()) {
+ Arrays.fill(pixels, 0, getWidth() * toDraw.bottom, paint.getColor());
+ return;
+ }
+ for (int y = toDraw.top; y < toDraw.bottom; y++) {
+ Arrays.fill(
+ pixels, y * getWidth() + toDraw.left, y * getWidth() + toDraw.right, paint.getColor());
+ }
+ }
+
+ void drawRect(RectF r, Paint paint) {
+ if (bufferedImage == null) {
+ return;
+ }
+
+ Graphics2D graphics2D = bufferedImage.createGraphics();
+ Rectangle2D r2d = new Rectangle2D.Float(r.left, r.top, r.right - r.left, r.bottom - r.top);
+ graphics2D.setColor(new Color(paint.getColor()));
+ graphics2D.draw(r2d);
+ graphics2D.dispose();
+ }
+
+ void drawBitmap(Bitmap source, int left, int top) {
+ ShadowLegacyBitmap shadowSource = Shadow.extract(source);
+ if (bufferedImage == null || shadowSource.bufferedImage == null) {
+ // pixel data not available, so there's nothing we can do
+ return;
+ }
+
+ int[] pixels = ((DataBufferInt) bufferedImage.getRaster().getDataBuffer()).getData();
+ int[] sourcePixels =
+ ((DataBufferInt) shadowSource.bufferedImage.getRaster().getDataBuffer()).getData();
+
+ // fast path
+ if (left == 0 && top == 0 && getWidth() == source.getWidth()) {
+ int size = min(getWidth() * getHeight(), source.getWidth() * source.getHeight());
+ System.arraycopy(sourcePixels, 0, pixels, 0, size);
+ return;
+ }
+ // slower (row-by-row) path
+ int startSourceY = max(0, -top);
+ int startSourceX = max(0, -left);
+ int startY = max(0, top);
+ int startX = max(0, left);
+ int endY = min(getHeight(), top + source.getHeight());
+ int endX = min(getWidth(), left + source.getWidth());
+ int lenY = endY - startY;
+ int lenX = endX - startX;
+ for (int y = 0; y < lenY; y++) {
+ System.arraycopy(
+ sourcePixels,
+ (startSourceY + y) * source.getWidth() + startSourceX,
+ pixels,
+ (startY + y) * getWidth() + startX,
+ lenX);
+ }
+ }
+
+ BufferedImage getBufferedImage() {
+ return bufferedImage;
+ }
+
+ void setBufferedImage(BufferedImage bufferedImage) {
+ this.bufferedImage = bufferedImage;
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyCanvas.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyCanvas.java
new file mode 100644
index 000000000..9c63ed3d4
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyCanvas.java
@@ -0,0 +1,642 @@
+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;
+import static android.os.Build.VERSION_CODES.M;
+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;
+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.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import com.google.common.base.Preconditions;
+import java.util.ArrayList;
+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.ReflectorObject;
+import org.robolectric.annotation.Resetter;
+import org.robolectric.res.android.NativeObjRegistry;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.util.ReflectionHelpers;
+import org.robolectric.util.reflector.Direct;
+import org.robolectric.util.reflector.ForType;
+
+/**
+ * Broken. This implementation is very specific to the application for which it was developed. Todo:
+ * Reimplement. Consider using the same strategy of collecting a history of draw events and
+ * providing methods for writing queries based on type, number, and order of events.
+ */
+@SuppressWarnings({"UnusedDeclaration"})
+@Implements(value = Canvas.class, isInAndroidSdk = false)
+public class ShadowLegacyCanvas extends ShadowCanvas {
+ private static final NativeObjRegistry<NativeCanvas> nativeObjectRegistry =
+ new NativeObjRegistry<>(NativeCanvas.class);
+
+ @RealObject protected Canvas realCanvas;
+ @ReflectorObject protected CanvasReflector canvasReflector;
+
+ private final List<RoundRectPaintHistoryEvent> roundRectPaintEvents = new ArrayList<>();
+ private List<PathPaintHistoryEvent> pathPaintEvents = new ArrayList<>();
+ private List<CirclePaintHistoryEvent> circlePaintEvents = new ArrayList<>();
+ private List<ArcPaintHistoryEvent> arcPaintEvents = new ArrayList<>();
+ private List<RectPaintHistoryEvent> rectPaintEvents = new ArrayList<>();
+ private List<LinePaintHistoryEvent> linePaintEvents = new ArrayList<>();
+ private List<OvalPaintHistoryEvent> ovalPaintEvents = new ArrayList<>();
+ private List<TextHistoryEvent> drawnTextEventHistory = new ArrayList<>();
+ private Paint drawnPaint;
+ private Bitmap targetBitmap = ReflectionHelpers.callConstructor(Bitmap.class);
+ private float translateX;
+ private float translateY;
+ private float scaleX = 1;
+ private float scaleY = 1;
+ private int height;
+ private int width;
+
+ @Implementation
+ protected void __constructor__(Bitmap bitmap) {
+ canvasReflector.__constructor__(bitmap);
+ this.targetBitmap = bitmap;
+ }
+
+ private long getNativeId() {
+ return RuntimeEnvironment.getApiLevel() <= KITKAT_WATCH
+ ? (int) ReflectionHelpers.getField(realCanvas, "mNativeCanvas")
+ : realCanvas.getNativeCanvasWrapper();
+ }
+
+ private NativeCanvas getNativeCanvas() {
+ return nativeObjectRegistry.getNativeObject(getNativeId());
+ }
+
+ @Override
+ public void appendDescription(String s) {
+ ShadowBitmap shadowBitmap = Shadow.extract(targetBitmap);
+ shadowBitmap.appendDescription(s);
+ }
+
+ @Override
+ public String getDescription() {
+ ShadowBitmap shadowBitmap = Shadow.extract(targetBitmap);
+ return shadowBitmap.getDescription();
+ }
+
+ @Implementation
+ protected void setBitmap(Bitmap bitmap) {
+ targetBitmap = bitmap;
+ }
+
+ @Implementation
+ protected void drawText(String text, float x, float y, Paint paint) {
+ drawnTextEventHistory.add(new TextHistoryEvent(x, y, paint, text));
+ }
+
+ @Implementation
+ protected void drawText(CharSequence text, int start, int end, float x, float y, Paint paint) {
+ drawnTextEventHistory.add(
+ new TextHistoryEvent(x, y, paint, text.subSequence(start, end).toString()));
+ }
+
+ @Implementation
+ protected void drawText(char[] text, int index, int count, float x, float y, Paint paint) {
+ drawnTextEventHistory.add(new TextHistoryEvent(x, y, paint, new String(text, index, count)));
+ }
+
+ @Implementation
+ protected void drawText(String text, int start, int end, float x, float y, Paint paint) {
+ drawnTextEventHistory.add(new TextHistoryEvent(x, y, paint, text.substring(start, end)));
+ }
+
+ @Implementation
+ protected void translate(float x, float y) {
+ this.translateX = x;
+ this.translateY = y;
+ }
+
+ @Implementation
+ protected void scale(float sx, float sy) {
+ this.scaleX = sx;
+ this.scaleY = sy;
+ }
+
+ @Implementation
+ protected void scale(float sx, float sy, float px, float py) {
+ this.scaleX = sx;
+ this.scaleY = sy;
+ }
+
+ @Implementation
+ protected void drawPaint(Paint paint) {
+ drawnPaint = paint;
+ }
+
+ @Implementation
+ protected void drawColor(int color) {
+ appendDescription("draw color " + color);
+ }
+
+ @Implementation
+ protected void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) {
+ describeBitmap(bitmap, paint);
+
+ int x = (int) (left + translateX);
+ int y = (int) (top + translateY);
+ if (x != 0 || y != 0) {
+ appendDescription(" at (" + x + "," + y + ")");
+ }
+
+ if (scaleX != 1 && scaleY != 1) {
+ appendDescription(" scaled by (" + scaleX + "," + scaleY + ")");
+ }
+
+ if (bitmap != null && targetBitmap != null) {
+ ShadowLegacyBitmap shadowTargetBitmap = Shadow.extract(targetBitmap);
+ shadowTargetBitmap.drawBitmap(bitmap, (int) left, (int) top);
+ }
+ }
+
+ @Implementation
+ protected void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) {
+ describeBitmap(bitmap, paint);
+
+ StringBuilder descriptionBuilder = new StringBuilder();
+ if (dst != null) {
+ descriptionBuilder
+ .append(" at (")
+ .append(dst.left)
+ .append(",")
+ .append(dst.top)
+ .append(") with height=")
+ .append(dst.height())
+ .append(" and width=")
+ .append(dst.width());
+ }
+
+ if (src != null) {
+ descriptionBuilder.append(" taken from ").append(src.toString());
+ }
+ appendDescription(descriptionBuilder.toString());
+ }
+
+ @Implementation
+ protected void drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint) {
+ describeBitmap(bitmap, paint);
+
+ StringBuilder descriptionBuilder = new StringBuilder();
+ if (dst != null) {
+ descriptionBuilder
+ .append(" at (")
+ .append(dst.left)
+ .append(",")
+ .append(dst.top)
+ .append(") with height=")
+ .append(dst.height())
+ .append(" and width=")
+ .append(dst.width());
+ }
+
+ if (src != null) {
+ descriptionBuilder.append(" taken from ").append(src.toString());
+ }
+ appendDescription(descriptionBuilder.toString());
+ }
+
+ @Implementation
+ protected void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) {
+ describeBitmap(bitmap, paint);
+
+ ShadowMatrix shadowMatrix = Shadow.extract(matrix);
+ appendDescription(" transformed by " + shadowMatrix.getDescription());
+ }
+
+ @Implementation
+ protected void drawPath(Path path, Paint paint) {
+ pathPaintEvents.add(new PathPaintHistoryEvent(new Path(path), new Paint(paint)));
+
+ separateLines();
+ ShadowPath shadowPath = Shadow.extract(path);
+ appendDescription("Path " + shadowPath.getPoints().toString());
+ }
+
+ @Implementation
+ protected void drawCircle(float cx, float cy, float radius, Paint paint) {
+ circlePaintEvents.add(new CirclePaintHistoryEvent(cx, cy, radius, paint));
+ }
+
+ @Implementation
+ protected void drawArc(
+ RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint) {
+ arcPaintEvents.add(new ArcPaintHistoryEvent(oval, startAngle, sweepAngle, useCenter, paint));
+ }
+
+ @Implementation
+ protected void drawRect(float left, float top, float right, float bottom, Paint paint) {
+ rectPaintEvents.add(new RectPaintHistoryEvent(left, top, right, bottom, paint));
+
+ if (targetBitmap != null) {
+ ShadowLegacyBitmap shadowTargetBitmap = Shadow.extract(targetBitmap);
+ shadowTargetBitmap.drawRect(new RectF(left, top, right, bottom), paint);
+ }
+ }
+
+ @Implementation
+ protected void drawRect(Rect r, Paint paint) {
+ rectPaintEvents.add(new RectPaintHistoryEvent(r.left, r.top, r.right, r.bottom, paint));
+
+ if (targetBitmap != null) {
+ ShadowLegacyBitmap shadowTargetBitmap = Shadow.extract(targetBitmap);
+ shadowTargetBitmap.drawRect(r, paint);
+ }
+ }
+
+ @Implementation
+ protected void drawRoundRect(RectF rect, float rx, float ry, Paint paint) {
+ roundRectPaintEvents.add(
+ new RoundRectPaintHistoryEvent(
+ rect.left, rect.top, rect.right, rect.bottom, rx, ry, paint));
+ }
+
+ @Implementation
+ protected void drawLine(float startX, float startY, float stopX, float stopY, Paint paint) {
+ linePaintEvents.add(new LinePaintHistoryEvent(startX, startY, stopX, stopY, paint));
+ }
+
+ @Implementation
+ protected void drawOval(RectF oval, Paint paint) {
+ ovalPaintEvents.add(new OvalPaintHistoryEvent(oval, paint));
+ }
+
+ private void describeBitmap(Bitmap bitmap, Paint paint) {
+ separateLines();
+
+ ShadowBitmap shadowBitmap = Shadow.extract(bitmap);
+ appendDescription(shadowBitmap.getDescription());
+
+ if (paint != null) {
+ ColorFilter colorFilter = paint.getColorFilter();
+ if (colorFilter != null) {
+ appendDescription(" with " + colorFilter.getClass().getSimpleName());
+ }
+ }
+ }
+
+ private void separateLines() {
+ if (getDescription().length() != 0) {
+ appendDescription("\n");
+ }
+ }
+
+ @Override
+ public int getPathPaintHistoryCount() {
+ return pathPaintEvents.size();
+ }
+
+ @Override
+ public int getCirclePaintHistoryCount() {
+ return circlePaintEvents.size();
+ }
+
+ @Override
+ public int getArcPaintHistoryCount() {
+ return arcPaintEvents.size();
+ }
+
+ @Override
+ public boolean hasDrawnPath() {
+ return getPathPaintHistoryCount() > 0;
+ }
+
+ @Override
+ public boolean hasDrawnCircle() {
+ return circlePaintEvents.size() > 0;
+ }
+
+ @Override
+ public Paint getDrawnPathPaint(int i) {
+ return pathPaintEvents.get(i).pathPaint;
+ }
+
+ @Override
+ public Path getDrawnPath(int i) {
+ return pathPaintEvents.get(i).drawnPath;
+ }
+
+ @Override
+ public CirclePaintHistoryEvent getDrawnCircle(int i) {
+ return circlePaintEvents.get(i);
+ }
+
+ @Override
+ public ArcPaintHistoryEvent getDrawnArc(int i) {
+ return arcPaintEvents.get(i);
+ }
+
+ @Override
+ public void resetCanvasHistory() {
+ drawnTextEventHistory.clear();
+ pathPaintEvents.clear();
+ circlePaintEvents.clear();
+ rectPaintEvents.clear();
+ roundRectPaintEvents.clear();
+ linePaintEvents.clear();
+ ovalPaintEvents.clear();
+ ShadowBitmap shadowBitmap = Shadow.extract(targetBitmap);
+ shadowBitmap.setDescription("");
+ }
+
+ @Override
+ public Paint getDrawnPaint() {
+ return drawnPaint;
+ }
+
+ @Override
+ public void setHeight(int height) {
+ this.height = height;
+ }
+
+ @Override
+ public void setWidth(int width) {
+ this.width = width;
+ }
+
+ @Implementation
+ protected int getWidth() {
+ if (width == 0) {
+ return targetBitmap.getWidth();
+ }
+ return width;
+ }
+
+ @Implementation
+ protected int getHeight() {
+ if (height == 0) {
+ return targetBitmap.getHeight();
+ }
+ return height;
+ }
+
+ @Implementation
+ protected boolean getClipBounds(Rect bounds) {
+ Preconditions.checkNotNull(bounds);
+ if (targetBitmap == null) {
+ return false;
+ }
+ bounds.set(0, 0, targetBitmap.getWidth(), targetBitmap.getHeight());
+ return !bounds.isEmpty();
+ }
+
+ @Override
+ public TextHistoryEvent getDrawnTextEvent(int i) {
+ return drawnTextEventHistory.get(i);
+ }
+
+ @Override
+ public int getTextHistoryCount() {
+ return drawnTextEventHistory.size();
+ }
+
+ @Override
+ public RectPaintHistoryEvent getDrawnRect(int i) {
+ return rectPaintEvents.get(i);
+ }
+
+ @Override
+ public RectPaintHistoryEvent getLastDrawnRect() {
+ return rectPaintEvents.get(rectPaintEvents.size() - 1);
+ }
+
+ @Override
+ public int getRectPaintHistoryCount() {
+ return rectPaintEvents.size();
+ }
+
+ @Override
+ public RoundRectPaintHistoryEvent getDrawnRoundRect(int i) {
+ return roundRectPaintEvents.get(i);
+ }
+
+ @Override
+ public RoundRectPaintHistoryEvent getLastDrawnRoundRect() {
+ return roundRectPaintEvents.get(roundRectPaintEvents.size() - 1);
+ }
+
+ @Override
+ public int getRoundRectPaintHistoryCount() {
+ return roundRectPaintEvents.size();
+ }
+
+ @Override
+ public LinePaintHistoryEvent getDrawnLine(int i) {
+ return linePaintEvents.get(i);
+ }
+
+ @Override
+ public int getLinePaintHistoryCount() {
+ return linePaintEvents.size();
+ }
+
+ @Override
+ public int getOvalPaintHistoryCount() {
+ return ovalPaintEvents.size();
+ }
+
+ @Override
+ public OvalPaintHistoryEvent getDrawnOval(int i) {
+ return ovalPaintEvents.get(i);
+ }
+
+ @Implementation(maxSdk = N_MR1)
+ protected int save() {
+ return getNativeCanvas().save();
+ }
+
+ @Implementation(maxSdk = N_MR1)
+ protected void restore() {
+ getNativeCanvas().restore();
+ }
+
+ @Implementation(maxSdk = N_MR1)
+ protected int getSaveCount() {
+ return getNativeCanvas().getSaveCount();
+ }
+
+ @Implementation(maxSdk = N_MR1)
+ protected void restoreToCount(int saveCount) {
+ getNativeCanvas().restoreToCount(saveCount);
+ }
+
+ @Implementation(minSdk = KITKAT)
+ protected void release() {
+ nativeObjectRegistry.unregister(getNativeId());
+ canvasReflector.release();
+ }
+
+ @Implementation(maxSdk = KITKAT_WATCH)
+ protected static int initRaster(int bitmapHandle) {
+ return (int) nativeObjectRegistry.register(new NativeCanvas());
+ }
+
+ @Implementation(minSdk = LOLLIPOP, maxSdk = LOLLIPOP_MR1)
+ protected static long initRaster(long bitmapHandle) {
+ return nativeObjectRegistry.register(new NativeCanvas());
+ }
+
+ @Implementation(minSdk = M, maxSdk = N_MR1)
+ protected static long initRaster(Bitmap bitmap) {
+ return nativeObjectRegistry.register(new NativeCanvas());
+ }
+
+ @Implementation(minSdk = O, maxSdk = P)
+ protected static long nInitRaster(Bitmap bitmap) {
+ return nativeObjectRegistry.register(new NativeCanvas());
+ }
+
+ @Implementation(minSdk = Q)
+ protected static long nInitRaster(long bitmapHandle) {
+ return nativeObjectRegistry.register(new NativeCanvas());
+ }
+
+ @Implementation(minSdk = O)
+ protected static int nGetSaveCount(long canvasHandle) {
+ return nativeObjectRegistry.getNativeObject(canvasHandle).getSaveCount();
+ }
+
+ @Implementation(minSdk = O)
+ protected static int nSave(long canvasHandle, int saveFlags) {
+ return nativeObjectRegistry.getNativeObject(canvasHandle).save();
+ }
+
+ @Implementation(maxSdk = KITKAT_WATCH)
+ protected static int native_saveLayer(int nativeCanvas, RectF bounds, int paint, int layerFlags) {
+ return nativeObjectRegistry.getNativeObject(nativeCanvas).save();
+ }
+
+ @Implementation(maxSdk = KITKAT_WATCH)
+ protected static int native_saveLayer(
+ int nativeCanvas, float l, float t, float r, float b, int paint, int layerFlags) {
+ return nativeObjectRegistry.getNativeObject(nativeCanvas).save();
+ }
+
+ @Implementation(minSdk = LOLLIPOP, maxSdk = N_MR1)
+ protected static int native_saveLayer(
+ long nativeCanvas, float l, float t, float r, float b, long nativePaint, int layerFlags) {
+ return nativeObjectRegistry.getNativeObject(nativeCanvas).save();
+ }
+
+ @Implementation(minSdk = O, maxSdk = R)
+ protected static int nSaveLayer(
+ long nativeCanvas, float l, float t, float r, float b, long nativePaint, int layerFlags) {
+ return nativeObjectRegistry.getNativeObject(nativeCanvas).save();
+ }
+
+ @Implementation(minSdk = S)
+ protected static int nSaveLayer(
+ long nativeCanvas, float l, float t, float r, float b, long nativePaint) {
+ return nativeObjectRegistry.getNativeObject(nativeCanvas).save();
+ }
+
+ @Implementation(maxSdk = KITKAT_WATCH)
+ protected static int native_saveLayerAlpha(
+ int nativeCanvas, RectF bounds, int alpha, int layerFlags) {
+ return nativeObjectRegistry.getNativeObject(nativeCanvas).save();
+ }
+
+ @Implementation(maxSdk = KITKAT_WATCH)
+ protected static int native_saveLayerAlpha(
+ int nativeCanvas, float l, float t, float r, float b, int alpha, int layerFlags) {
+ return nativeObjectRegistry.getNativeObject(nativeCanvas).save();
+ }
+
+ @Implementation(minSdk = LOLLIPOP, maxSdk = N_MR1)
+ protected static int native_saveLayerAlpha(
+ long nativeCanvas, float l, float t, float r, float b, int alpha, int layerFlags) {
+ return nativeObjectRegistry.getNativeObject(nativeCanvas).save();
+ }
+
+ @Implementation(minSdk = O, maxSdk = R)
+ protected static int nSaveLayerAlpha(
+ long nativeCanvas, float l, float t, float r, float b, int alpha, int layerFlags) {
+ return nativeObjectRegistry.getNativeObject(nativeCanvas).save();
+ }
+
+ @Implementation(minSdk = S)
+ protected static int nSaveLayerAlpha(
+ long nativeCanvas, float l, float t, float r, float b, int alpha) {
+ return nativeObjectRegistry.getNativeObject(nativeCanvas).save();
+ }
+
+ @Implementation(minSdk = O)
+ protected static boolean nRestore(long canvasHandle) {
+ return nativeObjectRegistry.getNativeObject(canvasHandle).restore();
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nRestoreToCount(long canvasHandle, int saveCount) {
+ nativeObjectRegistry.getNativeObject(canvasHandle).restoreToCount(saveCount);
+ }
+
+ private static class PathPaintHistoryEvent {
+ private final Path drawnPath;
+ private final Paint pathPaint;
+
+ PathPaintHistoryEvent(Path drawnPath, Paint pathPaint) {
+ this.drawnPath = drawnPath;
+ this.pathPaint = pathPaint;
+ }
+ }
+
+ @Resetter
+ public static void reset() {
+ nativeObjectRegistry.clear();
+ }
+
+ @SuppressWarnings("MemberName")
+ @ForType(Canvas.class)
+ private interface CanvasReflector {
+ @Direct
+ void __constructor__(Bitmap bitmap);
+
+ @Direct
+ void release();
+ }
+
+ private static class NativeCanvas {
+ private int saveCount = 1;
+
+ int save() {
+ return saveCount++;
+ }
+
+ boolean restore() {
+ if (saveCount > 1) {
+ saveCount--;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ int getSaveCount() {
+ return saveCount;
+ }
+
+ void restoreToCount(int saveCount) {
+ if (saveCount > 0) {
+ this.saveCount = saveCount;
+ }
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyMatrix.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyMatrix.java
new file mode 100644
index 000000000..a85af0c41
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyMatrix.java
@@ -0,0 +1,662 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.KITKAT;
+import static android.os.Build.VERSION_CODES.LOLLIPOP;
+
+import android.graphics.Matrix;
+import android.graphics.Matrix.ScaleToFit;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import java.awt.geom.AffineTransform;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadow.api.Shadow;
+
+@SuppressWarnings({"UnusedDeclaration"})
+@Implements(value = Matrix.class, isInAndroidSdk = false)
+public class ShadowLegacyMatrix extends ShadowMatrix {
+
+ private static final float EPSILON = 1e-3f;
+
+ private final Deque<String> preOps = new ArrayDeque<>();
+ private final Deque<String> postOps = new ArrayDeque<>();
+ private final Map<String, String> setOps = new LinkedHashMap<>();
+
+ private SimpleMatrix simpleMatrix = SimpleMatrix.newIdentityMatrix();
+
+ @Implementation
+ protected void __constructor__(Matrix src) {
+ set(src);
+ }
+
+ /**
+ * A list of all 'pre' operations performed on this Matrix. The last operation performed will be
+ * first in the list.
+ *
+ * @return A list of all 'pre' operations performed on this Matrix.
+ */
+ @Override
+ public List<String> getPreOperations() {
+ return Collections.unmodifiableList(new ArrayList<>(preOps));
+ }
+
+ /**
+ * A list of all 'post' operations performed on this Matrix. The last operation performed will be
+ * last in the list.
+ *
+ * @return A list of all 'post' operations performed on this Matrix.
+ */
+ @Override
+ public List<String> getPostOperations() {
+ return Collections.unmodifiableList(new ArrayList<>(postOps));
+ }
+
+ /**
+ * A map of all 'set' operations performed on this Matrix.
+ *
+ * @return A map of all 'set' operations performed on this Matrix.
+ */
+ @Override
+ public Map<String, String> getSetOperations() {
+ return Collections.unmodifiableMap(new LinkedHashMap<>(setOps));
+ }
+
+ @Implementation
+ protected boolean isIdentity() {
+ return simpleMatrix.equals(SimpleMatrix.IDENTITY);
+ }
+
+ @Implementation(minSdk = LOLLIPOP)
+ protected boolean isAffine() {
+ return simpleMatrix.isAffine();
+ }
+
+ @Implementation
+ protected boolean rectStaysRect() {
+ return simpleMatrix.rectStaysRect();
+ }
+
+ @Implementation
+ protected void getValues(float[] values) {
+ simpleMatrix.getValues(values);
+ }
+
+ @Implementation
+ protected void setValues(float[] values) {
+ simpleMatrix = new SimpleMatrix(values);
+ }
+
+ @Implementation
+ protected void set(Matrix src) {
+ reset();
+ if (src != null) {
+ ShadowLegacyMatrix shadowMatrix = Shadow.extract(src);
+ preOps.addAll(shadowMatrix.preOps);
+ postOps.addAll(shadowMatrix.postOps);
+ setOps.putAll(shadowMatrix.setOps);
+ simpleMatrix = new SimpleMatrix(getSimpleMatrix(src));
+ }
+ }
+
+ @Implementation
+ protected void reset() {
+ preOps.clear();
+ postOps.clear();
+ setOps.clear();
+ simpleMatrix = SimpleMatrix.newIdentityMatrix();
+ }
+
+ @Implementation
+ protected void setTranslate(float dx, float dy) {
+ setOps.put(TRANSLATE, dx + " " + dy);
+ simpleMatrix = SimpleMatrix.translate(dx, dy);
+ }
+
+ @Implementation
+ protected void setScale(float sx, float sy, float px, float py) {
+ setOps.put(SCALE, sx + " " + sy + " " + px + " " + py);
+ simpleMatrix = SimpleMatrix.scale(sx, sy, px, py);
+ }
+
+ @Implementation
+ protected void setScale(float sx, float sy) {
+ setOps.put(SCALE, sx + " " + sy);
+ simpleMatrix = SimpleMatrix.scale(sx, sy);
+ }
+
+ @Implementation
+ protected void setRotate(float degrees, float px, float py) {
+ setOps.put(ROTATE, degrees + " " + px + " " + py);
+ simpleMatrix = SimpleMatrix.rotate(degrees, px, py);
+ }
+
+ @Implementation
+ protected void setRotate(float degrees) {
+ setOps.put(ROTATE, Float.toString(degrees));
+ simpleMatrix = SimpleMatrix.rotate(degrees);
+ }
+
+ @Implementation
+ protected void setSinCos(float sinValue, float cosValue, float px, float py) {
+ setOps.put(SINCOS, sinValue + " " + cosValue + " " + px + " " + py);
+ simpleMatrix = SimpleMatrix.sinCos(sinValue, cosValue, px, py);
+ }
+
+ @Implementation
+ protected void setSinCos(float sinValue, float cosValue) {
+ setOps.put(SINCOS, sinValue + " " + cosValue);
+ simpleMatrix = SimpleMatrix.sinCos(sinValue, cosValue);
+ }
+
+ @Implementation
+ protected void setSkew(float kx, float ky, float px, float py) {
+ setOps.put(SKEW, kx + " " + ky + " " + px + " " + py);
+ simpleMatrix = SimpleMatrix.skew(kx, ky, px, py);
+ }
+
+ @Implementation
+ protected void setSkew(float kx, float ky) {
+ setOps.put(SKEW, kx + " " + ky);
+ simpleMatrix = SimpleMatrix.skew(kx, ky);
+ }
+
+ @Implementation
+ protected boolean setConcat(Matrix a, Matrix b) {
+ simpleMatrix = getSimpleMatrix(a).multiply(getSimpleMatrix(b));
+ return true;
+ }
+
+ @Implementation
+ protected boolean preTranslate(float dx, float dy) {
+ preOps.addFirst(TRANSLATE + " " + dx + " " + dy);
+ return preConcat(SimpleMatrix.translate(dx, dy));
+ }
+
+ @Implementation
+ protected boolean preScale(float sx, float sy, float px, float py) {
+ preOps.addFirst(SCALE + " " + sx + " " + sy + " " + px + " " + py);
+ return preConcat(SimpleMatrix.scale(sx, sy, px, py));
+ }
+
+ @Implementation
+ protected boolean preScale(float sx, float sy) {
+ preOps.addFirst(SCALE + " " + sx + " " + sy);
+ return preConcat(SimpleMatrix.scale(sx, sy));
+ }
+
+ @Implementation
+ protected boolean preRotate(float degrees, float px, float py) {
+ preOps.addFirst(ROTATE + " " + degrees + " " + px + " " + py);
+ return preConcat(SimpleMatrix.rotate(degrees, px, py));
+ }
+
+ @Implementation
+ protected boolean preRotate(float degrees) {
+ preOps.addFirst(ROTATE + " " + Float.toString(degrees));
+ return preConcat(SimpleMatrix.rotate(degrees));
+ }
+
+ @Implementation
+ protected boolean preSkew(float kx, float ky, float px, float py) {
+ preOps.addFirst(SKEW + " " + kx + " " + ky + " " + px + " " + py);
+ return preConcat(SimpleMatrix.skew(kx, ky, px, py));
+ }
+
+ @Implementation
+ protected boolean preSkew(float kx, float ky) {
+ preOps.addFirst(SKEW + " " + kx + " " + ky);
+ return preConcat(SimpleMatrix.skew(kx, ky));
+ }
+
+ @Implementation
+ protected boolean preConcat(Matrix other) {
+ preOps.addFirst(MATRIX + " " + other);
+ return preConcat(getSimpleMatrix(other));
+ }
+
+ @Implementation
+ protected boolean postTranslate(float dx, float dy) {
+ postOps.addLast(TRANSLATE + " " + dx + " " + dy);
+ return postConcat(SimpleMatrix.translate(dx, dy));
+ }
+
+ @Implementation
+ protected boolean postScale(float sx, float sy, float px, float py) {
+ postOps.addLast(SCALE + " " + sx + " " + sy + " " + px + " " + py);
+ return postConcat(SimpleMatrix.scale(sx, sy, px, py));
+ }
+
+ @Implementation
+ protected boolean postScale(float sx, float sy) {
+ postOps.addLast(SCALE + " " + sx + " " + sy);
+ return postConcat(SimpleMatrix.scale(sx, sy));
+ }
+
+ @Implementation
+ protected boolean postRotate(float degrees, float px, float py) {
+ postOps.addLast(ROTATE + " " + degrees + " " + px + " " + py);
+ return postConcat(SimpleMatrix.rotate(degrees, px, py));
+ }
+
+ @Implementation
+ protected boolean postRotate(float degrees) {
+ postOps.addLast(ROTATE + " " + Float.toString(degrees));
+ return postConcat(SimpleMatrix.rotate(degrees));
+ }
+
+ @Implementation
+ protected boolean postSkew(float kx, float ky, float px, float py) {
+ postOps.addLast(SKEW + " " + kx + " " + ky + " " + px + " " + py);
+ return postConcat(SimpleMatrix.skew(kx, ky, px, py));
+ }
+
+ @Implementation
+ protected boolean postSkew(float kx, float ky) {
+ postOps.addLast(SKEW + " " + kx + " " + ky);
+ return postConcat(SimpleMatrix.skew(kx, ky));
+ }
+
+ @Implementation
+ protected boolean postConcat(Matrix other) {
+ postOps.addLast(MATRIX + " " + other);
+ return postConcat(getSimpleMatrix(other));
+ }
+
+ @Implementation
+ protected boolean invert(Matrix inverse) {
+ final SimpleMatrix inverseMatrix = simpleMatrix.invert();
+ if (inverseMatrix != null) {
+ if (inverse != null) {
+ final ShadowLegacyMatrix shadowInverse = Shadow.extract(inverse);
+ shadowInverse.simpleMatrix = inverseMatrix;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ boolean hasPerspective() {
+ return (simpleMatrix.mValues[6] != 0 || simpleMatrix.mValues[7] != 0 || simpleMatrix.mValues[8] != 1);
+ }
+
+ protected AffineTransform getAffineTransform() {
+ // the AffineTransform constructor takes the value in a different order
+ // for a matrix [ 0 1 2 ]
+ // [ 3 4 5 ]
+ // the order is 0, 3, 1, 4, 2, 5...
+ return new AffineTransform(
+ simpleMatrix.mValues[0],
+ simpleMatrix.mValues[3],
+ simpleMatrix.mValues[1],
+ simpleMatrix.mValues[4],
+ simpleMatrix.mValues[2],
+ simpleMatrix.mValues[5]);
+ }
+
+ public PointF mapPoint(float x, float y) {
+ return simpleMatrix.transform(new PointF(x, y));
+ }
+
+ public PointF mapPoint(PointF point) {
+ return simpleMatrix.transform(point);
+ }
+
+ @Implementation
+ protected boolean mapRect(RectF destination, RectF source) {
+ final PointF leftTop = mapPoint(source.left, source.top);
+ final PointF rightBottom = mapPoint(source.right, source.bottom);
+ destination.set(
+ Math.min(leftTop.x, rightBottom.x),
+ Math.min(leftTop.y, rightBottom.y),
+ Math.max(leftTop.x, rightBottom.x),
+ Math.max(leftTop.y, rightBottom.y));
+ return true;
+ }
+
+ @Implementation
+ protected void mapPoints(float[] dst, int dstIndex, float[] src, int srcIndex, int pointCount) {
+ for (int i = 0; i < pointCount; i++) {
+ final PointF mapped = mapPoint(src[srcIndex + i * 2], src[srcIndex + i * 2 + 1]);
+ dst[dstIndex + i * 2] = mapped.x;
+ dst[dstIndex + i * 2 + 1] = mapped.y;
+ }
+ }
+
+ @Implementation
+ protected void mapVectors(float[] dst, int dstIndex, float[] src, int srcIndex, int vectorCount) {
+ final float transX = simpleMatrix.mValues[Matrix.MTRANS_X];
+ final float transY = simpleMatrix.mValues[Matrix.MTRANS_Y];
+
+ simpleMatrix.mValues[Matrix.MTRANS_X] = 0;
+ simpleMatrix.mValues[Matrix.MTRANS_Y] = 0;
+
+ for (int i = 0; i < vectorCount; i++) {
+ final PointF mapped = mapPoint(src[srcIndex + i * 2], src[srcIndex + i * 2 + 1]);
+ dst[dstIndex + i * 2] = mapped.x;
+ dst[dstIndex + i * 2 + 1] = mapped.y;
+ }
+
+ simpleMatrix.mValues[Matrix.MTRANS_X] = transX;
+ simpleMatrix.mValues[Matrix.MTRANS_Y] = transY;
+ }
+
+ @Implementation
+ protected float mapRadius(float radius) {
+ float[] src = new float[] {radius, 0.f, 0.f, radius};
+ mapVectors(src, 0, src, 0, 2);
+
+ float l1 = (float) Math.hypot(src[0], src[1]);
+ float l2 = (float) Math.hypot(src[2], src[3]);
+ return (float) Math.sqrt(l1 * l2);
+ }
+
+ @Implementation
+ protected boolean setRectToRect(RectF src, RectF dst, Matrix.ScaleToFit stf) {
+ if (src.isEmpty()) {
+ reset();
+ return false;
+ }
+ return simpleMatrix.setRectToRect(src, dst, stf);
+ }
+
+ @Implementation
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof Matrix) {
+ return getSimpleMatrix(((Matrix) obj)).equals(simpleMatrix);
+ } else {
+ return obj instanceof ShadowMatrix && obj.equals(simpleMatrix);
+ }
+ }
+
+ @Implementation(minSdk = KITKAT)
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(simpleMatrix);
+ }
+
+ @Override
+ public String getDescription() {
+ return "Matrix[pre=" + preOps + ", set=" + setOps + ", post=" + postOps + "]";
+ }
+
+ private static SimpleMatrix getSimpleMatrix(Matrix matrix) {
+ final ShadowLegacyMatrix otherMatrix = Shadow.extract(matrix);
+ return otherMatrix.simpleMatrix;
+ }
+
+ private boolean postConcat(SimpleMatrix matrix) {
+ simpleMatrix = matrix.multiply(simpleMatrix);
+ return true;
+ }
+
+ private boolean preConcat(SimpleMatrix matrix) {
+ simpleMatrix = simpleMatrix.multiply(matrix);
+ return true;
+ }
+
+ /**
+ * A simple implementation of an immutable matrix.
+ */
+ private static class SimpleMatrix {
+ private static final SimpleMatrix IDENTITY = newIdentityMatrix();
+
+ private static SimpleMatrix newIdentityMatrix() {
+ return new SimpleMatrix(
+ new float[] {
+ 1.0f, 0.0f, 0.0f,
+ 0.0f, 1.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f,
+ });
+ }
+
+ private final float[] mValues;
+
+ SimpleMatrix(SimpleMatrix matrix) {
+ mValues = Arrays.copyOf(matrix.mValues, matrix.mValues.length);
+ }
+
+ private SimpleMatrix(float[] values) {
+ if (values.length != 9) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ mValues = Arrays.copyOf(values, 9);
+ }
+
+ public boolean isAffine() {
+ return mValues[6] == 0.0f && mValues[7] == 0.0f && mValues[8] == 1.0f;
+ }
+
+ public boolean rectStaysRect() {
+ final float m00 = mValues[0];
+ final float m01 = mValues[1];
+ final float m10 = mValues[3];
+ final float m11 = mValues[4];
+ return (m00 == 0 && m11 == 0 && m01 != 0 && m10 != 0) || (m00 != 0 && m11 != 0 && m01 == 0 && m10 == 0);
+ }
+
+ public void getValues(float[] values) {
+ if (values.length < 9) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ System.arraycopy(mValues, 0, values, 0, 9);
+ }
+
+ public static SimpleMatrix translate(float dx, float dy) {
+ return new SimpleMatrix(new float[] {
+ 1.0f, 0.0f, dx,
+ 0.0f, 1.0f, dy,
+ 0.0f, 0.0f, 1.0f,
+ });
+ }
+
+ public static SimpleMatrix scale(float sx, float sy, float px, float py) {
+ return new SimpleMatrix(new float[] {
+ sx, 0.0f, px * (1 - sx),
+ 0.0f, sy, py * (1 - sy),
+ 0.0f, 0.0f, 1.0f,
+ });
+ }
+
+ public static SimpleMatrix scale(float sx, float sy) {
+ return new SimpleMatrix(new float[] {
+ sx, 0.0f, 0.0f,
+ 0.0f, sy, 0.0f,
+ 0.0f, 0.0f, 1.0f,
+ });
+ }
+
+ public static SimpleMatrix rotate(float degrees, float px, float py) {
+ final double radians = Math.toRadians(degrees);
+ final float sin = (float) Math.sin(radians);
+ final float cos = (float) Math.cos(radians);
+ return sinCos(sin, cos, px, py);
+ }
+
+ public static SimpleMatrix rotate(float degrees) {
+ final double radians = Math.toRadians(degrees);
+ final float sin = (float) Math.sin(radians);
+ final float cos = (float) Math.cos(radians);
+ return sinCos(sin, cos);
+ }
+
+ public static SimpleMatrix sinCos(float sin, float cos, float px, float py) {
+ return new SimpleMatrix(new float[] {
+ cos, -sin, sin * py + (1 - cos) * px,
+ sin, cos, -sin * px + (1 - cos) * py,
+ 0.0f, 0.0f, 1.0f,
+ });
+ }
+
+ public static SimpleMatrix sinCos(float sin, float cos) {
+ return new SimpleMatrix(new float[] {
+ cos, -sin, 0.0f,
+ sin, cos, 0.0f,
+ 0.0f, 0.0f, 1.0f,
+ });
+ }
+
+ public static SimpleMatrix skew(float kx, float ky, float px, float py) {
+ return new SimpleMatrix(new float[] {
+ 1.0f, kx, -kx * py,
+ ky, 1.0f, -ky * px,
+ 0.0f, 0.0f, 1.0f,
+ });
+ }
+
+ public static SimpleMatrix skew(float kx, float ky) {
+ return new SimpleMatrix(new float[] {
+ 1.0f, kx, 0.0f,
+ ky, 1.0f, 0.0f,
+ 0.0f, 0.0f, 1.0f,
+ });
+ }
+
+ public SimpleMatrix multiply(SimpleMatrix matrix) {
+ final float[] values = new float[9];
+ for (int i = 0; i < values.length; ++i) {
+ final int row = i / 3;
+ final int col = i % 3;
+ for (int j = 0; j < 3; ++j) {
+ values[i] += mValues[row * 3 + j] * matrix.mValues[j * 3 + col];
+ }
+ }
+ return new SimpleMatrix(values);
+ }
+
+ public SimpleMatrix invert() {
+ final float invDet = inverseDeterminant();
+ if (invDet == 0) {
+ return null;
+ }
+
+ final float[] src = mValues;
+ final float[] dst = new float[9];
+ dst[0] = cross_scale(src[4], src[8], src[5], src[7], invDet);
+ dst[1] = cross_scale(src[2], src[7], src[1], src[8], invDet);
+ dst[2] = cross_scale(src[1], src[5], src[2], src[4], invDet);
+
+ dst[3] = cross_scale(src[5], src[6], src[3], src[8], invDet);
+ dst[4] = cross_scale(src[0], src[8], src[2], src[6], invDet);
+ dst[5] = cross_scale(src[2], src[3], src[0], src[5], invDet);
+
+ dst[6] = cross_scale(src[3], src[7], src[4], src[6], invDet);
+ dst[7] = cross_scale(src[1], src[6], src[0], src[7], invDet);
+ dst[8] = cross_scale(src[0], src[4], src[1], src[3], invDet);
+ return new SimpleMatrix(dst);
+ }
+
+ public PointF transform(PointF point) {
+ return new PointF(
+ point.x * mValues[0] + point.y * mValues[1] + mValues[2],
+ point.x * mValues[3] + point.y * mValues[4] + mValues[5]);
+ }
+
+ // See: https://android.googlesource.com/platform/frameworks/base/+/6fca81de9b2079ec88e785f58bf49bf1f0c105e2/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java
+ protected boolean setRectToRect(RectF src, RectF dst, ScaleToFit stf) {
+ if (dst.isEmpty()) {
+ mValues[0] =
+ mValues[1] =
+ mValues[2] = mValues[3] = mValues[4] = mValues[5] = mValues[6] = mValues[7] = 0;
+ mValues[8] = 1;
+ } else {
+ float tx = dst.width() / src.width();
+ float sx = dst.width() / src.width();
+ float ty = dst.height() / src.height();
+ float sy = dst.height() / src.height();
+ boolean xLarger = false;
+
+ if (stf != ScaleToFit.FILL) {
+ if (sx > sy) {
+ xLarger = true;
+ sx = sy;
+ } else {
+ sy = sx;
+ }
+ }
+
+ tx = dst.left - src.left * sx;
+ ty = dst.top - src.top * sy;
+ if (stf == ScaleToFit.CENTER || stf == ScaleToFit.END) {
+ float diff;
+
+ if (xLarger) {
+ diff = dst.width() - src.width() * sy;
+ } else {
+ diff = dst.height() - src.height() * sy;
+ }
+
+ if (stf == ScaleToFit.CENTER) {
+ diff = diff / 2;
+ }
+
+ if (xLarger) {
+ tx += diff;
+ } else {
+ ty += diff;
+ }
+ }
+
+ mValues[0] = sx;
+ mValues[4] = sy;
+ mValues[2] = tx;
+ mValues[5] = ty;
+ mValues[1] = mValues[3] = mValues[6] = mValues[7] = 0;
+ }
+ // shared cleanup
+ mValues[8] = 1;
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return this == o || (o instanceof SimpleMatrix && equals((SimpleMatrix) o));
+ }
+
+ @SuppressWarnings("NonOverridingEquals")
+ public boolean equals(SimpleMatrix matrix) {
+ if (matrix == null) {
+ return false;
+ }
+ for (int i = 0; i < mValues.length; i++) {
+ if (!isNearlyZero(matrix.mValues[i] - mValues[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(mValues);
+ }
+
+ private static boolean isNearlyZero(float value) {
+ return Math.abs(value) < EPSILON;
+ }
+
+ private static float cross(float a, float b, float c, float d) {
+ return a * b - c * d;
+ }
+
+ private static float cross_scale(float a, float b, float c, float d, float scale) {
+ return cross(a, b, c, d) * scale;
+ }
+
+ private float inverseDeterminant() {
+ final float determinant = mValues[0] * cross(mValues[4], mValues[8], mValues[5], mValues[7]) +
+ mValues[1] * cross(mValues[5], mValues[6], mValues[3], mValues[8]) +
+ mValues[2] * cross(mValues[3], mValues[7], mValues[4], mValues[6]);
+ return isNearlyZero(determinant) ? 0.0f : 1.0f / determinant;
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyPath.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyPath.java
new file mode 100644
index 000000000..b4f113a4a
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyPath.java
@@ -0,0 +1,558 @@
+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;
+import static org.robolectric.shadows.ShadowPath.Point.Type.MOVE_TO;
+
+import android.graphics.Matrix;
+import android.graphics.Path;
+import android.graphics.Path.Direction;
+import android.graphics.RectF;
+import android.util.Log;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Arc2D;
+import java.awt.geom.Area;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Path2D;
+import java.awt.geom.PathIterator;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RoundRectangle2D;
+import java.util.ArrayList;
+import java.util.List;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.RealObject;
+
+/** The shadow only supports straight-line paths. */
+@SuppressWarnings({"UnusedDeclaration"})
+@Implements(value = Path.class, isInAndroidSdk = false)
+public class ShadowLegacyPath extends ShadowPath {
+ private static final String TAG = ShadowLegacyPath.class.getSimpleName();
+ private static final float EPSILON = 1e-4f;
+
+ @RealObject private Path realObject;
+
+ private List<Point> points = new ArrayList<>();
+
+ private float mLastX = 0;
+ private float mLastY = 0;
+ private Path2D mPath = new Path2D.Double();
+ private boolean mCachedIsEmpty = true;
+ private Path.FillType mFillType = Path.FillType.WINDING;
+ protected boolean isSimplePath;
+
+ @Implementation
+ protected void __constructor__(Path path) {
+ ShadowLegacyPath shadowPath = extract(path);
+ points = new ArrayList<>(shadowPath.getPoints());
+ mPath.append(shadowPath.mPath, /*connect=*/ false);
+ mFillType = shadowPath.getFillType();
+ }
+
+ Path2D getJavaShape() {
+ return mPath;
+ }
+
+ @Implementation
+ protected void moveTo(float x, float y) {
+ mPath.moveTo(mLastX = x, mLastY = y);
+
+ // Legacy recording behavior
+ Point p = new Point(x, y, MOVE_TO);
+ points.add(p);
+ }
+
+ @Implementation
+ protected void lineTo(float x, float y) {
+ if (!hasPoints()) {
+ mPath.moveTo(mLastX = 0, mLastY = 0);
+ }
+ mPath.lineTo(mLastX = x, mLastY = y);
+
+ // Legacy recording behavior
+ Point point = new Point(x, y, LINE_TO);
+ points.add(point);
+ }
+
+ @Implementation
+ protected void quadTo(float x1, float y1, float x2, float y2) {
+ isSimplePath = false;
+ if (!hasPoints()) {
+ moveTo(0, 0);
+ }
+ mPath.quadTo(x1, y1, mLastX = x2, mLastY = y2);
+ }
+
+ @Implementation
+ protected void cubicTo(float x1, float y1, float x2, float y2, float x3, float y3) {
+ if (!hasPoints()) {
+ mPath.moveTo(0, 0);
+ }
+ mPath.curveTo(x1, y1, x2, y2, mLastX = x3, mLastY = y3);
+ }
+
+ private boolean hasPoints() {
+ return !mPath.getPathIterator(null).isDone();
+ }
+
+ @Implementation
+ protected void reset() {
+ mPath.reset();
+ mLastX = 0;
+ mLastY = 0;
+
+ // Legacy recording behavior
+ points.clear();
+ }
+
+ @Implementation(minSdk = LOLLIPOP)
+ protected float[] approximate(float acceptableError) {
+ PathIterator iterator = mPath.getPathIterator(null, acceptableError);
+
+ float segment[] = new float[6];
+ float totalLength = 0;
+ ArrayList<Point2D.Float> points = new ArrayList<Point2D.Float>();
+ Point2D.Float previousPoint = null;
+ while (!iterator.isDone()) {
+ int type = iterator.currentSegment(segment);
+ Point2D.Float currentPoint = new Point2D.Float(segment[0], segment[1]);
+ // MoveTo shouldn't affect the length
+ if (previousPoint != null && type != PathIterator.SEG_MOVETO) {
+ totalLength += (float) currentPoint.distance(previousPoint);
+ }
+ previousPoint = currentPoint;
+ points.add(currentPoint);
+ iterator.next();
+ }
+
+ int nPoints = points.size();
+ float[] result = new float[nPoints * 3];
+ previousPoint = null;
+ // Distance that we've covered so far. Used to calculate the fraction of the path that
+ // we've covered up to this point.
+ float walkedDistance = .0f;
+ for (int i = 0; i < nPoints; i++) {
+ Point2D.Float point = points.get(i);
+ float distance = previousPoint != null ? (float) previousPoint.distance(point) : .0f;
+ walkedDistance += distance;
+ result[i * 3] = walkedDistance / totalLength;
+ result[i * 3 + 1] = point.x;
+ result[i * 3 + 2] = point.y;
+
+ previousPoint = point;
+ }
+
+ return result;
+ }
+
+ /**
+ * @return all the points that have been added to the {@code Path}
+ */
+ @Override
+ public List<Point> getPoints() {
+ return points;
+ }
+
+ @Implementation
+ protected void rewind() {
+ // call out to reset since there's nothing to optimize in
+ // terms of data structs.
+ reset();
+ }
+
+ @Implementation
+ protected void set(Path src) {
+ mPath.reset();
+
+ ShadowLegacyPath shadowSrc = extract(src);
+ setFillType(shadowSrc.mFillType);
+ mPath.append(shadowSrc.mPath, false /*connect*/);
+ }
+
+ @Implementation(minSdk = KITKAT)
+ protected boolean op(Path path1, Path path2, Path.Op op) {
+ Log.w(TAG, "android.graphics.Path#op() not supported yet.");
+ return false;
+ }
+
+ @Implementation(minSdk = LOLLIPOP)
+ protected boolean isConvex() {
+ Log.w(TAG, "android.graphics.Path#isConvex() not supported yet.");
+ return true;
+ }
+
+ @Implementation
+ protected Path.FillType getFillType() {
+ return mFillType;
+ }
+
+ @Implementation
+ protected void setFillType(Path.FillType fillType) {
+ mFillType = fillType;
+ mPath.setWindingRule(getWindingRule(fillType));
+ }
+
+ /**
+ * Returns the Java2D winding rules matching a given Android {@link
+ * android.graphics.Path.FillType}.
+ *
+ * @param type the android fill type
+ * @return the matching java2d winding rule.
+ */
+ private static int getWindingRule(Path.FillType type) {
+ switch (type) {
+ case WINDING:
+ case INVERSE_WINDING:
+ return GeneralPath.WIND_NON_ZERO;
+ case EVEN_ODD:
+ case INVERSE_EVEN_ODD:
+ return GeneralPath.WIND_EVEN_ODD;
+
+ default:
+ assert false;
+ return GeneralPath.WIND_NON_ZERO;
+ }
+ }
+
+ @Implementation
+ protected boolean isInverseFillType() {
+ throw new UnsupportedOperationException("isInverseFillType");
+ }
+
+ @Implementation
+ protected void toggleInverseFillType() {
+ throw new UnsupportedOperationException("toggleInverseFillType");
+ }
+
+ @Implementation
+ protected boolean isEmpty() {
+ if (!mCachedIsEmpty) {
+ return false;
+ }
+
+ mCachedIsEmpty = Boolean.TRUE;
+ for (PathIterator it = mPath.getPathIterator(null); !it.isDone(); it.next()) {
+ // int type = it.currentSegment(coords);
+ // if (type != PathIterator.SEG_MOVETO) {
+ // Once we know that the path is not empty, we do not need to check again unless
+ // Path#reset is called.
+ mCachedIsEmpty = false;
+ return false;
+ // }
+ }
+
+ return true;
+ }
+
+ @Implementation
+ protected boolean isRect(RectF rect) {
+ // create an Area that can test if the path is a rect
+ Area area = new Area(mPath);
+ if (area.isRectangular()) {
+ if (rect != null) {
+ fillBounds(rect);
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ @Implementation
+ protected void computeBounds(RectF bounds, boolean exact) {
+ fillBounds(bounds);
+ }
+
+ @Implementation
+ protected void incReserve(int extraPtCount) {
+ throw new UnsupportedOperationException("incReserve");
+ }
+
+ @Implementation
+ protected void rMoveTo(float dx, float dy) {
+ dx += mLastX;
+ dy += mLastY;
+ mPath.moveTo(mLastX = dx, mLastY = dy);
+ }
+
+ @Implementation
+ protected void rLineTo(float dx, float dy) {
+ if (!hasPoints()) {
+ mPath.moveTo(mLastX = 0, mLastY = 0);
+ }
+
+ if (Math.abs(dx) < EPSILON && Math.abs(dy) < EPSILON) {
+ // The delta is so small that this shouldn't generate a line
+ return;
+ }
+
+ dx += mLastX;
+ dy += mLastY;
+ mPath.lineTo(mLastX = dx, mLastY = dy);
+ }
+
+ @Implementation
+ protected void rQuadTo(float dx1, float dy1, float dx2, float dy2) {
+ if (!hasPoints()) {
+ mPath.moveTo(mLastX = 0, mLastY = 0);
+ }
+ dx1 += mLastX;
+ dy1 += mLastY;
+ dx2 += mLastX;
+ dy2 += mLastY;
+ mPath.quadTo(dx1, dy1, mLastX = dx2, mLastY = dy2);
+ }
+
+ @Implementation
+ protected void rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3) {
+ if (!hasPoints()) {
+ mPath.moveTo(mLastX = 0, mLastY = 0);
+ }
+ x1 += mLastX;
+ y1 += mLastY;
+ x2 += mLastX;
+ y2 += mLastY;
+ x3 += mLastX;
+ y3 += mLastY;
+ mPath.curveTo(x1, y1, x2, y2, mLastX = x3, mLastY = y3);
+ }
+
+ @Implementation
+ protected void arcTo(RectF oval, float startAngle, float sweepAngle) {
+ arcTo(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle, false);
+ }
+
+ @Implementation
+ protected void arcTo(RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo) {
+ arcTo(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle, forceMoveTo);
+ }
+
+ @Implementation(minSdk = LOLLIPOP)
+ protected void arcTo(
+ float left,
+ float top,
+ float right,
+ float bottom,
+ float startAngle,
+ float sweepAngle,
+ boolean forceMoveTo) {
+ isSimplePath = false;
+ Arc2D arc =
+ new Arc2D.Float(
+ left, top, right - left, bottom - top, -startAngle, -sweepAngle, Arc2D.OPEN);
+ mPath.append(arc, true /*connect*/);
+ if (hasPoints()) {
+ resetLastPointFromPath();
+ }
+ }
+
+ @Implementation
+ protected void close() {
+ if (!hasPoints()) {
+ mPath.moveTo(mLastX = 0, mLastY = 0);
+ }
+ mPath.closePath();
+ }
+
+ @Implementation
+ protected void addRect(RectF rect, Direction dir) {
+ addRect(rect.left, rect.top, rect.right, rect.bottom, dir);
+ }
+
+ @Implementation
+ protected void addRect(float left, float top, float right, float bottom, Path.Direction dir) {
+ moveTo(left, top);
+
+ switch (dir) {
+ case CW:
+ lineTo(right, top);
+ lineTo(right, bottom);
+ lineTo(left, bottom);
+ break;
+ case CCW:
+ lineTo(left, bottom);
+ lineTo(right, bottom);
+ lineTo(right, top);
+ break;
+ }
+
+ close();
+
+ resetLastPointFromPath();
+ }
+
+ @Implementation(minSdk = LOLLIPOP)
+ protected void addOval(float left, float top, float right, float bottom, Path.Direction dir) {
+ mPath.append(new Ellipse2D.Float(left, top, right - left, bottom - top), false);
+ }
+
+ @Implementation
+ protected void addCircle(float x, float y, float radius, Path.Direction dir) {
+ mPath.append(new Ellipse2D.Float(x - radius, y - radius, radius * 2, radius * 2), false);
+ }
+
+ @Implementation(minSdk = LOLLIPOP)
+ protected void addArc(
+ float left, float top, float right, float bottom, float startAngle, float sweepAngle) {
+ mPath.append(
+ new Arc2D.Float(
+ left, top, right - left, bottom - top, -startAngle, -sweepAngle, Arc2D.OPEN),
+ false);
+ }
+
+ @Implementation(minSdk = JELLY_BEAN)
+ 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)
+ protected void addRoundRect(RectF rect, float[] radii, Direction dir) {
+ if (rect == null) {
+ throw new NullPointerException("need rect parameter");
+ }
+ addRoundRect(rect.left, rect.top, rect.right, rect.bottom, radii, dir);
+ }
+
+ @Implementation(minSdk = LOLLIPOP)
+ protected void addRoundRect(
+ float left, float top, float right, float bottom, float rx, float ry, Path.Direction dir) {
+ mPath.append(
+ new RoundRectangle2D.Float(left, top, right - left, bottom - top, rx * 2, ry * 2), false);
+ }
+
+ @Implementation(minSdk = LOLLIPOP)
+ protected void addRoundRect(
+ float left, float top, float right, float bottom, float[] radii, Path.Direction dir) {
+ if (radii.length < 8) {
+ throw new ArrayIndexOutOfBoundsException("radii[] needs 8 values");
+ }
+ isSimplePath = false;
+
+ float[] cornerDimensions = new float[radii.length];
+ for (int i = 0; i < radii.length; i++) {
+ cornerDimensions[i] = 2 * radii[i];
+ }
+ mPath.append(
+ new RoundRectangle(left, top, right - left, bottom - top, cornerDimensions), false);
+ }
+
+ @Implementation
+ protected void addPath(Path src, float dx, float dy) {
+ isSimplePath = false;
+ ShadowLegacyPath.addPath(realObject, src, AffineTransform.getTranslateInstance(dx, dy));
+ }
+
+ @Implementation
+ protected void addPath(Path src) {
+ isSimplePath = false;
+ ShadowLegacyPath.addPath(realObject, src, null);
+ }
+
+ @Implementation
+ protected void addPath(Path src, Matrix matrix) {
+ if (matrix == null) {
+ return;
+ }
+ ShadowLegacyPath shadowSrc = extract(src);
+ if (!shadowSrc.isSimplePath) isSimplePath = false;
+
+ ShadowLegacyMatrix shadowMatrix = extract(matrix);
+ ShadowLegacyPath.addPath(realObject, src, shadowMatrix.getAffineTransform());
+ }
+
+ private static void addPath(Path destPath, Path srcPath, AffineTransform transform) {
+ if (destPath == null) {
+ return;
+ }
+
+ if (srcPath == null) {
+ return;
+ }
+
+ ShadowLegacyPath shadowDestPath = extract(destPath);
+ ShadowLegacyPath shadowSrcPath = extract(srcPath);
+ if (transform != null) {
+ shadowDestPath.mPath.append(shadowSrcPath.mPath.getPathIterator(transform), false);
+ } else {
+ shadowDestPath.mPath.append(shadowSrcPath.mPath, false);
+ }
+ }
+
+ @Implementation
+ protected void offset(float dx, float dy, Path dst) {
+ if (dst != null) {
+ dst.set(realObject);
+ } else {
+ dst = realObject;
+ }
+ dst.offset(dx, dy);
+ }
+
+ @Implementation
+ protected void offset(float dx, float dy) {
+ GeneralPath newPath = new GeneralPath();
+
+ PathIterator iterator = mPath.getPathIterator(new AffineTransform(0, 0, dx, 0, 0, dy));
+
+ newPath.append(iterator, false /*connect*/);
+ mPath = newPath;
+ }
+
+ @Implementation
+ protected void setLastPoint(float dx, float dy) {
+ mLastX = dx;
+ mLastY = dy;
+ }
+
+ @Implementation
+ protected void transform(Matrix matrix, Path dst) {
+ ShadowLegacyMatrix shadowMatrix = extract(matrix);
+
+ if (shadowMatrix.hasPerspective()) {
+ Log.w(TAG, "android.graphics.Path#transform() only supports affine transformations.");
+ }
+
+ GeneralPath newPath = new GeneralPath();
+
+ PathIterator iterator = mPath.getPathIterator(shadowMatrix.getAffineTransform());
+ newPath.append(iterator, false /*connect*/);
+
+ if (dst != null) {
+ ShadowLegacyPath shadowPath = extract(dst);
+ shadowPath.mPath = newPath;
+ } else {
+ mPath = newPath;
+ }
+ }
+
+ @Implementation
+ protected void transform(Matrix matrix) {
+ transform(matrix, null);
+ }
+
+ /**
+ * Fills the given {@link RectF} with the path bounds.
+ *
+ * @param bounds the RectF to be filled.
+ */
+ @Override
+ public void fillBounds(RectF bounds) {
+ Rectangle2D rect = mPath.getBounds2D();
+ bounds.left = (float) rect.getMinX();
+ bounds.right = (float) rect.getMaxX();
+ bounds.top = (float) rect.getMinY();
+ bounds.bottom = (float) rect.getMaxY();
+ }
+
+ private void resetLastPointFromPath() {
+ Point2D last = mPath.getCurrentPoint();
+ mLastX = (float) last.getX();
+ mLastY = (float) last.getY();
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyTypeface.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyTypeface.java
new file mode 100644
index 000000000..378bbb03f
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowLegacyTypeface.java
@@ -0,0 +1,273 @@
+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_MR1;
+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.R;
+import static android.os.Build.VERSION_CODES.S;
+import static org.robolectric.RuntimeEnvironment.getApiLevel;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.annotation.SuppressLint;
+import android.content.res.AssetManager;
+import android.graphics.FontFamily;
+import android.graphics.Typeface;
+import android.util.ArrayMap;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicLong;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.HiddenApi;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.RealObject;
+import org.robolectric.annotation.Resetter;
+import org.robolectric.res.Fs;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.util.ReflectionHelpers;
+import org.robolectric.util.ReflectionHelpers.ClassParameter;
+
+/** Shadow for {@link Typeface}. */
+@Implements(value = Typeface.class, looseSignatures = true, isInAndroidSdk = false)
+@SuppressLint("NewApi")
+public class ShadowLegacyTypeface extends ShadowTypeface {
+ private static final Map<Long, FontDesc> FONTS = Collections.synchronizedMap(new HashMap<>());
+ private static final AtomicLong nextFontId = new AtomicLong(1);
+ private FontDesc description;
+
+ @HiddenApi
+ @Implementation(maxSdk = KITKAT)
+ protected void __constructor__(int fontId) {
+ description = findById((long) fontId);
+ }
+
+ @HiddenApi
+ @Implementation(minSdk = LOLLIPOP)
+ protected void __constructor__(long fontId) {
+ description = findById(fontId);
+ }
+
+ @Implementation
+ protected static void __staticInitializer__() {
+ Shadow.directInitialize(Typeface.class);
+ if (RuntimeEnvironment.getApiLevel() > R) {
+ Typeface.loadPreinstalledSystemFontMap();
+ }
+ }
+
+ @Implementation(minSdk = P)
+ protected static Typeface create(Typeface family, int weight, boolean italic) {
+ if (family == null) {
+ return createUnderlyingTypeface(null, weight);
+ } else {
+ ShadowTypeface shadowTypeface = Shadow.extract(family);
+ return createUnderlyingTypeface(shadowTypeface.getFontDescription().getFamilyName(), weight);
+ }
+ }
+
+ @Implementation
+ protected static Typeface create(String familyName, int style) {
+ return createUnderlyingTypeface(familyName, style);
+ }
+
+ @Implementation
+ protected static Typeface create(Typeface family, int style) {
+ if (family == null) {
+ return createUnderlyingTypeface(null, style);
+ } else {
+ ShadowTypeface shadowTypeface = Shadow.extract(family);
+ return createUnderlyingTypeface(shadowTypeface.getFontDescription().getFamilyName(), style);
+ }
+ }
+
+ @Implementation
+ protected static Typeface createFromAsset(AssetManager mgr, String path) {
+ ShadowAssetManager shadowAssetManager = Shadow.extract(mgr);
+ Collection<Path> assetDirs = shadowAssetManager.getAllAssetDirs();
+ for (Path assetDir : assetDirs) {
+ Path assetFile = assetDir.resolve(path);
+ if (Files.exists(assetFile)) {
+ return createUnderlyingTypeface(path, Typeface.NORMAL);
+ }
+
+ // maybe path is e.g. "myFont", but we should match "myFont.ttf" too?
+ Path[] files;
+ try {
+ files = Fs.listFiles(assetDir, f -> f.getFileName().toString().startsWith(path));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ if (files.length != 0) {
+ return createUnderlyingTypeface(path, Typeface.NORMAL);
+ }
+ }
+
+ throw new RuntimeException("Font asset not found " + path);
+ }
+
+ @Implementation(minSdk = O, maxSdk = P)
+ protected static Typeface createFromResources(AssetManager mgr, String path, int cookie) {
+ return createUnderlyingTypeface(path, Typeface.NORMAL);
+ }
+
+ @Implementation(minSdk = O)
+ protected static Typeface createFromResources(
+ Object /* FamilyResourceEntry */ entry,
+ Object /* AssetManager */ mgr,
+ Object /* String */ path) {
+ return createUnderlyingTypeface((String) path, Typeface.NORMAL);
+ }
+
+ @Implementation
+ protected static Typeface createFromFile(File path) {
+ String familyName = path.toPath().getFileName().toString();
+ return createUnderlyingTypeface(familyName, Typeface.NORMAL);
+ }
+
+ @Implementation
+ protected static Typeface createFromFile(String path) {
+ return createFromFile(new File(path));
+ }
+
+ @Implementation
+ protected int getStyle() {
+ return description.getStyle();
+ }
+
+ @Override
+ @Implementation
+ public boolean equals(Object o) {
+ if (o instanceof Typeface) {
+ Typeface other = ((Typeface) o);
+ return Objects.equals(getFontDescription(), shadowOf(other).getFontDescription());
+ }
+ return false;
+ }
+
+ @Override
+ @Implementation
+ public int hashCode() {
+ return getFontDescription().hashCode();
+ }
+
+ @HiddenApi
+ @Implementation(minSdk = LOLLIPOP)
+ protected static Typeface createFromFamilies(Object /*FontFamily[]*/ families) {
+ return null;
+ }
+
+ @HiddenApi
+ @Implementation(minSdk = LOLLIPOP, maxSdk = N_MR1)
+ protected static Typeface createFromFamiliesWithDefault(Object /*FontFamily[]*/ families) {
+ return null;
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static Typeface createFromFamiliesWithDefault(
+ Object /*FontFamily[]*/ families, Object /* int */ weight, Object /* int */ italic) {
+ return createUnderlyingTypeface("fake-font", Typeface.NORMAL);
+ }
+
+ @Implementation(minSdk = P)
+ protected static Typeface createFromFamiliesWithDefault(
+ Object /*FontFamily[]*/ families,
+ Object /* String */ fallbackName,
+ Object /* int */ weight,
+ Object /* int */ italic) {
+ return createUnderlyingTypeface((String) fallbackName, Typeface.NORMAL);
+ }
+
+ @Implementation(minSdk = P, maxSdk = P)
+ protected static void buildSystemFallback(
+ String xmlPath,
+ String fontDir,
+ ArrayMap<String, Typeface> fontMap,
+ ArrayMap<String, FontFamily[]> fallbackMap) {
+ fontMap.put("sans-serif", createUnderlyingTypeface("sans-serif", 0));
+ }
+
+ /** Avoid spurious error message about /system/etc/fonts.xml */
+ @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1)
+ protected static void init() {}
+
+ @HiddenApi
+ @Implementation(minSdk = Q, maxSdk = R)
+ protected static void initSystemDefaultTypefaces(
+ Object systemFontMap, Object fallbacks, Object aliases) {}
+
+ @Resetter
+ public static synchronized void reset() {
+ FONTS.clear();
+ }
+
+ protected static Typeface createUnderlyingTypeface(String familyName, int style) {
+ long thisFontId = nextFontId.getAndIncrement();
+ FONTS.put(thisFontId, new FontDesc(familyName, style));
+ if (getApiLevel() >= LOLLIPOP) {
+ return ReflectionHelpers.callConstructor(
+ Typeface.class, ClassParameter.from(long.class, thisFontId));
+ } else {
+ return ReflectionHelpers.callConstructor(
+ Typeface.class, ClassParameter.from(int.class, (int) thisFontId));
+ }
+ }
+
+ private static synchronized FontDesc findById(long fontId) {
+ if (FONTS.containsKey(fontId)) {
+ return FONTS.get(fontId);
+ }
+ throw new RuntimeException("Unknown font id: " + fontId);
+ }
+
+ @Implementation(minSdk = O, maxSdk = R)
+ protected static long nativeCreateFromArray(long[] familyArray, int weight, int italic) {
+ // TODO: implement this properly
+ long thisFontId = nextFontId.getAndIncrement();
+ FONTS.put(thisFontId, new FontDesc(null, weight));
+ return thisFontId;
+ }
+
+ /**
+ * Returns the font description.
+ *
+ * @return Font description.
+ */
+ @Override
+ public FontDesc getFontDescription() {
+ return description;
+ }
+
+ @Implementation(minSdk = S)
+ protected static void nativeForceSetStaticFinalField(String fieldname, Typeface typeface) {
+ ReflectionHelpers.setStaticField(Typeface.class, fieldname, typeface);
+ }
+
+ @Implementation(minSdk = S)
+ protected static long nativeCreateFromArray(
+ long[] familyArray, long fallbackTypeface, int weight, int italic) {
+ return ShadowLegacyTypeface.nativeCreateFromArray(familyArray, weight, italic);
+ }
+
+ /** Shadow for {@link Typeface.Builder} */
+ @Implements(value = Typeface.Builder.class, minSdk = Q)
+ public static class ShadowBuilder {
+ @RealObject Typeface.Builder realBuilder;
+
+ @Implementation
+ protected Typeface build() {
+ String path = ReflectionHelpers.getField(realBuilder, "mPath");
+ return createUnderlyingTypeface(path, Typeface.NORMAL);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMatrix.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMatrix.java
index ef26f9e43..62b702107 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMatrix.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMatrix.java
@@ -1,29 +1,15 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.KITKAT;
-import static android.os.Build.VERSION_CODES.LOLLIPOP;
import android.graphics.Matrix;
-import android.graphics.Matrix.ScaleToFit;
-import android.graphics.PointF;
-import android.graphics.RectF;
-import java.awt.geom.AffineTransform;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Deque;
-import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
-import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
-import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowMatrix.Picker;
@SuppressWarnings({"UnusedDeclaration"})
-@Implements(Matrix.class)
-public class ShadowMatrix {
+@Implements(value = Matrix.class, shadowPicker = Picker.class)
+public abstract class ShadowMatrix {
public static final String TRANSLATE = "translate";
public static final String SCALE = "scale";
public static final String ROTATE = "rotate";
@@ -31,631 +17,35 @@ public class ShadowMatrix {
public static final String SKEW = "skew";
public static final String MATRIX = "matrix";
- private static final float EPSILON = 1e-3f;
-
- private final Deque<String> preOps = new ArrayDeque<>();
- private final Deque<String> postOps = new ArrayDeque<>();
- private final Map<String, String> setOps = new LinkedHashMap<>();
-
- private SimpleMatrix simpleMatrix = SimpleMatrix.newIdentityMatrix();
-
- @Implementation
- protected void __constructor__(Matrix src) {
- set(src);
- }
-
/**
- * A list of all 'pre' operations performed on this Matrix. The last operation performed will
- * be first in the list.
+ * A list of all 'pre' operations performed on this Matrix. The last operation performed will be
+ * first in the list.
+ *
* @return A list of all 'pre' operations performed on this Matrix.
*/
- public List<String> getPreOperations() {
- return Collections.unmodifiableList(new ArrayList<>(preOps));
- }
+ public abstract List<String> getPreOperations();
/**
- * A list of all 'post' operations performed on this Matrix. The last operation performed will
- * be last in the list.
+ * A list of all 'post' operations performed on this Matrix. The last operation performed will be
+ * last in the list.
+ *
* @return A list of all 'post' operations performed on this Matrix.
*/
- public List<String> getPostOperations() {
- return Collections.unmodifiableList(new ArrayList<>(postOps));
- }
+ public abstract List<String> getPostOperations();
/**
* A map of all 'set' operations performed on this Matrix.
+ *
* @return A map of all 'set' operations performed on this Matrix.
*/
- public Map<String, String> getSetOperations() {
- return Collections.unmodifiableMap(new LinkedHashMap<>(setOps));
- }
-
- @Implementation
- protected boolean isIdentity() {
- return simpleMatrix.equals(SimpleMatrix.IDENTITY);
- }
-
- @Implementation(minSdk = LOLLIPOP)
- protected boolean isAffine() {
- return simpleMatrix.isAffine();
- }
-
- @Implementation
- protected boolean rectStaysRect() {
- return simpleMatrix.rectStaysRect();
- }
-
- @Implementation
- protected void getValues(float[] values) {
- simpleMatrix.getValues(values);
- }
-
- @Implementation
- protected void setValues(float[] values) {
- simpleMatrix = new SimpleMatrix(values);
- }
-
- @Implementation
- protected void set(Matrix src) {
- reset();
- if (src != null) {
- ShadowMatrix shadowMatrix = Shadow.extract(src);
- preOps.addAll(shadowMatrix.preOps);
- postOps.addAll(shadowMatrix.postOps);
- setOps.putAll(shadowMatrix.setOps);
- simpleMatrix = new SimpleMatrix(getSimpleMatrix(src));
- }
- }
-
- @Implementation
- protected void reset() {
- preOps.clear();
- postOps.clear();
- setOps.clear();
- simpleMatrix = SimpleMatrix.newIdentityMatrix();
- }
-
- @Implementation
- protected void setTranslate(float dx, float dy) {
- setOps.put(TRANSLATE, dx + " " + dy);
- simpleMatrix = SimpleMatrix.translate(dx, dy);
- }
-
- @Implementation
- protected void setScale(float sx, float sy, float px, float py) {
- setOps.put(SCALE, sx + " " + sy + " " + px + " " + py);
- simpleMatrix = SimpleMatrix.scale(sx, sy, px, py);
- }
-
- @Implementation
- protected void setScale(float sx, float sy) {
- setOps.put(SCALE, sx + " " + sy);
- simpleMatrix = SimpleMatrix.scale(sx, sy);
- }
-
- @Implementation
- protected void setRotate(float degrees, float px, float py) {
- setOps.put(ROTATE, degrees + " " + px + " " + py);
- simpleMatrix = SimpleMatrix.rotate(degrees, px, py);
- }
-
- @Implementation
- protected void setRotate(float degrees) {
- setOps.put(ROTATE, Float.toString(degrees));
- simpleMatrix = SimpleMatrix.rotate(degrees);
- }
-
- @Implementation
- protected void setSinCos(float sinValue, float cosValue, float px, float py) {
- setOps.put(SINCOS, sinValue + " " + cosValue + " " + px + " " + py);
- simpleMatrix = SimpleMatrix.sinCos(sinValue, cosValue, px, py);
- }
-
- @Implementation
- protected void setSinCos(float sinValue, float cosValue) {
- setOps.put(SINCOS, sinValue + " " + cosValue);
- simpleMatrix = SimpleMatrix.sinCos(sinValue, cosValue);
- }
-
- @Implementation
- protected void setSkew(float kx, float ky, float px, float py) {
- setOps.put(SKEW, kx + " " + ky + " " + px + " " + py);
- simpleMatrix = SimpleMatrix.skew(kx, ky, px, py);
- }
-
- @Implementation
- protected void setSkew(float kx, float ky) {
- setOps.put(SKEW, kx + " " + ky);
- simpleMatrix = SimpleMatrix.skew(kx, ky);
- }
-
- @Implementation
- protected boolean setConcat(Matrix a, Matrix b) {
- simpleMatrix = getSimpleMatrix(a).multiply(getSimpleMatrix(b));
- return true;
- }
-
- @Implementation
- protected boolean preTranslate(float dx, float dy) {
- preOps.addFirst(TRANSLATE + " " + dx + " " + dy);
- return preConcat(SimpleMatrix.translate(dx, dy));
- }
-
- @Implementation
- protected boolean preScale(float sx, float sy, float px, float py) {
- preOps.addFirst(SCALE + " " + sx + " " + sy + " " + px + " " + py);
- return preConcat(SimpleMatrix.scale(sx, sy, px, py));
- }
-
- @Implementation
- protected boolean preScale(float sx, float sy) {
- preOps.addFirst(SCALE + " " + sx + " " + sy);
- return preConcat(SimpleMatrix.scale(sx, sy));
- }
-
- @Implementation
- protected boolean preRotate(float degrees, float px, float py) {
- preOps.addFirst(ROTATE + " " + degrees + " " + px + " " + py);
- return preConcat(SimpleMatrix.rotate(degrees, px, py));
- }
-
- @Implementation
- protected boolean preRotate(float degrees) {
- preOps.addFirst(ROTATE + " " + Float.toString(degrees));
- return preConcat(SimpleMatrix.rotate(degrees));
- }
-
- @Implementation
- protected boolean preSkew(float kx, float ky, float px, float py) {
- preOps.addFirst(SKEW + " " + kx + " " + ky + " " + px + " " + py);
- return preConcat(SimpleMatrix.skew(kx, ky, px, py));
- }
-
- @Implementation
- protected boolean preSkew(float kx, float ky) {
- preOps.addFirst(SKEW + " " + kx + " " + ky);
- return preConcat(SimpleMatrix.skew(kx, ky));
- }
-
- @Implementation
- protected boolean preConcat(Matrix other) {
- preOps.addFirst(MATRIX + " " + other);
- return preConcat(getSimpleMatrix(other));
- }
-
- @Implementation
- protected boolean postTranslate(float dx, float dy) {
- postOps.addLast(TRANSLATE + " " + dx + " " + dy);
- return postConcat(SimpleMatrix.translate(dx, dy));
- }
-
- @Implementation
- protected boolean postScale(float sx, float sy, float px, float py) {
- postOps.addLast(SCALE + " " + sx + " " + sy + " " + px + " " + py);
- return postConcat(SimpleMatrix.scale(sx, sy, px, py));
- }
-
- @Implementation
- protected boolean postScale(float sx, float sy) {
- postOps.addLast(SCALE + " " + sx + " " + sy);
- return postConcat(SimpleMatrix.scale(sx, sy));
- }
-
- @Implementation
- protected boolean postRotate(float degrees, float px, float py) {
- postOps.addLast(ROTATE + " " + degrees + " " + px + " " + py);
- return postConcat(SimpleMatrix.rotate(degrees, px, py));
- }
-
- @Implementation
- protected boolean postRotate(float degrees) {
- postOps.addLast(ROTATE + " " + Float.toString(degrees));
- return postConcat(SimpleMatrix.rotate(degrees));
- }
-
- @Implementation
- protected boolean postSkew(float kx, float ky, float px, float py) {
- postOps.addLast(SKEW + " " + kx + " " + ky + " " + px + " " + py);
- return postConcat(SimpleMatrix.skew(kx, ky, px, py));
- }
-
- @Implementation
- protected boolean postSkew(float kx, float ky) {
- postOps.addLast(SKEW + " " + kx + " " + ky);
- return postConcat(SimpleMatrix.skew(kx, ky));
- }
-
- @Implementation
- protected boolean postConcat(Matrix other) {
- postOps.addLast(MATRIX + " " + other);
- return postConcat(getSimpleMatrix(other));
- }
-
- @Implementation
- protected boolean invert(Matrix inverse) {
- final SimpleMatrix inverseMatrix = simpleMatrix.invert();
- if (inverseMatrix != null) {
- if (inverse != null) {
- final ShadowMatrix shadowInverse = Shadow.extract(inverse);
- shadowInverse.simpleMatrix = inverseMatrix;
- }
- return true;
- }
- return false;
- }
-
- boolean hasPerspective() {
- return (simpleMatrix.mValues[6] != 0 || simpleMatrix.mValues[7] != 0 || simpleMatrix.mValues[8] != 1);
- }
-
- protected AffineTransform getAffineTransform() {
- // the AffineTransform constructor takes the value in a different order
- // for a matrix [ 0 1 2 ]
- // [ 3 4 5 ]
- // the order is 0, 3, 1, 4, 2, 5...
- return new AffineTransform(
- simpleMatrix.mValues[0],
- simpleMatrix.mValues[3],
- simpleMatrix.mValues[1],
- simpleMatrix.mValues[4],
- simpleMatrix.mValues[2],
- simpleMatrix.mValues[5]);
- }
-
- public PointF mapPoint(float x, float y) {
- return simpleMatrix.transform(new PointF(x, y));
- }
-
- public PointF mapPoint(PointF point) {
- return simpleMatrix.transform(point);
- }
-
- @Implementation
- protected boolean mapRect(RectF destination, RectF source) {
- final PointF leftTop = mapPoint(source.left, source.top);
- final PointF rightBottom = mapPoint(source.right, source.bottom);
- destination.set(
- Math.min(leftTop.x, rightBottom.x),
- Math.min(leftTop.y, rightBottom.y),
- Math.max(leftTop.x, rightBottom.x),
- Math.max(leftTop.y, rightBottom.y));
- return true;
- }
-
- @Implementation
- protected void mapPoints(float[] dst, int dstIndex, float[] src, int srcIndex, int pointCount) {
- for (int i = 0; i < pointCount; i++) {
- final PointF mapped = mapPoint(src[srcIndex + i * 2], src[srcIndex + i * 2 + 1]);
- dst[dstIndex + i * 2] = mapped.x;
- dst[dstIndex + i * 2 + 1] = mapped.y;
- }
- }
-
- @Implementation
- protected void mapVectors(float[] dst, int dstIndex, float[] src, int srcIndex, int vectorCount) {
- final float transX = simpleMatrix.mValues[Matrix.MTRANS_X];
- final float transY = simpleMatrix.mValues[Matrix.MTRANS_Y];
-
- simpleMatrix.mValues[Matrix.MTRANS_X] = 0;
- simpleMatrix.mValues[Matrix.MTRANS_Y] = 0;
-
- for (int i = 0; i < vectorCount; i++) {
- final PointF mapped = mapPoint(src[srcIndex + i * 2], src[srcIndex + i * 2 + 1]);
- dst[dstIndex + i * 2] = mapped.x;
- dst[dstIndex + i * 2 + 1] = mapped.y;
- }
-
- simpleMatrix.mValues[Matrix.MTRANS_X] = transX;
- simpleMatrix.mValues[Matrix.MTRANS_Y] = transY;
- }
-
- @Implementation
- protected float mapRadius(float radius) {
- float[] src = new float[] {radius, 0.f, 0.f, radius};
- mapVectors(src, 0, src, 0, 2);
-
- float l1 = (float) Math.hypot(src[0], src[1]);
- float l2 = (float) Math.hypot(src[2], src[3]);
- return (float) Math.sqrt(l1 * l2);
- }
-
- @Implementation
- protected boolean setRectToRect(RectF src, RectF dst, Matrix.ScaleToFit stf) {
- if (src.isEmpty()) {
- reset();
- return false;
- }
- return simpleMatrix.setRectToRect(src, dst, stf);
- }
-
- @Implementation
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof Matrix) {
- return getSimpleMatrix(((Matrix) obj)).equals(simpleMatrix);
- } else {
- return obj instanceof ShadowMatrix && obj.equals(simpleMatrix);
- }
- }
-
- @Implementation(minSdk = KITKAT)
- @Override
- public int hashCode() {
- return Objects.hashCode(simpleMatrix);
- }
-
- public String getDescription() {
- return "Matrix[pre=" + preOps + ", set=" + setOps + ", post=" + postOps + "]";
- }
-
- private static SimpleMatrix getSimpleMatrix(Matrix matrix) {
- final ShadowMatrix otherMatrix = Shadow.extract(matrix);
- return otherMatrix.simpleMatrix;
- }
-
- private boolean postConcat(SimpleMatrix matrix) {
- simpleMatrix = matrix.multiply(simpleMatrix);
- return true;
- }
-
- private boolean preConcat(SimpleMatrix matrix) {
- simpleMatrix = simpleMatrix.multiply(matrix);
- return true;
- }
-
- /**
- * A simple implementation of an immutable matrix.
- */
- private static class SimpleMatrix {
- private static final SimpleMatrix IDENTITY = newIdentityMatrix();
-
- private static SimpleMatrix newIdentityMatrix() {
- return new SimpleMatrix(
- new float[] {
- 1.0f, 0.0f, 0.0f,
- 0.0f, 1.0f, 0.0f,
- 0.0f, 0.0f, 1.0f,
- });
- }
-
- private final float[] mValues;
-
- SimpleMatrix(SimpleMatrix matrix) {
- mValues = Arrays.copyOf(matrix.mValues, matrix.mValues.length);
- }
-
- private SimpleMatrix(float[] values) {
- if (values.length != 9) {
- throw new ArrayIndexOutOfBoundsException();
- }
- mValues = Arrays.copyOf(values, 9);
- }
-
- public boolean isAffine() {
- return mValues[6] == 0.0f && mValues[7] == 0.0f && mValues[8] == 1.0f;
- }
-
- public boolean rectStaysRect() {
- final float m00 = mValues[0];
- final float m01 = mValues[1];
- final float m10 = mValues[3];
- final float m11 = mValues[4];
- return (m00 == 0 && m11 == 0 && m01 != 0 && m10 != 0) || (m00 != 0 && m11 != 0 && m01 == 0 && m10 == 0);
- }
-
- public void getValues(float[] values) {
- if (values.length < 9) {
- throw new ArrayIndexOutOfBoundsException();
- }
- System.arraycopy(mValues, 0, values, 0, 9);
- }
-
- public static SimpleMatrix translate(float dx, float dy) {
- return new SimpleMatrix(new float[] {
- 1.0f, 0.0f, dx,
- 0.0f, 1.0f, dy,
- 0.0f, 0.0f, 1.0f,
- });
- }
+ public abstract Map<String, String> getSetOperations();
- public static SimpleMatrix scale(float sx, float sy, float px, float py) {
- return new SimpleMatrix(new float[] {
- sx, 0.0f, px * (1 - sx),
- 0.0f, sy, py * (1 - sy),
- 0.0f, 0.0f, 1.0f,
- });
- }
-
- public static SimpleMatrix scale(float sx, float sy) {
- return new SimpleMatrix(new float[] {
- sx, 0.0f, 0.0f,
- 0.0f, sy, 0.0f,
- 0.0f, 0.0f, 1.0f,
- });
- }
-
- public static SimpleMatrix rotate(float degrees, float px, float py) {
- final double radians = Math.toRadians(degrees);
- final float sin = (float) Math.sin(radians);
- final float cos = (float) Math.cos(radians);
- return sinCos(sin, cos, px, py);
- }
-
- public static SimpleMatrix rotate(float degrees) {
- final double radians = Math.toRadians(degrees);
- final float sin = (float) Math.sin(radians);
- final float cos = (float) Math.cos(radians);
- return sinCos(sin, cos);
- }
-
- public static SimpleMatrix sinCos(float sin, float cos, float px, float py) {
- return new SimpleMatrix(new float[] {
- cos, -sin, sin * py + (1 - cos) * px,
- sin, cos, -sin * px + (1 - cos) * py,
- 0.0f, 0.0f, 1.0f,
- });
- }
-
- public static SimpleMatrix sinCos(float sin, float cos) {
- return new SimpleMatrix(new float[] {
- cos, -sin, 0.0f,
- sin, cos, 0.0f,
- 0.0f, 0.0f, 1.0f,
- });
- }
-
- public static SimpleMatrix skew(float kx, float ky, float px, float py) {
- return new SimpleMatrix(new float[] {
- 1.0f, kx, -kx * py,
- ky, 1.0f, -ky * px,
- 0.0f, 0.0f, 1.0f,
- });
- }
-
- public static SimpleMatrix skew(float kx, float ky) {
- return new SimpleMatrix(new float[] {
- 1.0f, kx, 0.0f,
- ky, 1.0f, 0.0f,
- 0.0f, 0.0f, 1.0f,
- });
- }
-
- public SimpleMatrix multiply(SimpleMatrix matrix) {
- final float[] values = new float[9];
- for (int i = 0; i < values.length; ++i) {
- final int row = i / 3;
- final int col = i % 3;
- for (int j = 0; j < 3; ++j) {
- values[i] += mValues[row * 3 + j] * matrix.mValues[j * 3 + col];
- }
- }
- return new SimpleMatrix(values);
- }
-
- public SimpleMatrix invert() {
- final float invDet = inverseDeterminant();
- if (invDet == 0) {
- return null;
- }
-
- final float[] src = mValues;
- final float[] dst = new float[9];
- dst[0] = cross_scale(src[4], src[8], src[5], src[7], invDet);
- dst[1] = cross_scale(src[2], src[7], src[1], src[8], invDet);
- dst[2] = cross_scale(src[1], src[5], src[2], src[4], invDet);
-
- dst[3] = cross_scale(src[5], src[6], src[3], src[8], invDet);
- dst[4] = cross_scale(src[0], src[8], src[2], src[6], invDet);
- dst[5] = cross_scale(src[2], src[3], src[0], src[5], invDet);
-
- dst[6] = cross_scale(src[3], src[7], src[4], src[6], invDet);
- dst[7] = cross_scale(src[1], src[6], src[0], src[7], invDet);
- dst[8] = cross_scale(src[0], src[4], src[1], src[3], invDet);
- return new SimpleMatrix(dst);
- }
-
- public PointF transform(PointF point) {
- return new PointF(
- point.x * mValues[0] + point.y * mValues[1] + mValues[2],
- point.x * mValues[3] + point.y * mValues[4] + mValues[5]);
- }
-
- // See: https://android.googlesource.com/platform/frameworks/base/+/6fca81de9b2079ec88e785f58bf49bf1f0c105e2/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java
- protected boolean setRectToRect(RectF src, RectF dst, ScaleToFit stf) {
- if (dst.isEmpty()) {
- mValues[0] =
- mValues[1] =
- mValues[2] = mValues[3] = mValues[4] = mValues[5] = mValues[6] = mValues[7] = 0;
- mValues[8] = 1;
- } else {
- float tx = dst.width() / src.width();
- float sx = dst.width() / src.width();
- float ty = dst.height() / src.height();
- float sy = dst.height() / src.height();
- boolean xLarger = false;
-
- if (stf != ScaleToFit.FILL) {
- if (sx > sy) {
- xLarger = true;
- sx = sy;
- } else {
- sy = sx;
- }
- }
-
- tx = dst.left - src.left * sx;
- ty = dst.top - src.top * sy;
- if (stf == ScaleToFit.CENTER || stf == ScaleToFit.END) {
- float diff;
-
- if (xLarger) {
- diff = dst.width() - src.width() * sy;
- } else {
- diff = dst.height() - src.height() * sy;
- }
-
- if (stf == ScaleToFit.CENTER) {
- diff = diff / 2;
- }
-
- if (xLarger) {
- tx += diff;
- } else {
- ty += diff;
- }
- }
-
- mValues[0] = sx;
- mValues[4] = sy;
- mValues[2] = tx;
- mValues[5] = ty;
- mValues[1] = mValues[3] = mValues[6] = mValues[7] = 0;
- }
- // shared cleanup
- mValues[8] = 1;
- return true;
- }
-
- @Override
- public boolean equals(Object o) {
- return this == o || (o instanceof SimpleMatrix && equals((SimpleMatrix) o));
- }
-
- @SuppressWarnings("NonOverridingEquals")
- public boolean equals(SimpleMatrix matrix) {
- if (matrix == null) {
- return false;
- }
- for (int i = 0; i < mValues.length; i++) {
- if (!isNearlyZero(matrix.mValues[i] - mValues[i])) {
- return false;
- }
- }
- return true;
- }
-
- @Override
- public int hashCode() {
- return Arrays.hashCode(mValues);
- }
-
- private static boolean isNearlyZero(float value) {
- return Math.abs(value) < EPSILON;
- }
-
- private static float cross(float a, float b, float c, float d) {
- return a * b - c * d;
- }
-
- private static float cross_scale(float a, float b, float c, float d, float scale) {
- return cross(a, b, c, d) * scale;
- }
+ public abstract String getDescription();
- private float inverseDeterminant() {
- final float determinant = mValues[0] * cross(mValues[4], mValues[8], mValues[5], mValues[7]) +
- mValues[1] * cross(mValues[5], mValues[6], mValues[3], mValues[8]) +
- mValues[2] * cross(mValues[3], mValues[7], mValues[4], mValues[6]);
- return isNearlyZero(determinant) ? 0.0f : 1.0f / determinant;
+ /** Shadow picker for {@link Matrix}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(ShadowLegacyMatrix.class, ShadowNativeMatrix.class);
}
}
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaController.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaController.java
index 809b5cc5b..252bc427d 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaController.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaController.java
@@ -4,6 +4,7 @@ import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static org.robolectric.util.reflector.Reflector.reflector;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.PendingIntent;
import android.media.MediaMetadata;
import android.media.Rating;
@@ -12,6 +13,7 @@ import android.media.session.MediaController.Callback;
import android.media.session.MediaController.PlaybackInfo;
import android.media.session.PlaybackState;
import android.os.Bundle;
+import android.os.Handler;
import java.util.ArrayList;
import java.util.List;
import org.robolectric.annotation.Implementation;
@@ -30,6 +32,7 @@ public class ShadowMediaController {
private PlaybackInfo playbackInfo;
private MediaMetadata mediaMetadata;
private PendingIntent sessionActivity;
+ private Bundle extras;
/**
* A value of RATING_NONE for ratingType indicates that rating media is not supported by the media
@@ -122,14 +125,26 @@ public class ShadowMediaController {
return sessionActivity;
}
+ /** Saves the extras to control the return value of {@link MediaController#getExtras()}. */
+ public void setExtras(Bundle extras) {
+ this.extras = extras;
+ }
+
+ /** Gets the extras set via {@link #extras}. */
+ @Implementation
+ protected Bundle getExtras() {
+ return extras;
+ }
+
/**
* Register callback and store it in the shadow to make it easier to check the state of the
- * registered callbacks.
+ * registered callbacks. Handler is just passed on to the real class.
*/
@Implementation
- protected void registerCallback(@NonNull Callback callback) {
+ protected void registerCallback(@NonNull Callback callback, @Nullable Handler handler) {
callbacks.add(callback);
- reflector(MediaControllerReflector.class, realMediaController).registerCallback(callback);
+ reflector(MediaControllerReflector.class, realMediaController)
+ .registerCallback(callback, handler);
}
/**
@@ -192,7 +207,7 @@ public class ShadowMediaController {
interface MediaControllerReflector {
@Direct
- void registerCallback(@NonNull Callback callback);
+ void registerCallback(@NonNull Callback callback, @Nullable Handler handler);
@Direct
void unregisterCallback(@NonNull Callback callback);
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaStore.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaStore.java
index be962b324..31fc79634 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaStore.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowMediaStore.java
@@ -35,7 +35,11 @@ public class ShadowMediaStore {
@Implementation
protected static Bitmap getBitmap(ContentResolver cr, Uri url) {
- return ShadowBitmapFactory.create(url.toString(), null, null);
+ if (ShadowView.useRealGraphics()) {
+ return Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
+ } else {
+ return ShadowBitmapFactory.create(url.toString(), null, null);
+ }
}
}
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 dfe78a2e4..ab3c6e3d5 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeAllocationRegistry.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeAllocationRegistry.java
@@ -1,21 +1,65 @@
package org.robolectric.shadows;
-import static android.os.Build.VERSION_CODES.N;
+import static android.os.Build.VERSION_CODES.O;
+import static org.robolectric.util.reflector.Reflector.reflector;
import libcore.util.NativeAllocationRegistry;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.RealObject;
+import org.robolectric.nativeruntime.NativeAllocationRegistryNatives;
+import org.robolectric.shadows.ShadowNativeAllocationRegistry.Picker;
+import org.robolectric.util.reflector.Accessor;
+import org.robolectric.util.reflector.Direct;
+import org.robolectric.util.reflector.ForType;
-@Implements(value = NativeAllocationRegistry.class, minSdk = N, isInAndroidSdk = false, looseSignatures = true)
+/** Shadow for {@link NativeAllocationRegistry} that is backed by native code */
+@Implements(
+ value = NativeAllocationRegistry.class,
+ minSdk = O,
+ isInAndroidSdk = false,
+ shadowPicker = Picker.class)
public class ShadowNativeAllocationRegistry {
+ @RealObject protected NativeAllocationRegistry realNativeAllocationRegistry;
+
@Implementation
- protected Runnable registerNativeAllocation(Object referent, Object allocator) {
- return () -> {};
+ protected Runnable registerNativeAllocation(Object referent, long nativePtr) {
+ // Avoid registering native allocations for classes where native methods are no-ops (like
+ // Binder), or for classes that simulate native pointers (like binary resources) but don't
+ // actually use native libraries.
+ if (nativePtr != 0 && hasValidFreeFunction()) {
+ return reflector(NativeAllocationRegistryReflector.class, realNativeAllocationRegistry)
+ .registerNativeAllocation(referent, nativePtr);
+ } else {
+ return () -> {};
+ }
+ }
+
+ private boolean hasValidFreeFunction() {
+ return reflector(NativeAllocationRegistryReflector.class, realNativeAllocationRegistry)
+ .getFreeFunction()
+ != 0;
}
@Implementation
- protected Runnable registerNativeAllocation(Object referent, long nativePtr) {
- return () -> {};
+ protected static void applyFreeFunction(long freeFunction, long nativePtr) {
+ NativeAllocationRegistryNatives.applyFreeFunction(freeFunction, nativePtr);
+ }
+
+ @ForType(NativeAllocationRegistry.class)
+ interface NativeAllocationRegistryReflector {
+ @Direct
+ Runnable registerNativeAllocation(Object referent, long nativePtr);
+
+ @Accessor("freeFunction")
+ long getFreeFunction();
+ }
+
+ /** Shadow picker for {@link NativeAllocationRegistry}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(ShadowNoopNativeAllocationRegistry.class, ShadowNativeAllocationRegistry.class);
+ }
}
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeAnimatedImageDrawable.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeAnimatedImageDrawable.java
new file mode 100644
index 000000000..a73f0a61f
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeAnimatedImageDrawable.java
@@ -0,0 +1,125 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.P;
+import static android.os.Build.VERSION_CODES.Q;
+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.ImageDecoder;
+import android.graphics.Rect;
+import android.graphics.drawable.AnimatedImageDrawable;
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.AnimatedImageDrawableNatives;
+import org.robolectric.shadows.ShadowNativeAnimatedImageDrawable.Picker;
+
+/** Shadow for {@link AnimatedImageDrawable} that is backed by native code */
+@Implements(value = AnimatedImageDrawable.class, shadowPicker = Picker.class, minSdk = P)
+public class ShadowNativeAnimatedImageDrawable extends ShadowDrawable {
+ @Implementation(minSdk = Q)
+ protected static long nCreate(
+ long nativeImageDecoder,
+ ImageDecoder decoder,
+ int width,
+ int height,
+ long colorSpaceHandle,
+ boolean extended,
+ Rect cropRect)
+ throws IOException {
+ return AnimatedImageDrawableNatives.nCreate(
+ nativeImageDecoder, decoder, width, height, colorSpaceHandle, extended, cropRect);
+ }
+
+ @Implementation(minSdk = P, maxSdk = P)
+ protected static long nCreate(
+ long nativeImageDecoder, ImageDecoder decoder, int width, int height, Rect cropRect)
+ throws IOException {
+ return nCreate(nativeImageDecoder, decoder, width, height, 0, false, cropRect);
+ }
+
+ @Implementation
+ protected static long nGetNativeFinalizer() {
+ return AnimatedImageDrawableNatives.nGetNativeFinalizer();
+ }
+
+ @Implementation
+ protected static long nDraw(long nativePtr, long canvasNativePtr) {
+ return AnimatedImageDrawableNatives.nDraw(nativePtr, canvasNativePtr);
+ }
+
+ @Implementation
+ protected static void nSetAlpha(long nativePtr, int alpha) {
+ AnimatedImageDrawableNatives.nSetAlpha(nativePtr, alpha);
+ }
+
+ @Implementation
+ protected static int nGetAlpha(long nativePtr) {
+ return AnimatedImageDrawableNatives.nGetAlpha(nativePtr);
+ }
+
+ @Implementation
+ protected static void nSetColorFilter(long nativePtr, long nativeFilter) {
+ AnimatedImageDrawableNatives.nSetColorFilter(nativePtr, nativeFilter);
+ }
+
+ @Implementation
+ protected static boolean nIsRunning(long nativePtr) {
+ return AnimatedImageDrawableNatives.nIsRunning(nativePtr);
+ }
+
+ @Implementation
+ protected static boolean nStart(long nativePtr) {
+ return AnimatedImageDrawableNatives.nStart(nativePtr);
+ }
+
+ @Implementation
+ protected static boolean nStop(long nativePtr) {
+ return AnimatedImageDrawableNatives.nStop(nativePtr);
+ }
+
+ @Implementation
+ protected static int nGetRepeatCount(long nativePtr) {
+ return AnimatedImageDrawableNatives.nGetRepeatCount(nativePtr);
+ }
+
+ @Implementation
+ protected static void nSetRepeatCount(long nativePtr, int repeatCount) {
+ AnimatedImageDrawableNatives.nSetRepeatCount(nativePtr, repeatCount);
+ }
+
+ @Implementation(maxSdk = S_V2)
+ protected static void nSetOnAnimationEndListener(long nativePtr, AnimatedImageDrawable drawable) {
+ AnimatedImageDrawableNatives.nSetOnAnimationEndListener(nativePtr, drawable);
+ }
+
+ @Implementation(minSdk = TIRAMISU)
+ protected static void nSetOnAnimationEndListener(
+ long nativePtr, WeakReference<AnimatedImageDrawable> drawable) {
+ AnimatedImageDrawableNatives.nSetOnAnimationEndListener(nativePtr, drawable.get());
+ }
+
+ @Implementation
+ protected static long nNativeByteSize(long nativePtr) {
+ return AnimatedImageDrawableNatives.nNativeByteSize(nativePtr);
+ }
+
+ @Implementation
+ protected static void nSetMirrored(long nativePtr, boolean mirror) {
+ AnimatedImageDrawableNatives.nSetMirrored(nativePtr, mirror);
+ }
+
+ @Implementation(minSdk = S)
+ protected static void nSetBounds(long nativePtr, Rect rect) {
+ AnimatedImageDrawableNatives.nSetBounds(nativePtr, rect);
+ }
+
+ /** Shadow picker for {@link AnimatedImageDrawable}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(null, ShadowNativeAnimatedImageDrawable.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeAnimatedVectorDrawable.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeAnimatedVectorDrawable.java
new file mode 100644
index 000000000..79b1eafaf
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeAnimatedVectorDrawable.java
@@ -0,0 +1,120 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.N;
+import static android.os.Build.VERSION_CODES.N_MR1;
+import static android.os.Build.VERSION_CODES.O;
+
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.AnimatedVectorDrawable.VectorDrawableAnimatorRT;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.AnimatedVectorDrawableNatives;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.shadows.ShadowNativeAnimatedVectorDrawable.Picker;
+
+/** Shadow for {@link AnimatedVectorDrawable} that is backed by native code */
+@Implements(value = AnimatedVectorDrawable.class, minSdk = O, shadowPicker = Picker.class)
+public class ShadowNativeAnimatedVectorDrawable extends ShadowDrawable {
+
+ @Implementation(minSdk = N)
+ protected static long nCreateAnimatorSet() {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return AnimatedVectorDrawableNatives.nCreateAnimatorSet();
+ }
+
+ @Implementation(minSdk = N_MR1)
+ protected static void nSetVectorDrawableTarget(long animatorPtr, long vectorDrawablePtr) {
+ AnimatedVectorDrawableNatives.nSetVectorDrawableTarget(animatorPtr, vectorDrawablePtr);
+ }
+
+ @Implementation(minSdk = N_MR1)
+ protected static void nAddAnimator(
+ long setPtr,
+ long propertyValuesHolder,
+ long nativeInterpolator,
+ long startDelay,
+ long duration,
+ int repeatCount,
+ int repeatMode) {
+ AnimatedVectorDrawableNatives.nAddAnimator(
+ setPtr,
+ propertyValuesHolder,
+ nativeInterpolator,
+ startDelay,
+ duration,
+ repeatCount,
+ repeatMode);
+ }
+
+ @Implementation(minSdk = N)
+ protected static void nSetPropertyHolderData(long nativePtr, float[] data, int length) {
+ AnimatedVectorDrawableNatives.nSetPropertyHolderData(nativePtr, data, length);
+ }
+
+ @Implementation(minSdk = N_MR1)
+ protected static void nSetPropertyHolderData(long nativePtr, int[] data, int length) {
+ AnimatedVectorDrawableNatives.nSetPropertyHolderData(nativePtr, data, length);
+ }
+
+ @Implementation(minSdk = N)
+ protected static void nStart(long animatorSetPtr, VectorDrawableAnimatorRT set, int id) {
+ AnimatedVectorDrawableNatives.nStart(animatorSetPtr, set, id);
+ }
+
+ @Implementation(minSdk = N)
+ protected static void nReverse(long animatorSetPtr, VectorDrawableAnimatorRT set, int id) {
+ AnimatedVectorDrawableNatives.nReverse(animatorSetPtr, set, id);
+ }
+
+ @Implementation(minSdk = N)
+ protected static long nCreateGroupPropertyHolder(
+ long nativePtr, int propertyId, float startValue, float endValue) {
+ return AnimatedVectorDrawableNatives.nCreateGroupPropertyHolder(
+ nativePtr, propertyId, startValue, endValue);
+ }
+
+ @Implementation(minSdk = N)
+ protected static long nCreatePathDataPropertyHolder(
+ long nativePtr, long startValuePtr, long endValuePtr) {
+ return AnimatedVectorDrawableNatives.nCreatePathDataPropertyHolder(
+ nativePtr, startValuePtr, endValuePtr);
+ }
+
+ @Implementation(minSdk = N)
+ protected static long nCreatePathColorPropertyHolder(
+ long nativePtr, int propertyId, int startValue, int endValue) {
+ return AnimatedVectorDrawableNatives.nCreatePathColorPropertyHolder(
+ nativePtr, propertyId, startValue, endValue);
+ }
+
+ @Implementation(minSdk = N)
+ protected static long nCreatePathPropertyHolder(
+ long nativePtr, int propertyId, float startValue, float endValue) {
+ return AnimatedVectorDrawableNatives.nCreatePathPropertyHolder(
+ nativePtr, propertyId, startValue, endValue);
+ }
+
+ @Implementation(minSdk = N)
+ protected static long nCreateRootAlphaPropertyHolder(
+ long nativePtr, float startValue, float endValue) {
+ return AnimatedVectorDrawableNatives.nCreateRootAlphaPropertyHolder(
+ nativePtr, startValue, endValue);
+ }
+
+ @Implementation(minSdk = N)
+ protected static void nEnd(long animatorSetPtr) {
+ AnimatedVectorDrawableNatives.nEnd(animatorSetPtr);
+ }
+
+ @Implementation(minSdk = N)
+ protected static void nReset(long animatorSetPtr) {
+ AnimatedVectorDrawableNatives.nReset(animatorSetPtr);
+ }
+
+ /** Shadow picker for {@link AnimatedVectorDrawable}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(null, ShadowNativeAnimatedVectorDrawable.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBaseCanvas.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBaseCanvas.java
new file mode 100644
index 000000000..42080522f
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBaseCanvas.java
@@ -0,0 +1,877 @@
+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;
+import static android.os.Build.VERSION_CODES.TIRAMISU;
+import static org.robolectric.util.reflector.Reflector.reflector;
+
+import android.annotation.ColorLong;
+import android.graphics.BaseCanvas;
+import android.graphics.Bitmap;
+import android.graphics.Paint;
+import android.graphics.Path;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.RealObject;
+import org.robolectric.nativeruntime.BaseCanvasNatives;
+import org.robolectric.shadows.ShadowNativeBaseCanvas.Picker;
+import org.robolectric.util.reflector.Accessor;
+import org.robolectric.util.reflector.ForType;
+
+/** Shadow for {@link BaseCanvas} that is backed by native code */
+@Implements(
+ value = BaseCanvas.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ isInAndroidSdk = false)
+public class ShadowNativeBaseCanvas extends ShadowCanvas {
+
+ @RealObject BaseCanvas realBaseCanvas;
+
+ @Implementation(minSdk = Q)
+ protected static void nDrawBitmap(
+ long nativeCanvas,
+ long bitmapHandle,
+ float left,
+ float top,
+ long nativePaintOrZero,
+ int canvasDensity,
+ int screenDensity,
+ int bitmapDensity) {
+ BaseCanvasNatives.nDrawBitmap(
+ nativeCanvas,
+ bitmapHandle,
+ left,
+ top,
+ nativePaintOrZero,
+ canvasDensity,
+ screenDensity,
+ bitmapDensity);
+ }
+
+ @Implementation(minSdk = Q)
+ protected static void nDrawBitmap(
+ long nativeCanvas,
+ long bitmapHandle,
+ float srcLeft,
+ float srcTop,
+ float srcRight,
+ float srcBottom,
+ float dstLeft,
+ float dstTop,
+ float dstRight,
+ float dstBottom,
+ long nativePaintOrZero,
+ int screenDensity,
+ int bitmapDensity) {
+ BaseCanvasNatives.nDrawBitmap(
+ nativeCanvas,
+ bitmapHandle,
+ srcLeft,
+ srcTop,
+ srcRight,
+ srcBottom,
+ dstLeft,
+ dstTop,
+ dstRight,
+ dstBottom,
+ nativePaintOrZero,
+ screenDensity,
+ bitmapDensity);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nDrawBitmap(
+ long nativeCanvas,
+ int[] colors,
+ int offset,
+ int stride,
+ float x,
+ float y,
+ int width,
+ int height,
+ boolean hasAlpha,
+ long nativePaintOrZero) {
+ BaseCanvasNatives.nDrawBitmap(
+ nativeCanvas, colors, offset, stride, x, y, width, height, hasAlpha, nativePaintOrZero);
+ }
+
+ @Implementation(minSdk = O, maxSdk = P)
+ protected static void nDrawBitmap(
+ long nativeCanvas,
+ Bitmap bitmap,
+ float left,
+ float top,
+ long nativePaintOrZero,
+ int canvasDensity,
+ int screenDensity,
+ int bitmapDensity) {
+ BaseCanvasNatives.nDrawBitmap(
+ nativeCanvas,
+ bitmap.getNativeInstance(),
+ left,
+ top,
+ nativePaintOrZero,
+ canvasDensity,
+ screenDensity,
+ bitmapDensity);
+ }
+
+ @Implementation(minSdk = O, maxSdk = P)
+ protected static void nDrawBitmap(
+ long nativeCanvas,
+ Bitmap bitmap,
+ float srcLeft,
+ float srcTop,
+ float srcRight,
+ float srcBottom,
+ float dstLeft,
+ float dstTop,
+ float dstRight,
+ float dstBottom,
+ long nativePaintOrZero,
+ int screenDensity,
+ int bitmapDensity) {
+ BaseCanvasNatives.nDrawBitmap(
+ nativeCanvas,
+ bitmap.getNativeInstance(),
+ srcLeft,
+ srcTop,
+ srcRight,
+ srcBottom,
+ dstLeft,
+ dstTop,
+ dstRight,
+ dstBottom,
+ nativePaintOrZero,
+ screenDensity,
+ bitmapDensity);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nDrawColor(long nativeCanvas, int color, int mode) {
+ BaseCanvasNatives.nDrawColor(nativeCanvas, color, mode);
+ }
+
+ @Implementation(minSdk = Q)
+ protected static void nDrawColor(
+ long nativeCanvas, long nativeColorSpace, @ColorLong long color, int mode) {
+ BaseCanvasNatives.nDrawColor(nativeCanvas, nativeColorSpace, color, mode);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nDrawPaint(long nativeCanvas, long nativePaint) {
+ BaseCanvasNatives.nDrawPaint(nativeCanvas, nativePaint);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nDrawPoint(long canvasHandle, float x, float y, long paintHandle) {
+ BaseCanvasNatives.nDrawPoint(canvasHandle, x, y, paintHandle);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nDrawPoints(
+ long canvasHandle, float[] pts, int offset, int count, long paintHandle) {
+ BaseCanvasNatives.nDrawPoints(canvasHandle, pts, offset, count, paintHandle);
+ }
+
+ @Implementation(minSdk = O)
+ 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)
+ protected static void nDrawLines(
+ long canvasHandle, float[] pts, int offset, int count, long paintHandle) {
+ BaseCanvasNatives.nDrawLines(canvasHandle, pts, offset, count, paintHandle);
+ }
+
+ @Implementation(minSdk = O)
+ 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)
+ 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)
+ protected static void nDrawCircle(
+ long nativeCanvas, float cx, float cy, float radius, long nativePaint) {
+ BaseCanvasNatives.nDrawCircle(nativeCanvas, cx, cy, radius, nativePaint);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nDrawArc(
+ long nativeCanvas,
+ float left,
+ float top,
+ float right,
+ float bottom,
+ float startAngle,
+ float sweep,
+ boolean useCenter,
+ long nativePaint) {
+ BaseCanvasNatives.nDrawArc(
+ nativeCanvas, left, top, right, bottom, startAngle, sweep, useCenter, nativePaint);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nDrawRoundRect(
+ long nativeCanvas,
+ float left,
+ float top,
+ float right,
+ float bottom,
+ float rx,
+ float ry,
+ long nativePaint) {
+ BaseCanvasNatives.nDrawRoundRect(nativeCanvas, left, top, right, bottom, rx, ry, nativePaint);
+ }
+
+ @Implementation(minSdk = Q)
+ protected static void nDrawDoubleRoundRect(
+ long nativeCanvas,
+ float outerLeft,
+ float outerTop,
+ float outerRight,
+ float outerBottom,
+ float outerRx,
+ float outerRy,
+ float innerLeft,
+ float innerTop,
+ float innerRight,
+ float innerBottom,
+ float innerRx,
+ float innerRy,
+ long nativePaint) {
+ BaseCanvasNatives.nDrawDoubleRoundRect(
+ nativeCanvas,
+ outerLeft,
+ outerTop,
+ outerRight,
+ outerBottom,
+ outerRx,
+ outerRy,
+ innerLeft,
+ innerTop,
+ innerRight,
+ innerBottom,
+ innerRx,
+ innerRy,
+ nativePaint);
+ }
+
+ @Implementation(minSdk = Q)
+ protected static void nDrawDoubleRoundRect(
+ long nativeCanvas,
+ float outerLeft,
+ float outerTop,
+ float outerRight,
+ float outerBottom,
+ float[] outerRadii,
+ float innerLeft,
+ float innerTop,
+ float innerRight,
+ float innerBottom,
+ float[] innerRadii,
+ long nativePaint) {
+ BaseCanvasNatives.nDrawDoubleRoundRect(
+ nativeCanvas,
+ outerLeft,
+ outerTop,
+ outerRight,
+ outerBottom,
+ outerRadii,
+ innerLeft,
+ innerTop,
+ innerRight,
+ innerBottom,
+ innerRadii,
+ nativePaint);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nDrawPath(long nativeCanvas, long nativePath, long nativePaint) {
+ BaseCanvasNatives.nDrawPath(nativeCanvas, nativePath, nativePaint);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nDrawRegion(long nativeCanvas, long nativeRegion, long nativePaint) {
+ BaseCanvasNatives.nDrawRegion(nativeCanvas, nativeRegion, nativePaint);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nDrawNinePatch(
+ long nativeCanvas,
+ long nativeBitmap,
+ long ninePatch,
+ float dstLeft,
+ float dstTop,
+ float dstRight,
+ float dstBottom,
+ long nativePaintOrZero,
+ int screenDensity,
+ int bitmapDensity) {
+ BaseCanvasNatives.nDrawNinePatch(
+ nativeCanvas,
+ nativeBitmap,
+ ninePatch,
+ dstLeft,
+ dstTop,
+ dstRight,
+ dstBottom,
+ nativePaintOrZero,
+ screenDensity,
+ bitmapDensity);
+ }
+
+ @Implementation(minSdk = Q)
+ protected static void nDrawBitmapMatrix(
+ long nativeCanvas, long bitmapHandle, long nativeMatrix, long nativePaint) {
+ BaseCanvasNatives.nDrawBitmapMatrix(nativeCanvas, bitmapHandle, nativeMatrix, nativePaint);
+ }
+
+ @Implementation(minSdk = O, maxSdk = P)
+ protected static void nDrawBitmapMatrix(
+ long nativeCanvas, Bitmap bitmap, long nativeMatrix, long nativePaint) {
+ BaseCanvasNatives.nDrawBitmapMatrix(
+ nativeCanvas, bitmap.getNativeInstance(), nativeMatrix, nativePaint);
+ }
+
+ @Implementation(minSdk = Q)
+ protected static void nDrawBitmapMesh(
+ long nativeCanvas,
+ long bitmapHandle,
+ int meshWidth,
+ int meshHeight,
+ float[] verts,
+ int vertOffset,
+ int[] colors,
+ int colorOffset,
+ long nativePaint) {
+ BaseCanvasNatives.nDrawBitmapMesh(
+ nativeCanvas,
+ bitmapHandle,
+ meshWidth,
+ meshHeight,
+ verts,
+ vertOffset,
+ colors,
+ colorOffset,
+ nativePaint);
+ }
+
+ @Implementation(minSdk = O, maxSdk = P)
+ protected static void nDrawBitmapMesh(
+ long nativeCanvas,
+ Bitmap bitmap,
+ int meshWidth,
+ int meshHeight,
+ float[] verts,
+ int vertOffset,
+ int[] colors,
+ int colorOffset,
+ long nativePaint) {
+ BaseCanvasNatives.nDrawBitmapMesh(
+ nativeCanvas,
+ bitmap.getNativeInstance(),
+ meshWidth,
+ meshHeight,
+ verts,
+ vertOffset,
+ colors,
+ colorOffset,
+ nativePaint);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nDrawVertices(
+ long nativeCanvas,
+ int mode,
+ int n,
+ float[] verts,
+ int vertOffset,
+ float[] texs,
+ int texOffset,
+ int[] colors,
+ int colorOffset,
+ short[] indices,
+ int indexOffset,
+ int indexCount,
+ long nativePaint) {
+ BaseCanvasNatives.nDrawVertices(
+ nativeCanvas,
+ mode,
+ n,
+ verts,
+ vertOffset,
+ texs,
+ texOffset,
+ colors,
+ colorOffset,
+ indices,
+ indexOffset,
+ indexCount,
+ nativePaint);
+ }
+
+ @Implementation(minSdk = S)
+ protected static void nDrawGlyphs(
+ long nativeCanvas,
+ int[] glyphIds,
+ float[] positions,
+ int glyphIdStart,
+ int positionStart,
+ int glyphCount,
+ long nativeFont,
+ long nativePaint) {
+ BaseCanvasNatives.nDrawGlyphs(
+ nativeCanvas,
+ glyphIds,
+ positions,
+ glyphIdStart,
+ positionStart,
+ glyphCount,
+ nativeFont,
+ nativePaint);
+ }
+
+ @Implementation(minSdk = P)
+ protected static void nDrawText(
+ long nativeCanvas,
+ char[] text,
+ int index,
+ int count,
+ float x,
+ float y,
+ int flags,
+ long nativePaint) {
+ // This native code calls Typeface::resolveDefault, which requires Typeface clinit to have run.
+ ShadowNativeTypeface.ensureInitialized();
+ BaseCanvasNatives.nDrawText(nativeCanvas, text, index, count, x, y, flags, nativePaint);
+ }
+
+ @Implementation(minSdk = P)
+ protected static void nDrawText(
+ long nativeCanvas,
+ String text,
+ int start,
+ int end,
+ float x,
+ float y,
+ int flags,
+ long nativePaint) {
+ // This native code calls Typeface::resolveDefault, which requires Typeface clinit to have run.
+ ShadowNativeTypeface.ensureInitialized();
+ BaseCanvasNatives.nDrawText(nativeCanvas, text, start, end, x, y, flags, nativePaint);
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static void nDrawText(
+ long nativeCanvas,
+ char[] text,
+ int index,
+ int count,
+ float x,
+ float y,
+ int flags,
+ long nativePaint,
+ long nativeTypeface) {
+ // This native code calls Typeface::resolveDefault, which requires Typeface clinit to have run.
+ ShadowNativeTypeface.ensureInitialized();
+ BaseCanvasNatives.nDrawText(
+ nativeCanvas, text, index, count, x, y, flags, nativePaint, nativeTypeface);
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static void nDrawText(
+ long nativeCanvas,
+ String text,
+ int start,
+ int end,
+ float x,
+ float y,
+ int flags,
+ long nativePaint,
+ long nativeTypeface) {
+ // This native code calls Typeface::resolveDefault, which requires Typeface clinit to have run.
+ ShadowNativeTypeface.ensureInitialized();
+ BaseCanvasNatives.nDrawText(
+ nativeCanvas, text, start, end, x, y, flags, nativePaint, nativeTypeface);
+ }
+
+ @Implementation(minSdk = P)
+ protected static void nDrawTextRun(
+ long nativeCanvas,
+ String text,
+ int start,
+ int end,
+ int contextStart,
+ int contextEnd,
+ float x,
+ float y,
+ boolean isRtl,
+ long nativePaint) {
+ // This native code calls Typeface::resolveDefault, which requires Typeface clinit to have run.
+ ShadowNativeTypeface.ensureInitialized();
+ BaseCanvasNatives.nDrawTextRun(
+ nativeCanvas, text, start, end, contextStart, contextEnd, x, y, isRtl, nativePaint);
+ }
+
+ /**
+ * 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)
+ protected static void nDrawTextRun(
+ long nativeCanvas,
+ char[] text,
+ int start,
+ int count,
+ int contextStart,
+ int contextCount,
+ float x,
+ float y,
+ boolean isRtl,
+ long nativePaint,
+ long nativeTypefaceOrPrecomputedText) {
+ // This native code calls Typeface::resolveDefault, which requires Typeface clinit to have run.
+ ShadowNativeTypeface.ensureInitialized();
+ if (RuntimeEnvironment.getApiLevel() >= P) {
+ BaseCanvasNatives.nDrawTextRun(
+ nativeCanvas,
+ text,
+ start,
+ count,
+ contextStart,
+ contextCount,
+ x,
+ y,
+ isRtl,
+ nativePaint,
+ nativeTypefaceOrPrecomputedText);
+ } else {
+ BaseCanvasNatives.nDrawTextRunTypeface(
+ nativeCanvas,
+ text,
+ start,
+ count,
+ contextStart,
+ contextCount,
+ x,
+ y,
+ isRtl,
+ nativePaint,
+ nativeTypefaceOrPrecomputedText);
+ }
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static void nDrawTextRun(
+ long nativeCanvas,
+ String text,
+ int start,
+ int end,
+ int contextStart,
+ int contextEnd,
+ float x,
+ float y,
+ boolean isRtl,
+ long nativePaint,
+ long nativeTypeface) {
+ // This native code calls Typeface::resolveDefault, which requires Typeface clinit to have run.
+ ShadowNativeTypeface.ensureInitialized();
+ BaseCanvasNatives.nDrawTextRun(
+ nativeCanvas,
+ text,
+ start,
+ end,
+ contextStart,
+ contextEnd,
+ x,
+ y,
+ isRtl,
+ nativePaint,
+ nativeTypeface);
+ }
+
+ @Implementation(minSdk = P)
+ protected static void nDrawTextOnPath(
+ long nativeCanvas,
+ char[] text,
+ int index,
+ int count,
+ long nativePath,
+ float hOffset,
+ float vOffset,
+ int bidiFlags,
+ long nativePaint) {
+ // This native code calls Typeface::resolveDefault, which requires Typeface clinit to have run.
+ ShadowNativeTypeface.ensureInitialized();
+ BaseCanvasNatives.nDrawTextOnPath(
+ nativeCanvas, text, index, count, nativePath, hOffset, vOffset, bidiFlags, nativePaint);
+ }
+
+ @Implementation(minSdk = P)
+ protected static void nDrawTextOnPath(
+ long nativeCanvas,
+ String text,
+ long nativePath,
+ float hOffset,
+ float vOffset,
+ int flags,
+ long nativePaint) {
+ // This native code calls Typeface::resolveDefault, which requires Typeface clinit to have run.
+ ShadowNativeTypeface.ensureInitialized();
+ BaseCanvasNatives.nDrawTextOnPath(
+ nativeCanvas, text, nativePath, hOffset, vOffset, flags, nativePaint);
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static void nDrawTextOnPath(
+ long nativeCanvas,
+ char[] text,
+ int index,
+ int count,
+ long nativePath,
+ float hOffset,
+ float vOffset,
+ int bidiFlags,
+ long nativePaint,
+ long nativeTypeface) {
+ // This native code calls Typeface::resolveDefault, which requires Typeface clinit to have run.
+ ShadowNativeTypeface.ensureInitialized();
+ BaseCanvasNatives.nDrawTextOnPath(
+ nativeCanvas,
+ text,
+ index,
+ count,
+ nativePath,
+ hOffset,
+ vOffset,
+ bidiFlags,
+ nativePaint,
+ nativeTypeface);
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static void nDrawTextOnPath(
+ long nativeCanvas,
+ String text,
+ long nativePath,
+ float hOffset,
+ float vOffset,
+ int flags,
+ long nativePaint,
+ long nativeTypeface) {
+ // This native code calls Typeface::resolveDefault, which requires Typeface clinit to have run.
+ ShadowNativeTypeface.ensureInitialized();
+ BaseCanvasNatives.nDrawTextOnPath(
+ nativeCanvas, text, nativePath, hOffset, vOffset, flags, nativePaint, nativeTypeface);
+ }
+
+ @Implementation(minSdk = S, maxSdk = TIRAMISU)
+ protected static void nPunchHole(
+ long renderer, float left, float top, float right, float bottom, float rx, float ry) {
+ BaseCanvasNatives.nPunchHole(renderer, left, top, right, bottom, rx, ry);
+ }
+
+ @Implementation(minSdk = 10000)
+ protected static void nPunchHole(
+ long renderer,
+ float left,
+ float top,
+ float right,
+ float bottom,
+ float rx,
+ float ry,
+ float alpha) {
+ nPunchHole(renderer, left, top, right, bottom, rx, ry);
+ }
+
+ long getNativeCanvas() {
+ return reflector(BaseCanvasReflector.class, realBaseCanvas).getNativeCanvas();
+ }
+
+ @Override
+ public void appendDescription(String s) {
+ throw new UnsupportedOperationException(
+ "Legacy ShadowCanvas description APIs are not supported");
+ }
+
+ @Override
+ public String getDescription() {
+ throw new UnsupportedOperationException(
+ "Legacy ShadowCanvas description APIs are not supported");
+ }
+
+ @Override
+ public int getPathPaintHistoryCount() {
+ throw new UnsupportedOperationException(
+ "Legacy ShadowCanvas description APIs are not supported");
+ }
+
+ @Override
+ public int getCirclePaintHistoryCount() {
+ throw new UnsupportedOperationException(
+ "Legacy ShadowCanvas description APIs are not supported");
+ }
+
+ @Override
+ public int getArcPaintHistoryCount() {
+ throw new UnsupportedOperationException(
+ "Legacy ShadowCanvas description APIs are not supported");
+ }
+
+ @Override
+ public boolean hasDrawnPath() {
+ throw new UnsupportedOperationException(
+ "Legacy ShadowCanvas description APIs are not supported");
+ }
+
+ @Override
+ public boolean hasDrawnCircle() {
+ throw new UnsupportedOperationException(
+ "Legacy ShadowCanvas description APIs are not supported");
+ }
+
+ @Override
+ public Paint getDrawnPathPaint(int i) {
+ throw new UnsupportedOperationException(
+ "Legacy ShadowCanvas description APIs are not supported");
+ }
+
+ @Override
+ public Path getDrawnPath(int i) {
+ throw new UnsupportedOperationException(
+ "Legacy ShadowCanvas description APIs are not supported");
+ }
+
+ @Override
+ public CirclePaintHistoryEvent getDrawnCircle(int i) {
+ throw new UnsupportedOperationException(
+ "Legacy ShadowCanvas description APIs are not supported");
+ }
+
+ @Override
+ public ArcPaintHistoryEvent getDrawnArc(int i) {
+ throw new UnsupportedOperationException(
+ "Legacy ShadowCanvas description APIs are not supported");
+ }
+
+ @Override
+ public void resetCanvasHistory() {
+ throw new UnsupportedOperationException(
+ "Legacy ShadowCanvas description APIs are not supported");
+ }
+
+ @Override
+ public Paint getDrawnPaint() {
+ throw new UnsupportedOperationException(
+ "Legacy ShadowCanvas description APIs are not supported");
+ }
+
+ @Override
+ public void setHeight(int height) {
+ throw new UnsupportedOperationException("setHeight is not supported in native Canvas");
+ }
+
+ @Override
+ public void setWidth(int width) {
+ throw new UnsupportedOperationException("setWidth is not supported in native Canvas");
+ }
+
+ @Override
+ public TextHistoryEvent getDrawnTextEvent(int i) {
+ throw new UnsupportedOperationException(
+ "Legacy ShadowCanvas description APIs are not supported");
+ }
+
+ @Override
+ public int getTextHistoryCount() {
+ throw new UnsupportedOperationException(
+ "Legacy ShadowCanvas description APIs are not supported");
+ }
+
+ @Override
+ public RectPaintHistoryEvent getDrawnRect(int i) {
+ throw new UnsupportedOperationException(
+ "Legacy ShadowCanvas description APIs are not supported");
+ }
+
+ @Override
+ public RectPaintHistoryEvent getLastDrawnRect() {
+ throw new UnsupportedOperationException(
+ "Legacy ShadowCanvas description APIs are not supported");
+ }
+
+ @Override
+ public int getRectPaintHistoryCount() {
+ throw new UnsupportedOperationException(
+ "Legacy ShadowCanvas description APIs are not supported");
+ }
+
+ @Override
+ public RoundRectPaintHistoryEvent getDrawnRoundRect(int i) {
+ throw new UnsupportedOperationException(
+ "Legacy ShadowCanvas description APIs are not supported");
+ }
+
+ @Override
+ public RoundRectPaintHistoryEvent getLastDrawnRoundRect() {
+ throw new UnsupportedOperationException(
+ "Legacy ShadowCanvas description APIs are not supported");
+ }
+
+ @Override
+ public int getRoundRectPaintHistoryCount() {
+ throw new UnsupportedOperationException(
+ "Legacy ShadowCanvas description APIs are not supported");
+ }
+
+ @Override
+ public LinePaintHistoryEvent getDrawnLine(int i) {
+ throw new UnsupportedOperationException(
+ "Legacy ShadowCanvas description APIs are not supported");
+ }
+
+ @Override
+ public int getLinePaintHistoryCount() {
+ throw new UnsupportedOperationException(
+ "Legacy ShadowCanvas description APIs are not supported");
+ }
+
+ @Override
+ public int getOvalPaintHistoryCount() {
+ throw new UnsupportedOperationException(
+ "Legacy ShadowCanvas description APIs are not supported");
+ }
+
+ @Override
+ public OvalPaintHistoryEvent getDrawnOval(int i) {
+ throw new UnsupportedOperationException(
+ "Legacy ShadowCanvas description APIs are not supported");
+ }
+
+ @ForType(BaseCanvas.class)
+ interface BaseCanvasReflector {
+ @Accessor("mNativeCanvasWrapper")
+ long getNativeCanvas();
+ }
+
+ /** Shadow picker for {@link BaseCanvas}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(null, ShadowNativeBaseCanvas.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBaseRecordingCanvas.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBaseRecordingCanvas.java
new file mode 100644
index 000000000..1f061b53e
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBaseRecordingCanvas.java
@@ -0,0 +1,597 @@
+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;
+import static android.os.Build.VERSION_CODES.TIRAMISU;
+
+import android.annotation.ColorLong;
+import android.graphics.BaseRecordingCanvas;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.BaseRecordingCanvasNatives;
+import org.robolectric.shadows.ShadowNativeBaseRecordingCanvas.Picker;
+
+/** Shadow for {@link BaseRecordingCanvas} that is backed by native code */
+@Implements(
+ value = BaseRecordingCanvas.class,
+ minSdk = Q,
+ shadowPicker = Picker.class,
+ isInAndroidSdk = false)
+public class ShadowNativeBaseRecordingCanvas extends ShadowNativeCanvas {
+
+ @Implementation
+ protected static void nDrawBitmap(
+ long nativeCanvas,
+ long bitmapHandle,
+ float left,
+ float top,
+ long nativePaintOrZero,
+ int canvasDensity,
+ int screenDensity,
+ int bitmapDensity) {
+ BaseRecordingCanvasNatives.nDrawBitmap(
+ nativeCanvas,
+ bitmapHandle,
+ left,
+ top,
+ nativePaintOrZero,
+ canvasDensity,
+ screenDensity,
+ bitmapDensity);
+ }
+
+ @Implementation
+ protected static void nDrawBitmap(
+ long nativeCanvas,
+ long bitmapHandle,
+ float srcLeft,
+ float srcTop,
+ float srcRight,
+ float srcBottom,
+ float dstLeft,
+ float dstTop,
+ float dstRight,
+ float dstBottom,
+ long nativePaintOrZero,
+ int screenDensity,
+ int bitmapDensity) {
+ BaseRecordingCanvasNatives.nDrawBitmap(
+ nativeCanvas,
+ bitmapHandle,
+ srcLeft,
+ srcTop,
+ srcRight,
+ srcBottom,
+ dstLeft,
+ dstTop,
+ dstRight,
+ dstBottom,
+ nativePaintOrZero,
+ screenDensity,
+ bitmapDensity);
+ }
+
+ @Implementation
+ protected static void nDrawBitmap(
+ long nativeCanvas,
+ int[] colors,
+ int offset,
+ int stride,
+ float x,
+ float y,
+ int width,
+ int height,
+ boolean hasAlpha,
+ long nativePaintOrZero) {
+ BaseRecordingCanvasNatives.nDrawBitmap(
+ nativeCanvas, colors, offset, stride, x, y, width, height, hasAlpha, nativePaintOrZero);
+ }
+
+ @Implementation
+ protected static void nDrawColor(long nativeCanvas, int color, int mode) {
+ BaseRecordingCanvasNatives.nDrawColor(nativeCanvas, color, mode);
+ }
+
+ @Implementation
+ protected static void nDrawColor(
+ long nativeCanvas, long nativeColorSpace, @ColorLong long color, int mode) {
+ BaseRecordingCanvasNatives.nDrawColor(nativeCanvas, nativeColorSpace, color, mode);
+ }
+
+ @Implementation
+ protected static void nDrawPaint(long nativeCanvas, long nativePaint) {
+ BaseRecordingCanvasNatives.nDrawPaint(nativeCanvas, nativePaint);
+ }
+
+ @Implementation
+ protected static void nDrawPoint(long canvasHandle, float x, float y, long paintHandle) {
+ BaseRecordingCanvasNatives.nDrawPoint(canvasHandle, x, y, paintHandle);
+ }
+
+ @Implementation
+ protected static void nDrawPoints(
+ long canvasHandle, float[] pts, int offset, int count, long paintHandle) {
+ BaseRecordingCanvasNatives.nDrawPoints(canvasHandle, pts, offset, count, paintHandle);
+ }
+
+ @Implementation
+ 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
+ protected static void nDrawLines(
+ long canvasHandle, float[] pts, int offset, int count, long paintHandle) {
+ BaseRecordingCanvasNatives.nDrawLines(canvasHandle, pts, offset, count, paintHandle);
+ }
+
+ @Implementation
+ 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
+ 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
+ protected static void nDrawCircle(
+ long nativeCanvas, float cx, float cy, float radius, long nativePaint) {
+ BaseRecordingCanvasNatives.nDrawCircle(nativeCanvas, cx, cy, radius, nativePaint);
+ }
+
+ @Implementation
+ protected static void nDrawArc(
+ long nativeCanvas,
+ float left,
+ float top,
+ float right,
+ float bottom,
+ float startAngle,
+ float sweep,
+ boolean useCenter,
+ long nativePaint) {
+ BaseRecordingCanvasNatives.nDrawArc(
+ nativeCanvas, left, top, right, bottom, startAngle, sweep, useCenter, nativePaint);
+ }
+
+ @Implementation
+ protected static void nDrawRoundRect(
+ long nativeCanvas,
+ float left,
+ float top,
+ float right,
+ float bottom,
+ float rx,
+ float ry,
+ long nativePaint) {
+ BaseRecordingCanvasNatives.nDrawRoundRect(
+ nativeCanvas, left, top, right, bottom, rx, ry, nativePaint);
+ }
+
+ @Implementation
+ protected static void nDrawDoubleRoundRect(
+ long nativeCanvas,
+ float outerLeft,
+ float outerTop,
+ float outerRight,
+ float outerBottom,
+ float outerRx,
+ float outerRy,
+ float innerLeft,
+ float innerTop,
+ float innerRight,
+ float innerBottom,
+ float innerRx,
+ float innerRy,
+ long nativePaint) {
+ BaseRecordingCanvasNatives.nDrawDoubleRoundRect(
+ nativeCanvas,
+ outerLeft,
+ outerTop,
+ outerRight,
+ outerBottom,
+ outerRx,
+ outerRy,
+ innerLeft,
+ innerTop,
+ innerRight,
+ innerBottom,
+ innerRx,
+ innerRy,
+ nativePaint);
+ }
+
+ @Implementation
+ protected static void nDrawDoubleRoundRect(
+ long nativeCanvas,
+ float outerLeft,
+ float outerTop,
+ float outerRight,
+ float outerBottom,
+ float[] outerRadii,
+ float innerLeft,
+ float innerTop,
+ float innerRight,
+ float innerBottom,
+ float[] innerRadii,
+ long nativePaint) {
+ BaseRecordingCanvasNatives.nDrawDoubleRoundRect(
+ nativeCanvas,
+ outerLeft,
+ outerTop,
+ outerRight,
+ outerBottom,
+ outerRadii,
+ innerLeft,
+ innerTop,
+ innerRight,
+ innerBottom,
+ innerRadii,
+ nativePaint);
+ }
+
+ @Implementation
+ protected static void nDrawPath(long nativeCanvas, long nativePath, long nativePaint) {
+ BaseRecordingCanvasNatives.nDrawPath(nativeCanvas, nativePath, nativePaint);
+ }
+
+ @Implementation
+ protected static void nDrawRegion(long nativeCanvas, long nativeRegion, long nativePaint) {
+ BaseRecordingCanvasNatives.nDrawRegion(nativeCanvas, nativeRegion, nativePaint);
+ }
+
+ @Implementation
+ protected static void nDrawNinePatch(
+ long nativeCanvas,
+ long nativeBitmap,
+ long ninePatch,
+ float dstLeft,
+ float dstTop,
+ float dstRight,
+ float dstBottom,
+ long nativePaintOrZero,
+ int screenDensity,
+ int bitmapDensity) {
+ BaseRecordingCanvasNatives.nDrawNinePatch(
+ nativeCanvas,
+ nativeBitmap,
+ ninePatch,
+ dstLeft,
+ dstTop,
+ dstRight,
+ dstBottom,
+ nativePaintOrZero,
+ screenDensity,
+ bitmapDensity);
+ }
+
+ @Implementation
+ protected static void nDrawBitmapMatrix(
+ long nativeCanvas, long bitmapHandle, long nativeMatrix, long nativePaint) {
+ BaseRecordingCanvasNatives.nDrawBitmapMatrix(
+ nativeCanvas, bitmapHandle, nativeMatrix, nativePaint);
+ }
+
+ @Implementation
+ protected static void nDrawBitmapMesh(
+ long nativeCanvas,
+ long bitmapHandle,
+ int meshWidth,
+ int meshHeight,
+ float[] verts,
+ int vertOffset,
+ int[] colors,
+ int colorOffset,
+ long nativePaint) {
+ BaseRecordingCanvasNatives.nDrawBitmapMesh(
+ nativeCanvas,
+ bitmapHandle,
+ meshWidth,
+ meshHeight,
+ verts,
+ vertOffset,
+ colors,
+ colorOffset,
+ nativePaint);
+ }
+
+ @Implementation
+ protected static void nDrawVertices(
+ long nativeCanvas,
+ int mode,
+ int n,
+ float[] verts,
+ int vertOffset,
+ float[] texs,
+ int texOffset,
+ int[] colors,
+ int colorOffset,
+ short[] indices,
+ int indexOffset,
+ int indexCount,
+ long nativePaint) {
+ BaseRecordingCanvasNatives.nDrawVertices(
+ nativeCanvas,
+ mode,
+ n,
+ verts,
+ vertOffset,
+ texs,
+ texOffset,
+ colors,
+ colorOffset,
+ indices,
+ indexOffset,
+ indexCount,
+ nativePaint);
+ }
+
+ @Implementation(minSdk = S)
+ protected static void nDrawGlyphs(
+ long nativeCanvas,
+ int[] glyphIds,
+ float[] positions,
+ int glyphIdStart,
+ int positionStart,
+ int glyphCount,
+ long nativeFont,
+ long nativePaint) {
+ BaseRecordingCanvasNatives.nDrawGlyphs(
+ nativeCanvas,
+ glyphIds,
+ positions,
+ glyphIdStart,
+ positionStart,
+ glyphCount,
+ nativeFont,
+ nativePaint);
+ }
+
+ @Implementation
+ protected static void nDrawText(
+ long nativeCanvas,
+ char[] text,
+ int index,
+ int count,
+ float x,
+ float y,
+ int flags,
+ long nativePaint) {
+ BaseRecordingCanvasNatives.nDrawText(
+ nativeCanvas, text, index, count, x, y, flags, nativePaint);
+ }
+
+ @Implementation
+ protected static void nDrawText(
+ long nativeCanvas,
+ String text,
+ int start,
+ int end,
+ float x,
+ float y,
+ int flags,
+ long nativePaint) {
+ BaseRecordingCanvasNatives.nDrawText(nativeCanvas, text, start, end, x, y, flags, nativePaint);
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static void nDrawText(
+ long nativeCanvas,
+ char[] text,
+ int index,
+ int count,
+ float x,
+ float y,
+ int flags,
+ long nativePaint,
+ long nativeTypeface) {
+ BaseRecordingCanvasNatives.nDrawText(
+ nativeCanvas, text, index, count, x, y, flags, nativePaint, nativeTypeface);
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static void nDrawText(
+ long nativeCanvas,
+ String text,
+ int start,
+ int end,
+ float x,
+ float y,
+ int flags,
+ long nativePaint,
+ long nativeTypeface) {
+ BaseRecordingCanvasNatives.nDrawText(
+ nativeCanvas, text, start, end, x, y, flags, nativePaint, nativeTypeface);
+ }
+
+ @Implementation
+ protected static void nDrawTextRun(
+ long nativeCanvas,
+ String text,
+ int start,
+ int end,
+ int contextStart,
+ int contextEnd,
+ float x,
+ float y,
+ boolean isRtl,
+ long nativePaint) {
+ BaseRecordingCanvasNatives.nDrawTextRun(
+ nativeCanvas, text, start, end, contextStart, contextEnd, x, y, isRtl, nativePaint);
+ }
+
+ /**
+ * 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)
+ protected static void nDrawTextRun(
+ long nativeCanvas,
+ char[] text,
+ int start,
+ int count,
+ int contextStart,
+ int contextCount,
+ float x,
+ float y,
+ boolean isRtl,
+ long nativePaint,
+ long nativeTypefaceOrPrecomputedText) {
+ if (RuntimeEnvironment.getApiLevel() >= P) {
+ BaseRecordingCanvasNatives.nDrawTextRun(
+ nativeCanvas,
+ text,
+ start,
+ count,
+ contextStart,
+ contextCount,
+ x,
+ y,
+ isRtl,
+ nativePaint,
+ nativeTypefaceOrPrecomputedText);
+ } else {
+ BaseRecordingCanvasNatives.nDrawTextRunTypeface(
+ nativeCanvas,
+ text,
+ start,
+ count,
+ contextStart,
+ contextCount,
+ x,
+ y,
+ isRtl,
+ nativePaint,
+ nativeTypefaceOrPrecomputedText);
+ }
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static void nDrawTextRun(
+ long nativeCanvas,
+ String text,
+ int start,
+ int end,
+ int contextStart,
+ int contextEnd,
+ float x,
+ float y,
+ boolean isRtl,
+ long nativePaint,
+ long nativeTypeface) {
+ BaseRecordingCanvasNatives.nDrawTextRun(
+ nativeCanvas,
+ text,
+ start,
+ end,
+ contextStart,
+ contextEnd,
+ x,
+ y,
+ isRtl,
+ nativePaint,
+ nativeTypeface);
+ }
+
+ @Implementation
+ protected static void nDrawTextOnPath(
+ long nativeCanvas,
+ char[] text,
+ int index,
+ int count,
+ long nativePath,
+ float hOffset,
+ float vOffset,
+ int bidiFlags,
+ long nativePaint) {
+ BaseRecordingCanvasNatives.nDrawTextOnPath(
+ nativeCanvas, text, index, count, nativePath, hOffset, vOffset, bidiFlags, nativePaint);
+ }
+
+ @Implementation
+ protected static void nDrawTextOnPath(
+ long nativeCanvas,
+ String text,
+ long nativePath,
+ float hOffset,
+ float vOffset,
+ int flags,
+ long nativePaint) {
+ BaseRecordingCanvasNatives.nDrawTextOnPath(
+ nativeCanvas, text, nativePath, hOffset, vOffset, flags, nativePaint);
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static void nDrawTextOnPath(
+ long nativeCanvas,
+ char[] text,
+ int index,
+ int count,
+ long nativePath,
+ float hOffset,
+ float vOffset,
+ int bidiFlags,
+ long nativePaint,
+ long nativeTypeface) {
+ BaseRecordingCanvasNatives.nDrawTextOnPath(
+ nativeCanvas,
+ text,
+ index,
+ count,
+ nativePath,
+ hOffset,
+ vOffset,
+ bidiFlags,
+ nativePaint,
+ nativeTypeface);
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static void nDrawTextOnPath(
+ long nativeCanvas,
+ String text,
+ long nativePath,
+ float hOffset,
+ float vOffset,
+ int flags,
+ long nativePaint,
+ long nativeTypeface) {
+ BaseRecordingCanvasNatives.nDrawTextOnPath(
+ nativeCanvas, text, nativePath, hOffset, vOffset, flags, nativePaint, nativeTypeface);
+ }
+
+ @Implementation(minSdk = S, maxSdk = TIRAMISU)
+ protected static void nPunchHole(
+ long renderer, float left, float top, float right, float bottom, float rx, float ry) {
+ BaseRecordingCanvasNatives.nPunchHole(renderer, left, top, right, bottom, rx, ry);
+ }
+
+ @Implementation(minSdk = 10000)
+ protected static void nPunchHole(
+ long renderer,
+ float left,
+ float top,
+ float right,
+ float bottom,
+ float rx,
+ float ry,
+ float alpha) {
+ nPunchHole(renderer, left, top, right, bottom, rx, ry);
+ }
+
+ /** Shadow picker for {@link BaseRecordingCanvas}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(null, ShadowNativeBaseRecordingCanvas.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmap.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmap.java
new file mode 100644
index 000000000..cddbd4440
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmap.java
@@ -0,0 +1,504 @@
+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;
+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;
+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.graphics.Bitmap;
+import android.graphics.ColorSpace;
+import android.graphics.ColorSpace.Rgb.TransferParameters;
+import android.graphics.Matrix;
+import android.hardware.HardwareBuffer;
+import android.os.Parcel;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.Buffer;
+import java.util.ArrayList;
+import java.util.Collections;
+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.nativeruntime.BitmapNatives;
+import org.robolectric.nativeruntime.ColorSpaceRgbNatives;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.NativeAllocationRegistryNatives;
+import org.robolectric.util.reflector.Accessor;
+import org.robolectric.util.reflector.ForType;
+import org.robolectric.util.reflector.Static;
+
+/** Shadow for {@link Bitmap} that is backed by native code */
+@Implements(value = Bitmap.class, looseSignatures = true, minSdk = O, isInAndroidSdk = false)
+public class ShadowNativeBitmap extends ShadowBitmap {
+
+ @RealObject Bitmap realBitmap;
+
+ private int createdFromResId;
+
+ private static final List<Long> colorSpaceAllocationsP =
+ Collections.synchronizedList(new ArrayList<>());
+
+ /** Called by {@link ShadowNativeBitmapFactory}. */
+ void setCreatedFromResId(int createdFromResId) {
+ this.createdFromResId = createdFromResId;
+ }
+
+ @Implementation(minSdk = Q)
+ protected static Bitmap nativeCreate(
+ int[] colors,
+ int offset,
+ int stride,
+ int width,
+ int height,
+ int nativeConfig,
+ boolean mutable,
+ long nativeColorSpace) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return BitmapNatives.nativeCreate(
+ colors, offset, stride, width, height, nativeConfig, mutable, nativeColorSpace);
+ }
+
+ @Implementation(minSdk = O, maxSdk = P)
+ protected static Bitmap nativeCreate(
+ int[] colors,
+ int offset,
+ int stride,
+ int width,
+ int height,
+ int nativeConfig,
+ boolean mutable,
+ float[] xyzD50,
+ ColorSpace.Rgb.TransferParameters p) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ long colorSpacePtr = 0;
+ if (xyzD50 != null && p != null) {
+ colorSpacePtr =
+ ColorSpaceRgbNatives.nativeCreate(
+ (float) p.a,
+ (float) p.b,
+ (float) p.c,
+ (float) p.d,
+ (float) p.e,
+ (float) p.f,
+ (float) p.g,
+ xyzD50);
+ colorSpaceAllocationsP.add(colorSpacePtr);
+ }
+ return nativeCreate(
+ colors, offset, stride, width, height, nativeConfig, mutable, colorSpacePtr);
+ }
+
+ @Implementation(minSdk = LOLLIPOP)
+ protected static Bitmap nativeCopy(long nativeSrcBitmap, int nativeConfig, boolean isMutable) {
+ return BitmapNatives.nativeCopy(nativeSrcBitmap, nativeConfig, isMutable);
+ }
+
+ @Implementation(minSdk = M)
+ protected static Bitmap nativeCopyAshmem(long nativeSrcBitmap) {
+ return BitmapNatives.nativeCopyAshmem(nativeSrcBitmap);
+ }
+
+ @Implementation(minSdk = N)
+ protected static Bitmap nativeCopyAshmemConfig(long nativeSrcBitmap, int nativeConfig) {
+ return BitmapNatives.nativeCopyAshmemConfig(nativeSrcBitmap, nativeConfig);
+ }
+
+ @Implementation(minSdk = N)
+ protected static long nativeGetNativeFinalizer() {
+ return BitmapNatives.nativeGetNativeFinalizer();
+ }
+
+ @Implementation(minSdk = LOLLIPOP)
+ protected static Object nativeRecycle(Object nativeBitmap) {
+ BitmapNatives.nativeRecycle((long) nativeBitmap);
+ return true;
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nativeReconfigure(
+ long nativeBitmap, int width, int height, int config, boolean isPremultiplied) {
+ BitmapNatives.nativeReconfigure(nativeBitmap, width, height, config, isPremultiplied);
+ }
+
+ @Implementation(minSdk = LOLLIPOP)
+ 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)
+ protected static void nativeErase(long nativeBitmap, int color) {
+ BitmapNatives.nativeErase(nativeBitmap, color);
+ }
+
+ @Implementation(minSdk = Q)
+ protected static void nativeErase(long nativeBitmap, long colorSpacePtr, long color) {
+ BitmapNatives.nativeErase(nativeBitmap, colorSpacePtr, color);
+ }
+
+ @Implementation(minSdk = LOLLIPOP)
+ protected static int nativeRowBytes(long nativeBitmap) {
+ return BitmapNatives.nativeRowBytes(nativeBitmap);
+ }
+
+ @Implementation(minSdk = LOLLIPOP)
+ protected static int nativeConfig(long nativeBitmap) {
+ return BitmapNatives.nativeConfig(nativeBitmap);
+ }
+
+ @Implementation(minSdk = LOLLIPOP)
+ protected static int nativeGetPixel(long nativeBitmap, int x, int y) {
+ return BitmapNatives.nativeGetPixel(nativeBitmap, x, y);
+ }
+
+ @Implementation(minSdk = Q)
+ protected static long nativeGetColor(long nativeBitmap, int x, int y) {
+ return BitmapNatives.nativeGetColor(nativeBitmap, x, y);
+ }
+
+ @Implementation(minSdk = LOLLIPOP)
+ protected static void nativeGetPixels(
+ long nativeBitmap,
+ int[] pixels,
+ int offset,
+ int stride,
+ int x,
+ int y,
+ int width,
+ int height) {
+ BitmapNatives.nativeGetPixels(nativeBitmap, pixels, offset, stride, x, y, width, height);
+ }
+
+ @Implementation(minSdk = LOLLIPOP)
+ protected static void nativeSetPixel(long nativeBitmap, int x, int y, int color) {
+ BitmapNatives.nativeSetPixel(nativeBitmap, x, y, color);
+ }
+
+ @Implementation(minSdk = LOLLIPOP)
+ protected static void nativeSetPixels(
+ long nativeBitmap,
+ int[] colors,
+ int offset,
+ int stride,
+ int x,
+ int y,
+ int width,
+ int height) {
+ BitmapNatives.nativeSetPixels(nativeBitmap, colors, offset, stride, x, y, width, height);
+ }
+
+ @Implementation
+ protected static void nativeCopyPixelsToBuffer(long nativeBitmap, Buffer dst) {
+ BitmapNatives.nativeCopyPixelsToBuffer(nativeBitmap, dst);
+ }
+
+ @Implementation
+ protected static void nativeCopyPixelsFromBuffer(long nativeBitmap, Buffer src) {
+ BitmapNatives.nativeCopyPixelsFromBuffer(nativeBitmap, src);
+ }
+
+ @Implementation
+ 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
+ protected static Bitmap nativeExtractAlpha(long nativeBitmap, long nativePaint, int[] offsetXY) {
+ return BitmapNatives.nativeExtractAlpha(nativeBitmap, nativePaint, offsetXY);
+ }
+
+ @Implementation
+ protected static boolean nativeHasAlpha(long nativeBitmap) {
+ return BitmapNatives.nativeHasAlpha(nativeBitmap);
+ }
+
+ @Implementation(minSdk = LOLLIPOP)
+ protected static boolean nativeIsPremultiplied(long nativeBitmap) {
+ return BitmapNatives.nativeIsPremultiplied(nativeBitmap);
+ }
+
+ @Implementation(minSdk = LOLLIPOP)
+ protected static void nativeSetPremultiplied(long nativeBitmap, boolean isPremul) {
+ BitmapNatives.nativeSetPremultiplied(nativeBitmap, isPremul);
+ }
+
+ @Implementation(minSdk = LOLLIPOP)
+ protected static void nativeSetHasAlpha(
+ long nativeBitmap, boolean hasAlpha, boolean requestPremul) {
+ BitmapNatives.nativeSetHasAlpha(nativeBitmap, hasAlpha, requestPremul);
+ }
+
+ @Implementation(minSdk = JELLY_BEAN_MR1)
+ protected static boolean nativeHasMipMap(long nativeBitmap) {
+ return BitmapNatives.nativeHasMipMap(nativeBitmap);
+ }
+
+ @Implementation(minSdk = JELLY_BEAN_MR1)
+ protected static void nativeSetHasMipMap(long nativeBitmap, boolean hasMipMap) {
+ BitmapNatives.nativeSetHasMipMap(nativeBitmap, hasMipMap);
+ }
+
+ @Implementation
+ protected static boolean nativeSameAs(long nativeBitmap0, long nativeBitmap1) {
+ return BitmapNatives.nativeSameAs(nativeBitmap0, nativeBitmap1);
+ }
+
+ @Implementation(minSdk = N_MR1)
+ protected static void nativePrepareToDraw(long nativeBitmap) {
+ BitmapNatives.nativePrepareToDraw(nativeBitmap);
+ }
+
+ @Implementation(minSdk = O)
+ protected static int nativeGetAllocationByteCount(long nativeBitmap) {
+ return BitmapNatives.nativeGetAllocationByteCount(nativeBitmap);
+ }
+
+ @Implementation(minSdk = O)
+ protected static Bitmap nativeCopyPreserveInternalConfig(long nativeBitmap) {
+ return BitmapNatives.nativeCopyPreserveInternalConfig(nativeBitmap);
+ }
+
+ @Implementation(minSdk = Q)
+ protected static Bitmap nativeWrapHardwareBufferBitmap(
+ HardwareBuffer buffer, long nativeColorSpace) {
+ return BitmapNatives.nativeWrapHardwareBufferBitmap(buffer, nativeColorSpace);
+ }
+
+ @Implementation(minSdk = R)
+ protected static HardwareBuffer nativeGetHardwareBuffer(long nativeBitmap) {
+ return BitmapNatives.nativeGetHardwareBuffer(nativeBitmap);
+ }
+
+ @Implementation(minSdk = O, maxSdk = P)
+ protected static boolean nativeGetColorSpace(long nativePtr, float[] xyz, float[] params) {
+ ColorSpace colorSpace = nativeComputeColorSpace(nativePtr);
+ if (colorSpace == null) {
+ return false;
+ }
+ // In Android P, 'nativeGetColorSpace' is responsible for filling out the 'xyz' and 'params'
+ // float arrays. However, in Q and above, 'nativeGetColorSpace' was removed, and
+ // 'nativeComputeColorSpace' returns the ColorSpace object itself. This means for P, we need to
+ // do the reverse operations and generate the float arrays given the detected color space.
+ if (colorSpace instanceof ColorSpace.Rgb) {
+ TransferParameters transferParameters = ((ColorSpace.Rgb) colorSpace).getTransferParameters();
+ params[0] = (float) transferParameters.a;
+ params[1] = (float) transferParameters.b;
+ params[2] = (float) transferParameters.c;
+ params[3] = (float) transferParameters.d;
+ params[4] = (float) transferParameters.e;
+ params[5] = (float) transferParameters.f;
+ params[6] = (float) transferParameters.g;
+ ColorSpace.Rgb rgb =
+ (ColorSpace.Rgb)
+ ColorSpace.adapt(
+ colorSpace, reflector(ColorSpaceReflector.class).getIlluminantD50XYZ());
+ rgb.getTransform(xyz);
+ }
+ return true;
+ }
+
+ @Implementation(minSdk = Q)
+ protected static ColorSpace nativeComputeColorSpace(long nativePtr) {
+ return BitmapNatives.nativeComputeColorSpace(nativePtr);
+ }
+
+ @Implementation(minSdk = Q)
+ protected static void nativeSetColorSpace(long nativePtr, long nativeColorSpace) {
+ BitmapNatives.nativeSetColorSpace(nativePtr, nativeColorSpace);
+ }
+
+ @Implementation(minSdk = O)
+ protected static boolean nativeIsSRGB(long nativePtr) {
+ return BitmapNatives.nativeIsSRGB(nativePtr);
+ }
+
+ @Implementation(minSdk = P)
+ protected static boolean nativeIsSRGBLinear(long nativePtr) {
+ return BitmapNatives.nativeIsSRGBLinear(nativePtr);
+ }
+
+ @Implementation(minSdk = Q)
+ protected static void nativeSetImmutable(long nativePtr) {
+ BitmapNatives.nativeSetImmutable(nativePtr);
+ }
+
+ @Implementation(minSdk = Q)
+ protected static boolean nativeIsImmutable(long nativePtr) {
+ return BitmapNatives.nativeIsImmutable(nativePtr);
+ }
+
+ @Implementation(minSdk = S)
+ protected static boolean nativeIsBackedByAshmem(long nativePtr) {
+ return BitmapNatives.nativeIsBackedByAshmem(nativePtr);
+ }
+
+ @ForType(ColorSpace.class)
+ interface ColorSpaceReflector {
+ @Accessor("ILLUMINANT_D50_XYZ")
+ @Static
+ float[] getIlluminantD50XYZ();
+
+ @Accessor("sNamedColorSpaces")
+ ColorSpace[] getNamedColorSpaces();
+ }
+
+ @Implementation
+ protected void writeToParcel(Parcel p, int flags) {
+ // Modeled after
+ // https://cs.android.com/android/platform/superproject/+/android-12.0.0_r1:frameworks/base/libs/hwui/jni/Bitmap.cpp;l=872.
+ reflector(BitmapReflector.class, realBitmap).checkRecycled("Can't parcel a recycled bitmap");
+ int width = realBitmap.getWidth();
+ int height = realBitmap.getHeight();
+ p.writeInt(width);
+ p.writeInt(height);
+ p.writeInt(realBitmap.getDensity());
+ p.writeBoolean(realBitmap.isMutable());
+ p.writeSerializable(realBitmap.getConfig());
+ p.writeString(realBitmap.getColorSpace().getName());
+ p.writeBoolean(realBitmap.hasAlpha());
+ int[] pixels = new int[width * height];
+ realBitmap.getPixels(pixels, 0, width, 0, 0, width, height);
+ p.writeIntArray(pixels);
+ }
+
+ @Implementation
+ protected static Bitmap nativeCreateFromParcel(Parcel p) {
+ int parceledWidth = p.readInt();
+ int parceledHeight = p.readInt();
+ int density = p.readInt();
+ boolean mutable = p.readBoolean();
+ Bitmap.Config parceledConfig = (Bitmap.Config) p.readSerializable();
+ String colorSpaceName = p.readString();
+ boolean hasAlpha = p.readBoolean();
+ ColorSpace colorSpace = null;
+ ColorSpace[] namedColorSpaces = reflector(ColorSpaceReflector.class).getNamedColorSpaces();
+ for (ColorSpace named : namedColorSpaces) {
+ if (named.getName().equals(colorSpaceName)) {
+ colorSpace = named;
+ break;
+ }
+ }
+ int[] parceledColors = new int[parceledHeight * parceledWidth];
+ p.readIntArray(parceledColors);
+ Bitmap bitmap =
+ Bitmap.createBitmap(parceledWidth, parceledHeight, parceledConfig, hasAlpha, colorSpace);
+ bitmap.setPixels(parceledColors, 0, parceledWidth, 0, 0, parceledWidth, parceledHeight);
+ bitmap.setDensity(density);
+ if (!mutable) {
+ bitmap = bitmap.copy(parceledConfig, false);
+ }
+ return bitmap;
+ }
+
+ @ForType(Bitmap.class)
+ interface BitmapReflector {
+ void checkRecycled(String errorMessage);
+ }
+
+ @Override
+ public Bitmap getCreatedFromBitmap() {
+ throw new UnsupportedOperationException("Legacy ShadowBitmap APIs are not supported");
+ }
+
+ /**
+ * Resource ID from which this Bitmap was created.
+ *
+ * @return Resource ID from which this Bitmap was created, or {@code 0} if this Bitmap was not
+ * created from a resource.
+ */
+ @Override
+ public int getCreatedFromResId() {
+ return createdFromResId;
+ }
+
+ @Override
+ public String getCreatedFromPath() {
+ throw new UnsupportedOperationException("Legacy ShadowBitmap APIs are not supported");
+ }
+
+ @Override
+ public InputStream getCreatedFromStream() {
+ throw new UnsupportedOperationException("Legacy ShadowBitmap APIs are not supported");
+ }
+
+ @Override
+ public byte[] getCreatedFromBytes() {
+ throw new UnsupportedOperationException("Legacy ShadowBitmap APIs are not supported");
+ }
+
+ @Override
+ public int getCreatedFromX() {
+ throw new UnsupportedOperationException("Legacy ShadowBitmap APIs are not supported");
+ }
+
+ @Override
+ public int getCreatedFromY() {
+ throw new UnsupportedOperationException("Legacy ShadowBitmap APIs are not supported");
+ }
+
+ @Override
+ public int getCreatedFromWidth() {
+ throw new UnsupportedOperationException("Legacy ShadowBitmap APIs are not supported");
+ }
+
+ @Override
+ public int getCreatedFromHeight() {
+ throw new UnsupportedOperationException("Legacy ShadowBitmap APIs are not supported");
+ }
+
+ @Override
+ public int[] getCreatedFromColors() {
+ throw new UnsupportedOperationException("Legacy ShadowBitmap APIs are not supported");
+ }
+
+ @Override
+ public Matrix getCreatedFromMatrix() {
+ throw new UnsupportedOperationException("Legacy ShadowBitmap APIs are not supported");
+ }
+
+ @Override
+ public boolean getCreatedFromFilter() {
+ throw new UnsupportedOperationException("Legacy ShadowBitmap APIs are not supported");
+ }
+
+ @Override
+ public void setMutable(boolean mutable) {
+ throw new UnsupportedOperationException("Legacy ShadowBitmap APIs are not supported");
+ }
+
+ @Override
+ public void appendDescription(String s) {
+ throw new UnsupportedOperationException("Legacy ShadowBitmap APIs are not supported");
+ }
+
+ @Override
+ public String getDescription() {
+ throw new UnsupportedOperationException("Legacy ShadowBitmap APIs are not supported");
+ }
+
+ @Override
+ public void setDescription(String s) {
+ throw new UnsupportedOperationException("Legacy ShadowBitmap APIs are not supported");
+ }
+
+ @Resetter
+ public static void reset() {
+ synchronized (colorSpaceAllocationsP) {
+ for (Long ptr : colorSpaceAllocationsP) {
+ NativeAllocationRegistryNatives.applyFreeFunction(
+ ColorSpaceRgbNatives.nativeGetNativeFinalizer(), ptr);
+ }
+ colorSpaceAllocationsP.clear();
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmapDrawable.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmapDrawable.java
new file mode 100644
index 000000000..11b0341e1
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmapDrawable.java
@@ -0,0 +1,42 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.RealObject;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowNativeBitmapDrawable.Picker;
+
+/** Disable the legacy ShadowBitmapDrawable as it fakes the draw logic. */
+@Implements(
+ value = BitmapDrawable.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ isInAndroidSdk = false)
+public class ShadowNativeBitmapDrawable extends ShadowBitmapDrawable {
+ @RealObject BitmapDrawable bitmapDrawable;
+
+ @Override
+ public int getCreatedFromResId() {
+ return ((ShadowNativeBitmap) Shadow.extract(bitmapDrawable.getBitmap())).getCreatedFromResId();
+ }
+
+ @Override
+ protected void setCreatedFromResId(int createdFromResId, String resourceName) {
+ super.setCreatedFromResId(createdFromResId, resourceName);
+ Bitmap bitmap = bitmapDrawable.getBitmap();
+ if (bitmap != null && Shadow.extract(bitmap) instanceof ShadowNativeBitmap) {
+ ShadowNativeBitmap shadowNativeBitmap = Shadow.extract(bitmap);
+ shadowNativeBitmap.setCreatedFromResId(createdFromResId);
+ }
+ }
+
+ /** Shadow picker for {@link BitmapDrawable}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(ShadowBitmapDrawable.class, ShadowNativeBitmapDrawable.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmapFactory.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmapFactory.java
new file mode 100644
index 000000000..54213d87c
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmapFactory.java
@@ -0,0 +1,153 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.LOLLIPOP;
+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 org.robolectric.util.reflector.Reflector.reflector;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapFactory.Options;
+import android.graphics.Rect;
+import java.io.FileDescriptor;
+import java.io.InputStream;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.BitmapFactoryNatives;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowNativeBitmapFactory.Picker;
+import org.robolectric.util.reflector.Direct;
+import org.robolectric.util.reflector.ForType;
+
+/** Shadow for {@link BitmapFactory} that is backed by native code */
+@Implements(
+ value = BitmapFactory.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ isInAndroidSdk = false)
+public class ShadowNativeBitmapFactory {
+
+ static {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ }
+
+ @Implementation
+ protected static Bitmap decodeResource(Resources res, int id, BitmapFactory.Options options) {
+ Bitmap bitmap = reflector(BitmapFactoryReflector.class).decodeResource(res, id, options);
+ if (bitmap == null) {
+ return null;
+ }
+
+ ShadowNativeBitmap shadowNativeBitmap = Shadow.extract(bitmap);
+ shadowNativeBitmap.setCreatedFromResId(id);
+ return bitmap;
+ }
+
+ @Implementation
+ protected static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {
+ reflector(BitmapFactoryOptionsReflector.class).validate(opts);
+ Bitmap bitmap =
+ reflector(BitmapFactoryReflector.class).decodeStreamInternal(is, outPadding, opts);
+ reflector(BitmapFactoryReflector.class).setDensityFromOptions(bitmap, opts);
+ return bitmap;
+ }
+
+ @Implementation(minSdk = Q)
+ protected static Bitmap nativeDecodeStream(
+ InputStream is,
+ byte[] storage,
+ Rect padding,
+ Options opts,
+ long inBitmapHandle,
+ long colorSpaceHandle) {
+ return BitmapFactoryNatives.nativeDecodeStream(
+ is, storage, padding, opts, inBitmapHandle, colorSpaceHandle);
+ }
+
+ @Implementation(maxSdk = P)
+ protected static Bitmap nativeDecodeStream(
+ InputStream is, byte[] storage, Rect padding, Options opts) {
+ return nativeDecodeStream(is, storage, padding, opts, nativeInBitmap(opts), 0);
+ }
+
+ @Implementation(minSdk = Q)
+ protected static Bitmap nativeDecodeFileDescriptor(
+ FileDescriptor fd, Rect padding, Options opts, long inBitmapHandle, long colorSpaceHandle) {
+ return BitmapFactoryNatives.nativeDecodeFileDescriptor(
+ fd, padding, opts, inBitmapHandle, colorSpaceHandle);
+ }
+
+ @Implementation(maxSdk = P)
+ protected static Bitmap nativeDecodeFileDescriptor(
+ FileDescriptor fd, Rect padding, Options opts) {
+ return nativeDecodeFileDescriptor(fd, padding, opts, nativeInBitmap(opts), 0);
+ }
+
+ @Implementation(minSdk = Q)
+ protected static Bitmap nativeDecodeAsset(
+ long nativeAsset, Rect padding, Options opts, long inBitmapHandle, long colorSpaceHandle) {
+ return BitmapFactoryNatives.nativeDecodeAsset(
+ nativeAsset, padding, opts, inBitmapHandle, colorSpaceHandle);
+ }
+
+ @Implementation(minSdk = LOLLIPOP, maxSdk = P)
+ protected static Bitmap nativeDecodeAsset(long nativeAsset, Rect padding, Options opts) {
+ return nativeDecodeAsset(nativeAsset, padding, opts, nativeInBitmap(opts), 0);
+ }
+
+ @Implementation(minSdk = Q)
+ protected static Bitmap nativeDecodeByteArray(
+ byte[] data,
+ int offset,
+ int length,
+ Options opts,
+ long inBitmapHandle,
+ long colorSpaceHandle) {
+ return BitmapFactoryNatives.nativeDecodeByteArray(
+ data, offset, length, opts, inBitmapHandle, colorSpaceHandle);
+ }
+
+ @Implementation(maxSdk = P)
+ protected static Bitmap nativeDecodeByteArray(byte[] data, int offset, int length, Options opts) {
+ return nativeDecodeByteArray(data, offset, length, opts, nativeInBitmap(opts), 0);
+ }
+
+ @Implementation
+ protected static boolean nativeIsSeekable(FileDescriptor fd) {
+ return BitmapFactoryNatives.nativeIsSeekable(fd);
+ }
+
+ /** Helper for passing inBitmap's native pointer to native. */
+ static long nativeInBitmap(Options opts) {
+ if (opts == null || opts.inBitmap == null) {
+ return 0;
+ }
+
+ return opts.inBitmap.getNativeInstance();
+ }
+
+ @ForType(BitmapFactory.class)
+ interface BitmapFactoryReflector {
+ Bitmap decodeStreamInternal(InputStream is, Rect outPadding, Options opts);
+
+ void setDensityFromOptions(Bitmap outputBitmap, Options opts);
+
+ @Direct
+ Bitmap decodeResource(Resources res, int id, BitmapFactory.Options options);
+ }
+
+ @ForType(BitmapFactory.Options.class)
+ interface BitmapFactoryOptionsReflector {
+ void validate(Options opts);
+ }
+
+ /** Shadow picker for {@link BitmapFactory}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(ShadowBitmapFactory.class, ShadowNativeBitmapFactory.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmapShader.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmapShader.java
new file mode 100644
index 000000000..1c4aa80a9
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBitmapShader.java
@@ -0,0 +1,69 @@
+package org.robolectric.shadows;
+
+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.S;
+import static android.os.Build.VERSION_CODES.S_V2;
+import static android.os.Build.VERSION_CODES.TIRAMISU;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.BitmapShaderNatives;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.shadows.ShadowNativeBitmapShader.Picker;
+
+/** Shadow for {@link BitmapShader} that is backed by native code */
+@Implements(value = BitmapShader.class, minSdk = O, shadowPicker = Picker.class)
+public class ShadowNativeBitmapShader {
+
+ @Implementation(minSdk = O, maxSdk = P)
+ protected static long nativeCreate(
+ long nativeMatrix, Bitmap bitmap, int shaderTileModeX, int shaderTileModeY) {
+ return nativeCreate(
+ nativeMatrix,
+ bitmap != null ? bitmap.getNativeInstance() : 0,
+ shaderTileModeX,
+ shaderTileModeY,
+ false);
+ }
+
+ @Implementation(minSdk = Q, maxSdk = R)
+ protected static long nativeCreate(
+ long nativeMatrix, long bitmapHandle, int shaderTileModeX, int shaderTileModeY) {
+ return nativeCreate(nativeMatrix, bitmapHandle, shaderTileModeX, shaderTileModeY, false);
+ }
+
+ @Implementation(minSdk = S, maxSdk = S_V2)
+ protected static long nativeCreate(
+ long nativeMatrix,
+ long bitmapHandle,
+ int shaderTileModeX,
+ int shaderTileModeY,
+ boolean filter) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return BitmapShaderNatives.nativeCreate(
+ nativeMatrix, bitmapHandle, shaderTileModeX, shaderTileModeY, filter);
+ }
+
+ @Implementation(minSdk = TIRAMISU)
+ protected static long nativeCreate(
+ long nativeMatrix,
+ long bitmapHandle,
+ int shaderTileModeX,
+ int shaderTileModeY,
+ boolean filter,
+ boolean isDirectSampled) {
+ return nativeCreate(nativeMatrix, bitmapHandle, shaderTileModeX, shaderTileModeY, filter);
+ }
+
+ /** Shadow picker for {@link BitmapShader}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(null, ShadowNativeBitmapShader.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBlendModeColorFilter.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBlendModeColorFilter.java
new file mode 100644
index 000000000..f4cbf9901
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBlendModeColorFilter.java
@@ -0,0 +1,29 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+import static android.os.Build.VERSION_CODES.Q;
+
+import android.graphics.BlendModeColorFilter;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.BlendModeColorFilterNatives;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.shadows.ShadowNativeBlendModeColorFilter.Picker;
+
+/** Shadow for {@link BlendModeColorFilter} that is backed by native code */
+@Implements(value = BlendModeColorFilter.class, minSdk = O, shadowPicker = Picker.class)
+public class ShadowNativeBlendModeColorFilter {
+
+ @Implementation(minSdk = Q)
+ protected static long native_CreateBlendModeFilter(int srcColor, int blendmode) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return BlendModeColorFilterNatives.native_CreateBlendModeFilter(srcColor, blendmode);
+ }
+
+ /** Shadow picker for {@link BlendModeColorFilter}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(null, ShadowNativeBlendModeColorFilter.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBlurMaskFilter.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBlurMaskFilter.java
new file mode 100644
index 000000000..77d4a9da2
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeBlurMaskFilter.java
@@ -0,0 +1,28 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+
+import android.graphics.BlurMaskFilter;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.BlurMaskFilterNatives;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.shadows.ShadowNativeBlurMaskFilter.Picker;
+
+/** Shadow for {@link BlurMaskFilter} that is backed by native code */
+@Implements(value = BlurMaskFilter.class, minSdk = O, shadowPicker = Picker.class)
+public class ShadowNativeBlurMaskFilter {
+
+ @Implementation(minSdk = O)
+ protected static long nativeConstructor(float radius, int style) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return BlurMaskFilterNatives.nativeConstructor(radius, style);
+ }
+
+ /** Shadow picker for {@link BlurMaskFilter}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(null, ShadowNativeBlurMaskFilter.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCanvas.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCanvas.java
new file mode 100644
index 000000000..c2dffb8eb
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCanvas.java
@@ -0,0 +1,209 @@
+package org.robolectric.shadows;
+
+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.S;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.CanvasNatives;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+
+/** Shadow for {@link Canvas} that is backed by native code */
+@Implements(value = Canvas.class, minSdk = O, isInAndroidSdk = false)
+public class ShadowNativeCanvas extends ShadowNativeBaseCanvas {
+
+ @Implementation(minSdk = O)
+ protected static void nFreeCaches() {
+ CanvasNatives.nFreeCaches();
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nFreeTextLayoutCaches() {
+ CanvasNatives.nFreeTextLayoutCaches();
+ }
+
+ @Implementation(minSdk = O)
+ protected static long nGetNativeFinalizer() {
+ return CanvasNatives.nGetNativeFinalizer();
+ }
+
+ @Implementation(minSdk = P)
+ protected static void nSetCompatibilityVersion(int apiLevel) {
+ CanvasNatives.nSetCompatibilityVersion(apiLevel);
+ }
+
+ @Implementation(minSdk = O, maxSdk = P)
+ protected static long nInitRaster(Bitmap bitmap) {
+ return nInitRaster(bitmap != null ? bitmap.getNativeInstance() : 0);
+ }
+
+ @Implementation(minSdk = Q)
+ protected static long nInitRaster(long bitmapHandle) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return CanvasNatives.nInitRaster(bitmapHandle);
+ }
+
+ @Implementation(minSdk = O, maxSdk = P)
+ protected static void nSetBitmap(long canvasHandle, Bitmap bitmap) {
+ CanvasNatives.nSetBitmap(canvasHandle, bitmap != null ? bitmap.getNativeInstance() : 0);
+ }
+
+ @Implementation(minSdk = Q)
+ protected static void nSetBitmap(long canvasHandle, long bitmapHandle) {
+ CanvasNatives.nSetBitmap(canvasHandle, bitmapHandle);
+ }
+
+ @Implementation(minSdk = O)
+ protected static boolean nGetClipBounds(long nativeCanvas, Rect bounds) {
+ return CanvasNatives.nGetClipBounds(nativeCanvas, bounds);
+ }
+
+ @Implementation(minSdk = O)
+ protected static boolean nIsOpaque(long canvasHandle) {
+ return CanvasNatives.nIsOpaque(canvasHandle);
+ }
+
+ @Implementation(minSdk = O)
+ protected static int nGetWidth(long canvasHandle) {
+ return CanvasNatives.nGetWidth(canvasHandle);
+ }
+
+ @Implementation(minSdk = O)
+ protected static int nGetHeight(long canvasHandle) {
+ return CanvasNatives.nGetHeight(canvasHandle);
+ }
+
+ @Implementation(minSdk = O)
+ protected static int nSave(long canvasHandle, int saveFlags) {
+ return CanvasNatives.nSave(canvasHandle, saveFlags);
+ }
+
+ @Implementation(minSdk = S)
+ 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);
+ }
+
+ @Implementation(minSdk = O, maxSdk = R)
+ protected static int nSaveLayer(
+ long nativeCanvas, float l, float t, float r, float b, long nativePaint, int layerFlags) {
+ return nSaveLayer(nativeCanvas, l, t, r, b, nativePaint);
+ }
+
+ @Implementation(minSdk = S)
+ 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);
+ }
+
+ @Implementation(minSdk = O, maxSdk = R)
+ protected static int nSaveLayerAlpha(
+ long nativeCanvas, float l, float t, float r, float b, int alpha, int layerFlags) {
+ return nSaveLayerAlpha(nativeCanvas, l, t, r, b, alpha);
+ }
+
+ @Implementation(minSdk = Q)
+ 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)
+ protected static void nRestoreUnclippedLayer(long nativeCanvas, int saveCount, long nativePaint) {
+ CanvasNatives.nRestoreUnclippedLayer(nativeCanvas, saveCount, nativePaint);
+ }
+
+ @Implementation(minSdk = O)
+ protected static boolean nRestore(long canvasHandle) {
+ return CanvasNatives.nRestore(canvasHandle);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nRestoreToCount(long canvasHandle, int saveCount) {
+ CanvasNatives.nRestoreToCount(canvasHandle, saveCount);
+ }
+
+ @Implementation(minSdk = O)
+ protected static int nGetSaveCount(long canvasHandle) {
+ return CanvasNatives.nGetSaveCount(canvasHandle);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nTranslate(long canvasHandle, float dx, float dy) {
+ CanvasNatives.nTranslate(canvasHandle, dx, dy);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nScale(long canvasHandle, float sx, float sy) {
+ CanvasNatives.nScale(canvasHandle, sx, sy);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nRotate(long canvasHandle, float degrees) {
+ CanvasNatives.nRotate(canvasHandle, degrees);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSkew(long canvasHandle, float sx, float sy) {
+ CanvasNatives.nSkew(canvasHandle, sx, sy);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nConcat(long nativeCanvas, long nativeMatrix) {
+ CanvasNatives.nConcat(nativeCanvas, nativeMatrix);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetMatrix(long nativeCanvas, long nativeMatrix) {
+ CanvasNatives.nSetMatrix(nativeCanvas, nativeMatrix);
+ }
+
+ @Implementation(minSdk = O)
+ 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)
+ protected static boolean nClipPath(long nativeCanvas, long nativePath, int regionOp) {
+ return CanvasNatives.nClipPath(nativeCanvas, nativePath, regionOp);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetDrawFilter(long nativeCanvas, long nativeFilter) {
+ CanvasNatives.nSetDrawFilter(nativeCanvas, nativeFilter);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nGetMatrix(long nativeCanvas, long nativeMatrix) {
+ CanvasNatives.nGetMatrix(nativeCanvas, nativeMatrix);
+ }
+
+ @Implementation(minSdk = O)
+ protected static boolean nQuickReject(long nativeCanvas, long nativePath) {
+ return CanvasNatives.nQuickReject(nativeCanvas, nativePath);
+ }
+
+ @Implementation(minSdk = O)
+ protected static boolean nQuickReject(
+ long nativeCanvas, float left, float top, float right, float bottom) {
+ return CanvasNatives.nQuickReject(nativeCanvas, left, top, right, bottom);
+ }
+
+ /**
+ * In Android P and below, Canvas.saveUnclippedLayer called {@link
+ * ShadowNativeCanvas#nSaveLayer(long, float, float, float, float, long)}.
+ *
+ * <p>However, in Android Q, a new native method was added specifically to save unclipped layers.
+ * Use this new method to fix things like ScrollView fade effects in P and below.
+ */
+ @Implementation(minSdk = P, maxSdk = P)
+ protected int saveUnclippedLayer(int left, int top, int right, int bottom) {
+ return nSaveUnclippedLayer(getNativeCanvas(), 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
new file mode 100644
index 000000000..8bb4f1698
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCanvasProperty.java
@@ -0,0 +1,38 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+
+import android.graphics.CanvasProperty;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.CanvasPropertyNatives;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.shadows.ShadowNativeCanvasProperty.Picker;
+
+/** Shadow for {@link CanvasProperty} that is backed by native code */
+@Implements(
+ value = CanvasProperty.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ isInAndroidSdk = false)
+public class ShadowNativeCanvasProperty<T> {
+
+ @Implementation
+ protected static long nCreateFloat(float initialValue) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return CanvasPropertyNatives.nCreateFloat(initialValue);
+ }
+
+ @Implementation
+ protected static long nCreatePaint(long initialValuePaintPtr) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return CanvasPropertyNatives.nCreatePaint(initialValuePaintPtr);
+ }
+
+ /** Shadow picker for {@link CanvasProperty}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(null, ShadowNativeCanvasProperty.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColor.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColor.java
new file mode 100644
index 000000000..05e3aa3fa
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColor.java
@@ -0,0 +1,34 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+
+import android.graphics.Color;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.ColorNatives;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.shadows.ShadowNativeColor.Picker;
+
+/** Shadow for {@link Color} that is backed by native code */
+@Implements(value = Color.class, minSdk = O, shadowPicker = Picker.class, isInAndroidSdk = false)
+public class ShadowNativeColor {
+
+ @Implementation(minSdk = O)
+ protected static void nativeRGBToHSV(int red, int greed, int blue, float[] hsv) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ ColorNatives.nativeRGBToHSV(red, greed, blue, hsv);
+ }
+
+ @Implementation(minSdk = O)
+ protected static int nativeHSVToColor(int alpha, float[] hsv) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return ColorNatives.nativeHSVToColor(alpha, hsv);
+ }
+
+ /** Shadow picker for {@link Color}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(ShadowColor.class, ShadowNativeColor.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorFilter.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorFilter.java
new file mode 100644
index 000000000..ed641a2f0
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorFilter.java
@@ -0,0 +1,32 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+import static android.os.Build.VERSION_CODES.O_MR1;
+
+import android.graphics.ColorFilter;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.ColorFilterNatives;
+import org.robolectric.shadows.ShadowNativeColorFilter.Picker;
+
+/** Shadow for {@link ColorFilter} that is backed by native code */
+@Implements(value = ColorFilter.class, minSdk = O, shadowPicker = Picker.class)
+public class ShadowNativeColorFilter {
+
+ @Implementation(minSdk = O_MR1)
+ protected static long nativeGetFinalizer() {
+ return ColorFilterNatives.nativeGetFinalizer();
+ }
+
+ @Implementation(minSdk = O, maxSdk = O)
+ protected static void nSafeUnref(long nativeInstance) {
+ ColorFilterNatives.nSafeUnref(nativeInstance);
+ }
+
+ /** Shadow picker for {@link ColorFilter}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(null, ShadowNativeColorFilter.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorMatrixColorFilter.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorMatrixColorFilter.java
new file mode 100644
index 000000000..307303bea
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorMatrixColorFilter.java
@@ -0,0 +1,28 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+
+import android.graphics.ColorMatrixColorFilter;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.ColorMatrixColorFilterNatives;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.shadows.ShadowNativeColorMatrixColorFilter.Picker;
+
+/** Shadow for {@link ColorMatrixColorFilter} that is backed by native code */
+@Implements(value = ColorMatrixColorFilter.class, minSdk = O, shadowPicker = Picker.class)
+public class ShadowNativeColorMatrixColorFilter {
+
+ @Implementation(minSdk = O)
+ protected static long nativeColorMatrixFilter(float[] array) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return ColorMatrixColorFilterNatives.nativeColorMatrixFilter(array);
+ }
+
+ /** Shadow picker for {@link ColorMatrixColorFilter}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(null, ShadowNativeColorMatrixColorFilter.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
new file mode 100644
index 000000000..6bb12eeb2
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeColorSpaceRgb.java
@@ -0,0 +1,39 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+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.nativeruntime.ColorSpaceRgbNatives;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.shadows.ShadowNativeColorSpaceRgb.Picker;
+
+/** 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 {
+
+ @Implementation(minSdk = Q)
+ protected static long nativeGetNativeFinalizer() {
+ return ColorSpaceRgbNatives.nativeGetNativeFinalizer();
+ }
+
+ @Implementation(minSdk = Q)
+ protected static long nativeCreate(
+ float a, float b, float c, float d, float e, float f, float g, float[] xyz) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return ColorSpaceRgbNatives.nativeCreate(a, b, c, d, e, f, g, xyz);
+ }
+
+ /** Shadow picker for {@link ColorSpace.Rgb}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(ShadowColorSpaceRgb.class, ShadowNativeColorSpaceRgb.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeComposePathEffect.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeComposePathEffect.java
new file mode 100644
index 000000000..6bf1e3ff4
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeComposePathEffect.java
@@ -0,0 +1,28 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+
+import android.graphics.ComposePathEffect;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.ComposePathEffectNatives;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.shadows.ShadowNativeComposePathEffect.Picker;
+
+/** Shadow for {@link ComposePathEffect} that is backed by native code */
+@Implements(value = ComposePathEffect.class, minSdk = O, shadowPicker = Picker.class)
+public class ShadowNativeComposePathEffect {
+
+ @Implementation(minSdk = O)
+ protected static long nativeCreate(long nativeOuterpe, long nativeInnerpe) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return ComposePathEffectNatives.nativeCreate(nativeOuterpe, nativeInnerpe);
+ }
+
+ /** Shadow picker for {@link ComposePathEffect}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(null, ShadowNativeComposePathEffect.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeComposeShader.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeComposeShader.java
new file mode 100644
index 000000000..b5e5b4a25
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeComposeShader.java
@@ -0,0 +1,30 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+
+import android.graphics.ComposeShader;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.ComposeShaderNatives;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.shadows.ShadowNativeComposeShader.Picker;
+
+/** Shadow for {@link ComposeShader} that is backed by native code */
+@Implements(value = ComposeShader.class, minSdk = O, shadowPicker = Picker.class)
+public class ShadowNativeComposeShader {
+
+ @Implementation(minSdk = O)
+ protected static long nativeCreate(
+ long nativeMatrix, long nativeShaderA, long nativeShaderB, int porterDuffMode) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return ComposeShaderNatives.nativeCreate(
+ nativeMatrix, nativeShaderA, nativeShaderB, porterDuffMode);
+ }
+
+ /** Shadow picker for {@link ComposeShader}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(null, ShadowNativeComposeShader.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCornerPathEffect.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCornerPathEffect.java
new file mode 100644
index 000000000..7c306298b
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeCornerPathEffect.java
@@ -0,0 +1,28 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+
+import android.graphics.CornerPathEffect;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.CornerPathEffectNatives;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.shadows.ShadowNativeCornerPathEffect.Picker;
+
+/** Shadow for {@link CornerPathEffect} that is backed by native code */
+@Implements(value = CornerPathEffect.class, minSdk = O, shadowPicker = Picker.class)
+public class ShadowNativeCornerPathEffect {
+
+ @Implementation(minSdk = O)
+ protected static long nativeCreate(float radius) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return CornerPathEffectNatives.nativeCreate(radius);
+ }
+
+ /** Shadow picker for {@link CornerPathEffect}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(null, ShadowNativeCornerPathEffect.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeDashPathEffect.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeDashPathEffect.java
new file mode 100644
index 000000000..d8ad1eb3f
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeDashPathEffect.java
@@ -0,0 +1,28 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+
+import android.graphics.DashPathEffect;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DashPathEffectNatives;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.shadows.ShadowNativeDashPathEffect.Picker;
+
+/** Shadow for {@link DashPathEffect} that is backed by native code */
+@Implements(value = DashPathEffect.class, minSdk = O, shadowPicker = Picker.class)
+public class ShadowNativeDashPathEffect {
+
+ @Implementation(minSdk = O)
+ protected static long nativeCreate(float[] intervals, float phase) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return DashPathEffectNatives.nativeCreate(intervals, phase);
+ }
+
+ /** Shadow picker for {@link DashPathEffect}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(null, ShadowNativeDashPathEffect.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeDiscretePathEffect.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeDiscretePathEffect.java
new file mode 100644
index 000000000..b7f5e3ed6
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeDiscretePathEffect.java
@@ -0,0 +1,28 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+
+import android.graphics.DiscretePathEffect;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.DiscretePathEffectNatives;
+import org.robolectric.shadows.ShadowNativeDiscretePathEffect.Picker;
+
+/** Shadow for {@link DiscretePathEffect} that is backed by native code */
+@Implements(value = DiscretePathEffect.class, minSdk = O, shadowPicker = Picker.class)
+public class ShadowNativeDiscretePathEffect {
+
+ @Implementation(minSdk = O)
+ protected static long nativeCreate(float length, float deviation) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return DiscretePathEffectNatives.nativeCreate(length, deviation);
+ }
+
+ /** Shadow picker for {@link DiscretePathEffect}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(null, ShadowNativeDiscretePathEffect.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeDisplayListCanvas.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeDisplayListCanvas.java
new file mode 100644
index 000000000..f369ca517
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeDisplayListCanvas.java
@@ -0,0 +1,72 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+import static android.os.Build.VERSION_CODES.P;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.RecordingCanvasNatives;
+import org.robolectric.shadows.ShadowNativeDisplayListCanvas.Picker;
+
+/** Shadow for {@link android.view.DisplayListCanvas} that is backed by native code */
+@Implements(
+ className = "android.view.DisplayListCanvas",
+ minSdk = O,
+ maxSdk = P,
+ shadowPicker = Picker.class)
+public class ShadowNativeDisplayListCanvas extends ShadowNativeRecordingCanvas {
+
+ @Implementation
+ protected static long nCreateDisplayListCanvas(long node, int width, int height) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return RecordingCanvasNatives.nCreateDisplayListCanvas(node, width, height);
+ }
+
+ @Implementation
+ protected static void nResetDisplayListCanvas(long canvas, long node, int width, int height) {
+ RecordingCanvasNatives.nResetDisplayListCanvas(canvas, node, width, height);
+ }
+
+ @Implementation
+ protected static int nGetMaximumTextureWidth() {
+ return RecordingCanvasNatives.nGetMaximumTextureWidth();
+ }
+
+ @Implementation
+ protected static int nGetMaximumTextureHeight() {
+ return RecordingCanvasNatives.nGetMaximumTextureHeight();
+ }
+
+ @Implementation
+ protected static void nDrawRenderNode(long renderer, long renderNode) {
+ RecordingCanvasNatives.nDrawRenderNode(renderer, renderNode);
+ }
+
+ @Implementation
+ protected static void nDrawCircle(
+ long renderer, long propCx, long propCy, long propRadius, long propPaint) {
+ RecordingCanvasNatives.nDrawCircle(renderer, propCx, propCy, propRadius, propPaint);
+ }
+
+ @Implementation
+ protected static void nDrawRoundRect(
+ long renderer,
+ long propLeft,
+ long propTop,
+ long propRight,
+ long propBottom,
+ long propRx,
+ long propRy,
+ long propPaint) {
+ RecordingCanvasNatives.nDrawRoundRect(
+ renderer, propLeft, propTop, propRight, propBottom, propRx, propRy, propPaint);
+ }
+
+ /** Shadow picker for {@link android.view.DisplayListCanvas}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(ShadowDisplayListCanvas.class, ShadowNativeDisplayListCanvas.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeEmbossMaskFilter.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeEmbossMaskFilter.java
new file mode 100644
index 000000000..52ed28e42
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeEmbossMaskFilter.java
@@ -0,0 +1,29 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+
+import android.graphics.EmbossMaskFilter;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.EmbossMaskFilterNatives;
+import org.robolectric.shadows.ShadowNativeEmbossMaskFilter.Picker;
+
+/** Shadow for {@link EmbossMaskFilter} that is backed by native code */
+@Implements(value = EmbossMaskFilter.class, minSdk = O, shadowPicker = Picker.class)
+public class ShadowNativeEmbossMaskFilter {
+
+ @Implementation(minSdk = O)
+ protected static long nativeConstructor(
+ float[] direction, float ambient, float specular, float blurRadius) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return EmbossMaskFilterNatives.nativeConstructor(direction, ambient, specular, blurRadius);
+ }
+
+ /** Shadow picker for {@link EmbossMaskFilter}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(null, ShadowNativeEmbossMaskFilter.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFont.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFont.java
new file mode 100644
index 000000000..2c64de3cd
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFont.java
@@ -0,0 +1,281 @@
+package org.robolectric.shadows;
+
+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 org.robolectric.util.reflector.Reflector.reflector;
+
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.graphics.fonts.Font;
+import android.util.TypedValue;
+import com.google.common.base.Ascii;
+import com.google.common.base.Preconditions;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.RealObject;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.FontBuilderNatives;
+import org.robolectric.nativeruntime.FontNatives;
+import org.robolectric.shadows.ShadowNativeFont.Picker;
+import org.robolectric.util.reflector.Accessor;
+import org.robolectric.util.reflector.ForType;
+
+/** Shadow for {@link Font} that is backed by native code */
+@Implements(value = Font.class, minSdk = P, shadowPicker = Picker.class, isInAndroidSdk = false)
+public class ShadowNativeFont {
+ @Implementation(minSdk = S)
+ protected static long nGetMinikinFontPtr(long font) {
+ return FontNatives.nGetMinikinFontPtr(font);
+ }
+
+ @Implementation(minSdk = S)
+ protected static long nCloneFont(long font) {
+ return FontNatives.nCloneFont(font);
+ }
+
+ @Implementation(minSdk = S)
+ protected static ByteBuffer nNewByteBuffer(long font) {
+ return FontNatives.nNewByteBuffer(font);
+ }
+
+ @Implementation(minSdk = S)
+ protected static long nGetBufferAddress(long font) {
+ return FontNatives.nGetBufferAddress(font);
+ }
+
+ @Implementation(minSdk = S)
+ protected static int nGetSourceId(long font) {
+ return FontNatives.nGetSourceId(font);
+ }
+
+ @Implementation(minSdk = S)
+ protected static long nGetReleaseNativeFont() {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return FontNatives.nGetReleaseNativeFont();
+ }
+
+ @Implementation(minSdk = S)
+ protected static float nGetGlyphBounds(long font, int glyphId, long paint, RectF rect) {
+ return FontNatives.nGetGlyphBounds(font, glyphId, paint, rect);
+ }
+
+ @Implementation(minSdk = S)
+ protected static float nGetFontMetrics(long font, long paint, Paint.FontMetrics metrics) {
+ return FontNatives.nGetFontMetrics(font, paint, metrics);
+ }
+
+ @Implementation(minSdk = S)
+ protected static String nGetFontPath(long fontPtr) {
+ return FontNatives.nGetFontPath(fontPtr);
+ }
+
+ @Implementation(minSdk = S)
+ protected static String nGetLocaleList(long familyPtr) {
+ return FontNatives.nGetLocaleList(familyPtr);
+ }
+
+ @Implementation(minSdk = S)
+ protected static int nGetPackedStyle(long fontPtr) {
+ return FontNatives.nGetPackedStyle(fontPtr);
+ }
+
+ @Implementation(minSdk = S)
+ protected static int nGetIndex(long fontPtr) {
+ return FontNatives.nGetIndex(fontPtr);
+ }
+
+ @Implementation(minSdk = S)
+ protected static int nGetAxisCount(long fontPtr) {
+ return FontNatives.nGetAxisCount(fontPtr);
+ }
+
+ @Implementation(minSdk = S)
+ protected static long nGetAxisInfo(long fontPtr, int i) {
+ return FontNatives.nGetAxisInfo(fontPtr, i);
+ }
+
+ @Implementation(minSdk = S)
+ protected static long[] nGetAvailableFontSet() {
+ return FontNatives.nGetAvailableFontSet();
+ }
+
+ /** Shadow for {@link Font.Builder} that is backed by native code */
+ @Implements(
+ value = Font.Builder.class,
+ minSdk = P,
+ shadowPicker = ShadowNativeFontBuilder.Picker.class,
+ isInAndroidSdk = false)
+ public static class ShadowNativeFontBuilder {
+
+ @RealObject Font.Builder realFontBuilder;
+
+ @Implementation(minSdk = Q, maxSdk = Q)
+ protected void __constructor__(AssetManager am, String path, boolean isAsset, int cookie) {
+ // In Android Q, this method uses native methods that do not exist in later versions, so
+ // they need to be re-implemented using logic from S.
+ reflector(FontBuilderReflector.class, realFontBuilder).setWeight(-1);
+ reflector(FontBuilderReflector.class, realFontBuilder).setItalic(-1);
+ reflector(FontBuilderReflector.class, realFontBuilder).setLocaleList("");
+ try {
+ ByteBuffer buf = createBuffer(am, path, isAsset, cookie);
+ reflector(FontBuilderReflector.class, realFontBuilder).setBuffer(buf);
+ } catch (IOException e) {
+ reflector(FontBuilderReflector.class, realFontBuilder).setException(e);
+ }
+ }
+
+ @Implementation(minSdk = Q, maxSdk = Q)
+ protected void __constructor__(Resources res, int resId) {
+ // In Android Q, this method uses native methods that do not exist in later versions, so
+ // they need to be re-implemented using logic from S.
+ reflector(FontBuilderReflector.class, realFontBuilder).setWeight(-1);
+ reflector(FontBuilderReflector.class, realFontBuilder).setItalic(-1);
+ reflector(FontBuilderReflector.class, realFontBuilder).setLocaleList("");
+ final TypedValue value = new TypedValue();
+ res.getValue(resId, value, true);
+ if (value.string == null) {
+ reflector(FontBuilderReflector.class, realFontBuilder)
+ .setException(new FileNotFoundException(resId + " not found"));
+ return;
+ }
+ final String str = value.string.toString();
+ if (Ascii.toLowerCase(str).endsWith(".xml")) {
+ reflector(FontBuilderReflector.class, realFontBuilder)
+ .setException(new FileNotFoundException(resId + " must be font file."));
+ return;
+ }
+ try {
+ ByteBuffer buf = createBuffer(res.getAssets(), str, false, value.assetCookie);
+ reflector(FontBuilderReflector.class, realFontBuilder).setBuffer(buf);
+ } catch (IOException e) {
+ reflector(FontBuilderReflector.class, realFontBuilder).setException(e);
+ }
+ }
+
+ @Implementation(minSdk = Q)
+ protected static long nInitBuilder() {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return FontBuilderNatives.nInitBuilder();
+ }
+
+ @Implementation(minSdk = Q)
+ protected static void nAddAxis(long builderPtr, int tag, float value) {
+ FontBuilderNatives.nAddAxis(builderPtr, tag, value);
+ }
+
+ @Implementation(minSdk = S)
+ protected static long nBuild(
+ long builderPtr,
+ ByteBuffer buffer,
+ String filePath,
+ String localeList,
+ int weight,
+ boolean italic,
+ int ttcIndex) {
+ return FontBuilderNatives.nBuild(
+ builderPtr, buffer, filePath, localeList, weight, italic, ttcIndex);
+ }
+
+ @Implementation(minSdk = Q, maxSdk = R)
+ protected static long nBuild(
+ long builderPtr,
+ ByteBuffer buffer,
+ String filePath,
+ int weight,
+ boolean italic,
+ int ttcIndex) {
+ return nBuild(builderPtr, buffer, filePath, "", weight, italic, ttcIndex);
+ }
+
+ @Implementation(minSdk = Q, maxSdk = TIRAMISU)
+ protected static long nGetReleaseNativeFont() {
+ // Starting in S, nGetReleaseNativeFont was moved from Font.Builder to Font, and despite
+ // existing in S, Font.Builder.nGetReleaseNativeFont does not get registered with a native
+ // method.
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return FontNatives.nGetReleaseNativeFont();
+ }
+
+ @Implementation(minSdk = S)
+ protected static long nClone(
+ long fontPtr, long builderPtr, int weight, boolean italic, int ttcIndex) {
+ return FontBuilderNatives.nClone(fontPtr, builderPtr, weight, italic, ttcIndex);
+ }
+
+ /**
+ * The Android implementation attempts to call {@link java.nio.ByteBuffer#array()} on a direct
+ * byte buffer. This is supported in Libcore but not the JVM. Use an implementation that copies
+ * the data from the asset into a direct buffer.
+ */
+ @Implementation(minSdk = R)
+ protected static ByteBuffer createBuffer(
+ AssetManager am, String path, boolean isAsset, int cookie) throws IOException {
+ return assetToBuffer(am, path, isAsset, cookie);
+ }
+
+ @ForType(Font.Builder.class)
+ interface FontBuilderReflector {
+ @Accessor("mBuffer")
+ void setBuffer(ByteBuffer buffer);
+
+ @Accessor("mException")
+ void setException(IOException e);
+
+ @Accessor("mWeight")
+ void setWeight(int weight);
+
+ @Accessor("mItalic")
+ void setItalic(int italic);
+
+ @Accessor("mLocaleList")
+ void setLocaleList(String localeList);
+ }
+
+ /** Shadow picker for {@link Font.Builder}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(ShadowFontBuilder.class, ShadowNativeFontBuilder.class);
+ }
+ }
+ }
+
+ static ByteBuffer assetToBuffer(AssetManager am, String path, boolean isAsset, int cookie)
+ throws IOException {
+ Preconditions.checkNotNull(am, "assetManager can not be null");
+ Preconditions.checkNotNull(path, "path can not be null");
+ try (InputStream assetStream =
+ isAsset
+ ? am.open(path, AssetManager.ACCESS_BUFFER)
+ : am.openNonAsset(cookie, path, AssetManager.ACCESS_BUFFER)) {
+ int capacity = assetStream.available();
+ ByteBuffer buffer = ByteBuffer.allocateDirect(capacity);
+ buffer.order(ByteOrder.nativeOrder());
+ byte[] buf = new byte[8 * 1024]; // 8k
+ int bytesRead;
+ while ((bytesRead = assetStream.read(buf)) != -1) {
+ buffer.put(buf, 0, bytesRead);
+ }
+ if (assetStream.read() != -1) {
+ throw new IOException("Unable to access full contents of " + path);
+ }
+ return buffer;
+ }
+ }
+
+ /** Shadow picker for {@link Font}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(ShadowFont.class, ShadowNativeFont.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontFamily.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontFamily.java
new file mode 100644
index 000000000..7a78e471f
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontFamily.java
@@ -0,0 +1,96 @@
+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 android.content.res.AssetManager;
+import android.graphics.FontFamily;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.FontFamilyNatives;
+import org.robolectric.shadows.ShadowNativeFontFamily.Picker;
+
+/** Shadow for {@link FontFamily} that is backed by native code */
+@Implements(
+ value = FontFamily.class,
+ minSdk = O,
+ isInAndroidSdk = false,
+ shadowPicker = Picker.class)
+public class ShadowNativeFontFamily {
+ @Implementation(minSdk = O)
+ public static long nInitBuilder(String langs, int variant) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return FontFamilyNatives.nInitBuilder(langs, variant);
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static void nAllowUnsupportedFont(long builderPtr) {
+ FontFamilyNatives.nAllowUnsupportedFont(builderPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static long nCreateFamily(long mBuilderPtr) {
+ return FontFamilyNatives.nCreateFamily(mBuilderPtr);
+ }
+
+ @Implementation(minSdk = P)
+ protected static long nGetBuilderReleaseFunc() {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return FontFamilyNatives.nGetBuilderReleaseFunc();
+ }
+
+ @Implementation(minSdk = P)
+ 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)
+ protected static boolean nAddFont(
+ long builderPtr, ByteBuffer font, int ttcIndex, int weight, int isItalic) {
+ return FontFamilyNatives.nAddFont(builderPtr, font, ttcIndex, weight, isItalic);
+ }
+
+ @Implementation(minSdk = O, maxSdk = Q)
+ protected static boolean nAddFontFromAssetManager(
+ long builderPtr,
+ AssetManager mgr,
+ String path,
+ int cookie,
+ boolean isAsset,
+ int ttcIndex,
+ int weight,
+ int isItalic) {
+ try {
+ ByteBuffer byteBuffer = ShadowNativeFont.assetToBuffer(mgr, path, isAsset, cookie);
+ return nAddFont(builderPtr, byteBuffer, ttcIndex, weight, isItalic);
+ } catch (IOException e) {
+ throw new UnsupportedOperationException(e);
+ }
+ }
+
+ @Implementation(minSdk = O)
+ 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)
+ protected static void nAddAxisValue(long builderPtr, int tag, float value) {
+ FontFamilyNatives.nAddAxisValue(builderPtr, tag, value);
+ }
+
+ /** Shadow picker for {@link FontFamily}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(ShadowFontFamily.class, ShadowNativeFontFamily.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontFileUtil.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontFileUtil.java
new file mode 100644
index 000000000..a38e280aa
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontFileUtil.java
@@ -0,0 +1,45 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.Q;
+import static android.os.Build.VERSION_CODES.S;
+
+import android.graphics.fonts.FontFileUtil;
+import java.nio.ByteBuffer;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.FontFileUtilNatives;
+import org.robolectric.shadows.ShadowNativeFontFileUtil.Picker;
+
+/** Shadow for {@link FontFileUtil} that is backed by native code */
+@Implements(
+ value = FontFileUtil.class,
+ isInAndroidSdk = false,
+ minSdk = Q,
+ shadowPicker = Picker.class)
+public class ShadowNativeFontFileUtil {
+ @Implementation(minSdk = S)
+ protected static long nGetFontRevision(ByteBuffer buffer, int index) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return FontFileUtilNatives.nGetFontRevision(buffer, index);
+ }
+
+ @Implementation(minSdk = S)
+ protected static String nGetFontPostScriptName(ByteBuffer buffer, int index) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return FontFileUtilNatives.nGetFontPostScriptName(buffer, index);
+ }
+
+ @Implementation(minSdk = S)
+ protected static int nIsPostScriptType1Font(ByteBuffer buffer, int index) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return FontFileUtilNatives.nIsPostScriptType1Font(buffer, index);
+ }
+
+ /** Shadow picker for {@link FontFileUtil}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(null, ShadowNativeFontFileUtil.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontsFontFamily.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontsFontFamily.java
new file mode 100644
index 000000000..360f61365
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeFontsFontFamily.java
@@ -0,0 +1,86 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.Q;
+import static android.os.Build.VERSION_CODES.S;
+
+import android.graphics.fonts.FontFamily;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.FontFamilyBuilderNatives;
+import org.robolectric.nativeruntime.FontsFontFamilyNatives;
+import org.robolectric.shadows.ShadowNativeFontsFontFamily.Picker;
+
+/** Shadow for {@link FontFamily} that is backed by native code */
+@Implements(
+ value = FontFamily.class,
+ minSdk = Q,
+ shadowPicker = Picker.class,
+ isInAndroidSdk = false)
+public class ShadowNativeFontsFontFamily {
+ @Implementation(minSdk = S)
+ protected static int nGetFontSize(long family) {
+ return FontsFontFamilyNatives.nGetFontSize(family);
+ }
+
+ @Implementation(minSdk = S)
+ protected static long nGetFont(long family, int i) {
+ return FontsFontFamilyNatives.nGetFont(family, i);
+ }
+
+ @Implementation(minSdk = S)
+ protected static String nGetLangTags(long family) {
+ return FontsFontFamilyNatives.nGetLangTags(family);
+ }
+
+ @Implementation(minSdk = S)
+ protected static int nGetVariant(long family) {
+ return FontsFontFamilyNatives.nGetVariant(family);
+ }
+
+ /** Shadow for {@link FontFamily.Builder} that is backed by native code */
+ @Implements(
+ value = FontFamily.Builder.class,
+ minSdk = Q,
+ shadowPicker = ShadowNativeFontFamilyBuilder.Picker.class,
+ isInAndroidSdk = false)
+ public static class ShadowNativeFontFamilyBuilder {
+ @Implementation
+ protected static long nInitBuilder() {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return FontFamilyBuilderNatives.nInitBuilder();
+ }
+
+ @Implementation
+ protected static void nAddFont(long builderPtr, long fontPtr) {
+ FontFamilyBuilderNatives.nAddFont(builderPtr, fontPtr);
+ }
+
+ @Implementation
+ protected static long nBuild(
+ long builderPtr, String langTags, int variant, boolean isCustomFallback) {
+ return FontFamilyBuilderNatives.nBuild(builderPtr, langTags, variant, isCustomFallback);
+ }
+
+ @Implementation
+ protected static long nGetReleaseNativeFamily() {
+ return FontFamilyBuilderNatives.nGetReleaseNativeFamily();
+ }
+
+ /** Shadow picker for {@link FontFamily.Builder}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(
+ ShadowFontsFontFamily.ShadowFontsFontFamilyBuilder.class,
+ ShadowNativeFontFamilyBuilder.class);
+ }
+ }
+ }
+
+ /** Shadow picker for {@link FontFamily}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(ShadowFontsFontFamily.class, ShadowNativeFontsFontFamily.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeHardwareRenderer.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeHardwareRenderer.java
new file mode 100644
index 000000000..263522d84
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeHardwareRenderer.java
@@ -0,0 +1,396 @@
+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.TIRAMISU;
+
+import android.graphics.Bitmap;
+import android.graphics.HardwareRenderer;
+import android.graphics.HardwareRenderer.ASurfaceTransactionCallback;
+import android.graphics.HardwareRenderer.FrameCompleteCallback;
+import android.graphics.HardwareRenderer.FrameDrawingCallback;
+import android.graphics.HardwareRenderer.PictureCapturedCallback;
+import android.graphics.HardwareRenderer.PrepareSurfaceControlForWebviewCallback;
+import android.view.Surface;
+import java.io.FileDescriptor;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.HardwareRendererNatives;
+import org.robolectric.shadows.ShadowNativeHardwareRenderer.Picker;
+
+/** Shadow for {@link HardwareRenderer} that is backed by native code */
+@Implements(
+ value = HardwareRenderer.class,
+ minSdk = Q,
+ looseSignatures = true,
+ shadowPicker = Picker.class)
+public class ShadowNativeHardwareRenderer {
+ @Implementation
+ protected static void disableVsync() {
+ HardwareRendererNatives.disableVsync();
+ }
+
+ @Implementation
+ protected static void preload() {
+ HardwareRendererNatives.preload();
+ }
+
+ @Implementation(minSdk = S)
+ protected static boolean isWebViewOverlaysEnabled() {
+ return HardwareRendererNatives.isWebViewOverlaysEnabled();
+ }
+
+ @Implementation
+ protected static void setupShadersDiskCache(String cacheFile, String skiaCacheFile) {
+ HardwareRendererNatives.setupShadersDiskCache(cacheFile, skiaCacheFile);
+ }
+
+ @Implementation
+ protected static void nRotateProcessStatsBuffer() {
+ HardwareRendererNatives.nRotateProcessStatsBuffer();
+ }
+
+ @Implementation
+ protected static void nSetProcessStatsBuffer(int fd) {
+ HardwareRendererNatives.nSetProcessStatsBuffer(fd);
+ }
+
+ @Implementation
+ protected static int nGetRenderThreadTid(long nativeProxy) {
+ return HardwareRendererNatives.nGetRenderThreadTid(nativeProxy);
+ }
+
+ @Implementation
+ protected static long nCreateRootRenderNode() {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return HardwareRendererNatives.nCreateRootRenderNode();
+ }
+
+ @Implementation(minSdk = S)
+ protected static long nCreateProxy(boolean translucent, long rootRenderNode) {
+ return HardwareRendererNatives.nCreateProxy(translucent, rootRenderNode);
+ }
+
+ @Implementation(minSdk = R, maxSdk = R)
+ protected static long nCreateProxy(
+ boolean translucent, boolean isWideGamut, long rootRenderNode) {
+ return nCreateProxy(true, rootRenderNode);
+ }
+
+ @Implementation(minSdk = Q, maxSdk = Q)
+ protected static Object nCreateProxy(Object translucent, Object rootRenderNode) {
+ return nCreateProxy((boolean) translucent, (long) rootRenderNode);
+ }
+
+ @Implementation
+ protected static void nDeleteProxy(long nativeProxy) {
+ HardwareRendererNatives.nDeleteProxy(nativeProxy);
+ }
+
+ @Implementation
+ protected static boolean nLoadSystemProperties(long nativeProxy) {
+ return HardwareRendererNatives.nLoadSystemProperties(nativeProxy);
+ }
+
+ @Implementation
+ protected static void nSetName(long nativeProxy, String name) {
+ HardwareRendererNatives.nSetName(nativeProxy, name);
+ }
+
+ @Implementation(minSdk = R)
+ protected static void nSetSurface(long nativeProxy, Surface window, boolean discardBuffer) {
+ HardwareRendererNatives.nSetSurface(nativeProxy, window, discardBuffer);
+ }
+
+ @Implementation(minSdk = S)
+ protected static void nSetSurfaceControl(long nativeProxy, long nativeSurfaceControl) {
+ HardwareRendererNatives.nSetSurfaceControl(nativeProxy, nativeSurfaceControl);
+ }
+
+ @Implementation
+ protected static boolean nPause(long nativeProxy) {
+ return HardwareRendererNatives.nPause(nativeProxy);
+ }
+
+ @Implementation
+ protected static void nSetStopped(long nativeProxy, boolean stopped) {
+ HardwareRendererNatives.nSetStopped(nativeProxy, stopped);
+ }
+
+ @Implementation
+ protected static void nSetLightGeometry(
+ long nativeProxy, float lightX, float lightY, float lightZ, float lightRadius) {
+ HardwareRendererNatives.nSetLightGeometry(nativeProxy, lightX, lightY, lightZ, lightRadius);
+ }
+
+ @Implementation
+ protected static void nSetLightAlpha(
+ long nativeProxy, float ambientShadowAlpha, float spotShadowAlpha) {
+ HardwareRendererNatives.nSetLightAlpha(nativeProxy, ambientShadowAlpha, spotShadowAlpha);
+ }
+
+ @Implementation
+ protected static void nSetOpaque(long nativeProxy, boolean opaque) {
+ HardwareRendererNatives.nSetOpaque(nativeProxy, opaque);
+ }
+
+ @Implementation(minSdk = S)
+ protected static void nSetColorMode(long nativeProxy, int colorMode) {
+ HardwareRendererNatives.nSetColorMode(nativeProxy, colorMode);
+ }
+
+ @Implementation(minSdk = S)
+ protected static void nSetSdrWhitePoint(long nativeProxy, float whitePoint) {
+ HardwareRendererNatives.nSetSdrWhitePoint(nativeProxy, whitePoint);
+ }
+
+ @Implementation(minSdk = S)
+ protected static void nSetIsHighEndGfx(boolean isHighEndGfx) {
+ HardwareRendererNatives.nSetIsHighEndGfx(isHighEndGfx);
+ }
+
+ @Implementation
+ protected static int nSyncAndDrawFrame(long nativeProxy, long[] frameInfo, int size) {
+ return HardwareRendererNatives.nSyncAndDrawFrame(nativeProxy, frameInfo, size);
+ }
+
+ @Implementation
+ protected static void nDestroy(long nativeProxy, long rootRenderNode) {
+ HardwareRendererNatives.nDestroy(nativeProxy, rootRenderNode);
+ }
+
+ @Implementation
+ protected static void nRegisterAnimatingRenderNode(long rootRenderNode, long animatingNode) {
+ HardwareRendererNatives.nRegisterAnimatingRenderNode(rootRenderNode, animatingNode);
+ }
+
+ @Implementation
+ protected static void nRegisterVectorDrawableAnimator(long rootRenderNode, long animator) {
+ HardwareRendererNatives.nRegisterVectorDrawableAnimator(rootRenderNode, animator);
+ }
+
+ @Implementation
+ protected static long nCreateTextureLayer(long nativeProxy) {
+ return HardwareRendererNatives.nCreateTextureLayer(nativeProxy);
+ }
+
+ @Implementation
+ protected static void nBuildLayer(long nativeProxy, long node) {
+ HardwareRendererNatives.nBuildLayer(nativeProxy, node);
+ }
+
+ @Implementation
+ protected static boolean nCopyLayerInto(long nativeProxy, long layer, long bitmapHandle) {
+ return HardwareRendererNatives.nCopyLayerInto(nativeProxy, layer, bitmapHandle);
+ }
+
+ @Implementation
+ protected static void nPushLayerUpdate(long nativeProxy, long layer) {
+ HardwareRendererNatives.nPushLayerUpdate(nativeProxy, layer);
+ }
+
+ @Implementation
+ protected static void nCancelLayerUpdate(long nativeProxy, long layer) {
+ HardwareRendererNatives.nCancelLayerUpdate(nativeProxy, layer);
+ }
+
+ @Implementation
+ protected static void nDetachSurfaceTexture(long nativeProxy, long layer) {
+ HardwareRendererNatives.nDetachSurfaceTexture(nativeProxy, layer);
+ }
+
+ @Implementation
+ protected static void nDestroyHardwareResources(long nativeProxy) {
+ HardwareRendererNatives.nDestroyHardwareResources(nativeProxy);
+ }
+
+ @Implementation
+ protected static void nTrimMemory(int level) {
+ HardwareRendererNatives.nTrimMemory(level);
+ }
+
+ @Implementation
+ protected static void nOverrideProperty(String name, String value) {
+ HardwareRendererNatives.nOverrideProperty(name, value);
+ }
+
+ @Implementation
+ protected static void nFence(long nativeProxy) {
+ HardwareRendererNatives.nFence(nativeProxy);
+ }
+
+ @Implementation
+ protected static void nStopDrawing(long nativeProxy) {
+ HardwareRendererNatives.nStopDrawing(nativeProxy);
+ }
+
+ @Implementation
+ protected static void nNotifyFramePending(long nativeProxy) {
+ HardwareRendererNatives.nNotifyFramePending(nativeProxy);
+ }
+
+ @Implementation
+ protected static void nDumpProfileInfo(long nativeProxy, FileDescriptor fd, int dumpFlags) {
+ HardwareRendererNatives.nDumpProfileInfo(nativeProxy, fd, dumpFlags);
+ }
+
+ @Implementation
+ protected static void nAddRenderNode(long nativeProxy, long rootRenderNode, boolean placeFront) {
+ HardwareRendererNatives.nAddRenderNode(nativeProxy, rootRenderNode, placeFront);
+ }
+
+ @Implementation
+ protected static void nRemoveRenderNode(long nativeProxy, long rootRenderNode) {
+ HardwareRendererNatives.nRemoveRenderNode(nativeProxy, rootRenderNode);
+ }
+
+ @Implementation
+ protected static void nDrawRenderNode(long nativeProxy, long rootRenderNode) {
+ HardwareRendererNatives.nDrawRenderNode(nativeProxy, rootRenderNode);
+ }
+
+ @Implementation
+ protected static void nSetContentDrawBounds(
+ long nativeProxy, int left, int top, int right, int bottom) {
+ HardwareRendererNatives.nSetContentDrawBounds(nativeProxy, left, top, right, bottom);
+ }
+
+ @Implementation
+ protected static void nSetPictureCaptureCallback(
+ long nativeProxy, PictureCapturedCallback callback) {
+ HardwareRendererNatives.nSetPictureCaptureCallback(nativeProxy, callback);
+ }
+
+ @Implementation(minSdk = S)
+ protected static void nSetASurfaceTransactionCallback(Object nativeProxy, Object callback) {
+ // Requires looseSignatures because ASurfaceTransactionCallback is S+.
+ HardwareRendererNatives.nSetASurfaceTransactionCallback(
+ (long) nativeProxy, (ASurfaceTransactionCallback) callback);
+ }
+
+ @Implementation(minSdk = S)
+ protected static void nSetPrepareSurfaceControlForWebviewCallback(
+ Object nativeProxy, Object callback) {
+ // Need to use loose signatures here as PrepareSurfaceControlForWebviewCallback is S+.
+ HardwareRendererNatives.nSetPrepareSurfaceControlForWebviewCallback(
+ (long) nativeProxy, (PrepareSurfaceControlForWebviewCallback) callback);
+ }
+
+ @Implementation
+ protected static void nSetFrameCallback(long nativeProxy, FrameDrawingCallback callback) {
+ HardwareRendererNatives.nSetFrameCallback(nativeProxy, callback);
+ }
+
+ @Implementation
+ protected static void nSetFrameCompleteCallback(
+ long nativeProxy, FrameCompleteCallback callback) {
+ HardwareRendererNatives.nSetFrameCompleteCallback(nativeProxy, callback);
+ }
+
+ @Implementation(minSdk = R)
+ protected static void nAddObserver(long nativeProxy, long nativeObserver) {
+ HardwareRendererNatives.nAddObserver(nativeProxy, nativeObserver);
+ }
+
+ @Implementation(minSdk = R)
+ protected static void nRemoveObserver(long nativeProxy, long nativeObserver) {
+ HardwareRendererNatives.nRemoveObserver(nativeProxy, nativeObserver);
+ }
+
+ @Implementation(maxSdk = TIRAMISU)
+ protected static int nCopySurfaceInto(
+ Surface surface, int srcLeft, int srcTop, int srcRight, int srcBottom, long bitmapHandle) {
+ return HardwareRendererNatives.nCopySurfaceInto(
+ surface, srcLeft, srcTop, srcRight, srcBottom, bitmapHandle);
+ }
+
+ @Implementation
+ protected static Bitmap nCreateHardwareBitmap(long renderNode, int width, int height) {
+ return HardwareRendererNatives.nCreateHardwareBitmap(renderNode, width, height);
+ }
+
+ @Implementation
+ protected static void nSetHighContrastText(boolean enabled) {
+ HardwareRendererNatives.nSetHighContrastText(enabled);
+ }
+
+ @Implementation(minSdk = Q, maxSdk = S)
+ protected static void nHackySetRTAnimationsEnabled(boolean enabled) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ HardwareRendererNatives.nHackySetRTAnimationsEnabled(enabled);
+ }
+
+ @Implementation
+ protected static void nSetDebuggingEnabled(boolean enabled) {
+ HardwareRendererNatives.nSetDebuggingEnabled(enabled);
+ }
+
+ @Implementation
+ protected static void nSetIsolatedProcess(boolean enabled) {
+ HardwareRendererNatives.nSetIsolatedProcess(enabled);
+ }
+
+ @Implementation
+ protected static void nSetContextPriority(int priority) {
+ HardwareRendererNatives.nSetContextPriority(priority);
+ }
+
+ @Implementation
+ protected static void nAllocateBuffers(long nativeProxy) {
+ HardwareRendererNatives.nAllocateBuffers(nativeProxy);
+ }
+
+ @Implementation
+ protected static void nSetForceDark(long nativeProxy, boolean enabled) {
+ HardwareRendererNatives.nSetForceDark(nativeProxy, enabled);
+ }
+
+ @Implementation(minSdk = S)
+ protected static void nSetDisplayDensityDpi(int densityDpi) {
+ HardwareRendererNatives.nSetDisplayDensityDpi(densityDpi);
+ }
+
+ @Implementation(minSdk = S, maxSdk = TIRAMISU)
+ protected static void nInitDisplayInfo(
+ int width,
+ int height,
+ float refreshRate,
+ int wideColorDataspace,
+ long appVsyncOffsetNanos,
+ long presentationDeadlineNanos) {
+ HardwareRendererNatives.nInitDisplayInfo(
+ width,
+ height,
+ refreshRate,
+ wideColorDataspace,
+ appVsyncOffsetNanos,
+ presentationDeadlineNanos);
+ }
+
+ @Implementation(minSdk = 10000)
+ protected static void nInitDisplayInfo(
+ int width,
+ int height,
+ float refreshRate,
+ int wideColorDataspace,
+ long appVsyncOffsetNanos,
+ long presentationDeadlineNanos,
+ boolean supportsFp16ForHdr) {
+ nInitDisplayInfo(
+ width,
+ height,
+ refreshRate,
+ wideColorDataspace,
+ appVsyncOffsetNanos,
+ presentationDeadlineNanos);
+ }
+
+ /** Shadow picker for {@link HardwareRenderer}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(ShadowHardwareRenderer.class, ShadowNativeHardwareRenderer.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeHardwareRendererObserver.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeHardwareRendererObserver.java
new file mode 100644
index 000000000..97b05eb18
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeHardwareRendererObserver.java
@@ -0,0 +1,60 @@
+package org.robolectric.shadows;
+
+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.HardwareRendererObserver;
+import java.lang.ref.WeakReference;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.HardwareRendererObserverNatives;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowNativeHardwareRendererObserver.Picker;
+
+/** Shadow for {@link HardwareRendererObserver} that is backed by native code */
+@Implements(
+ value = HardwareRendererObserver.class,
+ minSdk = R,
+ shadowPicker = Picker.class,
+ isInAndroidSdk = false)
+public class ShadowNativeHardwareRendererObserver {
+
+ public HardwareRendererObserverNatives hardwareRendererObserverNatives =
+ new HardwareRendererObserverNatives();
+
+ @Implementation
+ protected static int nGetNextBuffer(long nativePtr, long[] data) {
+ return HardwareRendererObserverNatives.nGetNextBuffer(nativePtr, data);
+ }
+
+ @Implementation(minSdk = R, maxSdk = R)
+ protected long nCreateObserver() {
+ return nCreateObserver(false);
+ }
+
+ @Implementation(minSdk = S, maxSdk = S_V2)
+ protected long nCreateObserver(boolean waitForPresentTime) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return hardwareRendererObserverNatives.nCreateObserver(waitForPresentTime);
+ }
+
+ @Implementation(minSdk = TIRAMISU)
+ protected static long nCreateObserver(
+ WeakReference<HardwareRendererObserver> observer, boolean waitForPresentTime) {
+ HardwareRendererObserver hardwareRendererObserver = observer.get();
+ ShadowNativeHardwareRendererObserver shadowNativeHardwareRendererObserver =
+ Shadow.extract(hardwareRendererObserver);
+ return shadowNativeHardwareRendererObserver.hardwareRendererObserverNatives.nCreateObserver(
+ waitForPresentTime);
+ }
+
+ /** Shadow picker for {@link HardwareRendererObserver}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(null, ShadowNativeHardwareRendererObserver.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeImageDecoder.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeImageDecoder.java
new file mode 100644
index 000000000..4913c9f24
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeImageDecoder.java
@@ -0,0 +1,211 @@
+package org.robolectric.shadows;
+
+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 android.content.res.AssetManager.AssetInputStream;
+import android.graphics.Bitmap;
+import android.graphics.ColorSpace;
+import android.graphics.ImageDecoder;
+import android.graphics.ImageDecoder.Source;
+import android.graphics.Rect;
+import android.util.Size;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+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;
+
+/** Shadow for {@link android.graphics.ImageDecoder} that is backed by native code */
+@Implements(value = ImageDecoder.class, minSdk = P, shadowPicker = Picker.class)
+public class ShadowNativeImageDecoder {
+
+ static {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ }
+
+ @Implementation(minSdk = P, maxSdk = Q)
+ protected static ImageDecoder createFromAsset(AssetInputStream ais, Source source)
+ throws IOException {
+ return createFromAsset(ais, false, source);
+ }
+
+ @Implementation(minSdk = R)
+ protected static ImageDecoder createFromAsset(
+ AssetInputStream ais, boolean preferAnimation, Source source) throws IOException {
+ int capacity = ais.available();
+ ByteBuffer buffer = ByteBuffer.allocateDirect(capacity);
+ buffer.order(ByteOrder.nativeOrder());
+ byte[] buf = new byte[8 * 1024]; // 8k
+ int bytesRead;
+ while ((bytesRead = ais.read(buf)) != -1) {
+ buffer.put(buf, 0, bytesRead);
+ }
+ if (ais.read() != -1) {
+ throw new IOException("Unable to access full contents of asset");
+ }
+ return nCreate(buffer, 0, bytesRead, preferAnimation, source);
+ }
+
+ @Implementation(minSdk = P, maxSdk = Q)
+ protected static ImageDecoder nCreate(long asset, Source src) throws IOException {
+ return nCreate(asset, false, src);
+ }
+
+ @Implementation(minSdk = R)
+ protected static ImageDecoder nCreate(long asset, boolean preferAnimation, Source src)
+ throws IOException {
+ throw new UnsupportedEncodingException();
+ }
+
+ @Implementation(minSdk = P, maxSdk = Q)
+ protected static ImageDecoder nCreate(ByteBuffer buffer, int position, int limit, Source src)
+ throws IOException {
+ return nCreate(buffer, position, limit, false, src);
+ }
+
+ @Implementation(minSdk = R)
+ protected static ImageDecoder nCreate(
+ ByteBuffer buffer, int position, int limit, boolean preferAnimation, Source src)
+ throws IOException {
+ return ImageDecoderNatives.nCreate(buffer, position, limit, preferAnimation, src);
+ }
+
+ @Implementation(minSdk = P, maxSdk = Q)
+ protected static ImageDecoder nCreate(byte[] data, int offset, int length, Source src)
+ throws IOException {
+ return nCreate(data, offset, length, false, src);
+ }
+
+ @Implementation(minSdk = R)
+ protected static ImageDecoder nCreate(
+ byte[] data, int offset, int length, boolean preferAnimation, Source src) throws IOException {
+ return ImageDecoderNatives.nCreate(data, offset, length, preferAnimation, src);
+ }
+
+ @Implementation(minSdk = P, maxSdk = Q)
+ protected static ImageDecoder nCreate(InputStream is, byte[] storage, Source src)
+ throws IOException {
+ return nCreate(is, storage, false, src);
+ }
+
+ @Implementation(minSdk = R)
+ protected static ImageDecoder nCreate(
+ InputStream is, byte[] storage, boolean preferAnimation, Source src) throws IOException {
+ return ImageDecoderNatives.nCreate(is, storage, preferAnimation, src);
+ }
+
+ @Implementation(maxSdk = Q)
+ protected static ImageDecoder nCreate(FileDescriptor fd, Source src) throws IOException {
+ throw new UnsupportedEncodingException();
+ }
+
+ @Implementation(minSdk = S)
+ protected static ImageDecoder nCreate(
+ FileDescriptor fd, long length, boolean preferAnimation, Source src) throws IOException {
+ return ImageDecoderNatives.nCreate(fd, length, preferAnimation, src);
+ }
+
+ @Implementation(minSdk = P, maxSdk = P)
+ protected static Bitmap nDecodeBitmap(
+ long nativePtr,
+ ImageDecoder decoder,
+ boolean doPostProcess,
+ int width,
+ int height,
+ Rect cropRect,
+ boolean mutable,
+ int allocator,
+ boolean unpremulRequired,
+ boolean conserveMemory,
+ boolean decodeAsAlphaMask,
+ ColorSpace desiredColorSpace)
+ throws IOException {
+ return nDecodeBitmap(
+ nativePtr,
+ decoder,
+ doPostProcess,
+ width,
+ height,
+ cropRect,
+ mutable,
+ allocator,
+ unpremulRequired,
+ conserveMemory,
+ decodeAsAlphaMask,
+ /* desiredColorSpace = */ 0, // Desired color space is currently not supported in P.
+ /* extended = */ false);
+ }
+
+ @Implementation(minSdk = Q)
+ protected static Bitmap nDecodeBitmap(
+ long nativePtr,
+ ImageDecoder decoder,
+ boolean doPostProcess,
+ int width,
+ int height,
+ Rect cropRect,
+ boolean mutable,
+ int allocator,
+ boolean unpremulRequired,
+ boolean conserveMemory,
+ boolean decodeAsAlphaMask,
+ long desiredColorSpace,
+ boolean extended)
+ throws IOException {
+ return ImageDecoderNatives.nDecodeBitmap(
+ nativePtr,
+ decoder,
+ doPostProcess,
+ width,
+ height,
+ cropRect,
+ mutable,
+ allocator,
+ unpremulRequired,
+ conserveMemory,
+ decodeAsAlphaMask,
+ desiredColorSpace,
+ extended);
+ }
+
+ @Implementation
+ protected static Size nGetSampledSize(long nativePtr, int sampleSize) {
+ return ImageDecoderNatives.nGetSampledSize(nativePtr, sampleSize);
+ }
+
+ @Implementation
+ protected static void nGetPadding(long nativePtr, Rect outRect) {
+ ImageDecoderNatives.nGetPadding(nativePtr, outRect);
+ }
+
+ @Implementation
+ protected static void nClose(long nativePtr) {
+ ImageDecoderNatives.nClose(nativePtr);
+ }
+
+ @Implementation
+ protected static String nGetMimeType(long nativePtr) {
+ return ImageDecoderNatives.nGetMimeType(nativePtr);
+ }
+
+ @Implementation
+ protected static ColorSpace nGetColorSpace(long nativePtr) {
+ return ImageDecoderNatives.nGetColorSpace(nativePtr);
+ }
+
+ /** Shadow picker for {@link ImageDecoder}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(ShadowImageDecoder.class, ShadowNativeImageDecoder.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeInterpolator.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeInterpolator.java
new file mode 100644
index 000000000..21a292c89
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeInterpolator.java
@@ -0,0 +1,55 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+
+import android.graphics.Interpolator;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.InterpolatorNatives;
+import org.robolectric.shadows.ShadowNativeInterpolator.Picker;
+
+/** Shadow for {@link Interpolator} that is backed by native code */
+@Implements(value = Interpolator.class, minSdk = O, shadowPicker = Picker.class)
+public class ShadowNativeInterpolator {
+
+ @Implementation
+ protected static long nativeConstructor(int valueCount, int frameCount) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return InterpolatorNatives.nativeConstructor(valueCount, frameCount);
+ }
+
+ @Implementation
+ protected static void nativeDestructor(long nativeInstance) {
+ InterpolatorNatives.nativeDestructor(nativeInstance);
+ }
+
+ @Implementation
+ protected static void nativeReset(long nativeInstance, int valueCount, int frameCount) {
+ InterpolatorNatives.nativeReset(nativeInstance, valueCount, frameCount);
+ }
+
+ @Implementation
+ protected static void nativeSetKeyFrame(
+ long nativeInstance, int index, int msec, float[] values, float[] blend) {
+ InterpolatorNatives.nativeSetKeyFrame(nativeInstance, index, msec, values, blend);
+ }
+
+ @Implementation
+ protected static void nativeSetRepeatMirror(
+ long nativeInstance, float repeatCount, boolean mirror) {
+ InterpolatorNatives.nativeSetRepeatMirror(nativeInstance, repeatCount, mirror);
+ }
+
+ @Implementation
+ protected static int nativeTimeToValues(long nativeInstance, int msec, float[] values) {
+ return InterpolatorNatives.nativeTimeToValues(nativeInstance, msec, values);
+ }
+
+ /** Shadow picker for {@link Interpolator}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(null, ShadowNativeInterpolator.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLightingColorFilter.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLightingColorFilter.java
new file mode 100644
index 000000000..88e4ea5c3
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLightingColorFilter.java
@@ -0,0 +1,28 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+
+import android.graphics.LightingColorFilter;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.LightingColorFilterNatives;
+import org.robolectric.shadows.ShadowNativeLightingColorFilter.Picker;
+
+/** Shadow for {@link LightingColorFilter} that is backed by native code */
+@Implements(value = LightingColorFilter.class, minSdk = O, shadowPicker = Picker.class)
+public class ShadowNativeLightingColorFilter {
+
+ @Implementation(minSdk = O)
+ protected static long native_CreateLightingFilter(int mul, int add) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return LightingColorFilterNatives.native_CreateLightingFilter(mul, add);
+ }
+
+ /** Shadow picker for {@link LightingColorFilter}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(null, ShadowNativeLightingColorFilter.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLineBreaker.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLineBreaker.java
new file mode 100644
index 000000000..f5d029cce
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLineBreaker.java
@@ -0,0 +1,97 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.Q;
+
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.graphics.text.LineBreaker;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.LineBreakerNatives;
+import org.robolectric.shadows.ShadowNativeLineBreaker.Picker;
+
+/** Shadow for {@link LineBreaker} that is backed by native code */
+@Implements(value = LineBreaker.class, minSdk = Q, shadowPicker = Picker.class)
+public class ShadowNativeLineBreaker {
+ @Implementation
+ protected static long nInit(
+ int breakStrategy, int hyphenationFrequency, boolean isJustified, int[] indents) {
+ return LineBreakerNatives.nInit(breakStrategy, hyphenationFrequency, isJustified, indents);
+ }
+
+ @Implementation
+ protected static long nGetReleaseFunc() {
+ // Called first by the static initializer.
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return LineBreakerNatives.nGetReleaseFunc();
+ }
+
+ @Implementation
+ protected static long nComputeLineBreaks(
+ long nativePtr,
+ char[] text,
+ long measuredTextPtr,
+ @IntRange(from = 0) int length,
+ @FloatRange(from = 0.0f) float firstWidth,
+ @IntRange(from = 0) int firstWidthLineCount,
+ @FloatRange(from = 0.0f) float restWidth,
+ float[] variableTabStops,
+ float defaultTabStop,
+ @IntRange(from = 0) int indentsOffset) {
+ return LineBreakerNatives.nComputeLineBreaks(
+ nativePtr,
+ text,
+ measuredTextPtr,
+ length,
+ firstWidth,
+ firstWidthLineCount,
+ restWidth,
+ variableTabStops,
+ defaultTabStop,
+ indentsOffset);
+ }
+
+ // Result accessors
+ @Implementation
+ protected static int nGetLineCount(long ptr) {
+ return LineBreakerNatives.nGetLineCount(ptr);
+ }
+
+ @Implementation
+ protected static int nGetLineBreakOffset(long ptr, int idx) {
+ return LineBreakerNatives.nGetLineBreakOffset(ptr, idx);
+ }
+
+ @Implementation
+ protected static float nGetLineWidth(long ptr, int idx) {
+ return LineBreakerNatives.nGetLineWidth(ptr, idx);
+ }
+
+ @Implementation
+ protected static float nGetLineAscent(long ptr, int idx) {
+ return LineBreakerNatives.nGetLineAscent(ptr, idx);
+ }
+
+ @Implementation
+ protected static float nGetLineDescent(long ptr, int idx) {
+ return LineBreakerNatives.nGetLineDescent(ptr, idx);
+ }
+
+ @Implementation
+ protected static int nGetLineFlag(long ptr, int idx) {
+ return LineBreakerNatives.nGetLineFlag(ptr, idx);
+ }
+
+ @Implementation
+ protected static long nGetReleaseResultFunc() {
+ return LineBreakerNatives.nGetReleaseResultFunc();
+ }
+
+ /** Shadow picker for {@link LineBreaker}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(ShadowLineBreaker.class, ShadowNativeLineBreaker.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLinearGradient.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLinearGradient.java
new file mode 100644
index 000000000..c3458fcf5
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeLinearGradient.java
@@ -0,0 +1,60 @@
+package org.robolectric.shadows;
+
+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 android.graphics.LinearGradient;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.LinearGradientNatives;
+import org.robolectric.shadows.ShadowNativeLinearGradient.Picker;
+
+/** Shadow for {@link LinearGradient} that is backed by native code */
+@Implements(value = LinearGradient.class, minSdk = O, shadowPicker = Picker.class)
+public class ShadowNativeLinearGradient {
+ @Implementation(minSdk = Q)
+ protected long nativeCreate(
+ long matrix,
+ float x0,
+ float y0,
+ float x1,
+ float y1,
+ long[] colors,
+ float[] positions,
+ int tileMode,
+ long colorSpaceHandle) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return LinearGradientNatives.nativeCreate(
+ matrix, x0, y0, x1, y1, colors, positions, tileMode, colorSpaceHandle);
+ }
+
+ @Implementation(minSdk = O, maxSdk = P)
+ protected long nativeCreate1(
+ long matrix,
+ float x0,
+ float y0,
+ float x1,
+ float y1,
+ int[] colors,
+ float[] positions,
+ int tileMode) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return LinearGradientNatives.nativeCreate1(matrix, x0, y0, x1, y1, colors, positions, tileMode);
+ }
+
+ @Implementation(minSdk = O, maxSdk = P)
+ protected long nativeCreate2(
+ long matrix, float x0, float y0, float x1, float y1, int color0, int color1, int tileMode) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return LinearGradientNatives.nativeCreate2(matrix, x0, y0, x1, y1, color0, color1, tileMode);
+ }
+
+ /** Shadow picker for {@link LinearGradient}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(null, ShadowNativeLinearGradient.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMaskFilter.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMaskFilter.java
new file mode 100644
index 000000000..97b18ac52
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMaskFilter.java
@@ -0,0 +1,26 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+
+import android.graphics.MaskFilter;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.MaskFilterNatives;
+import org.robolectric.shadows.ShadowNativeMaskFilter.Picker;
+
+/** Shadow for {@link MaskFilter} that is backed by native code */
+@Implements(value = MaskFilter.class, minSdk = O, shadowPicker = Picker.class)
+public class ShadowNativeMaskFilter {
+
+ @Implementation(minSdk = O)
+ protected static void nativeDestructor(long nativeFilter) {
+ MaskFilterNatives.nativeDestructor(nativeFilter);
+ }
+
+ /** Shadow picker for {@link MaskFilter}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(null, ShadowNativeMaskFilter.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMatrix.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMatrix.java
new file mode 100644
index 000000000..e840968db
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMatrix.java
@@ -0,0 +1,264 @@
+package org.robolectric.shadows;
+
+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;
+
+import android.graphics.Matrix;
+import android.graphics.RectF;
+import java.util.List;
+import java.util.Map;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.MatrixNatives;
+
+/** Shadow for {@link Matrix} that is backed by native code */
+@Implements(value = Matrix.class, minSdk = O, isInAndroidSdk = false)
+public class ShadowNativeMatrix extends ShadowMatrix {
+
+ @Implementation(minSdk = LOLLIPOP, maxSdk = N_MR1)
+ protected static long native_create(long nSrcOrZero) {
+ return nCreate(nSrcOrZero);
+ }
+
+ @Implementation(minSdk = O)
+ protected static long nCreate(long nSrcOrZero) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return MatrixNatives.nCreate(nSrcOrZero);
+ }
+
+ @Implementation(minSdk = O)
+ protected static long nGetNativeFinalizer() {
+ return MatrixNatives.nGetNativeFinalizer();
+ }
+
+ @Implementation(minSdk = O)
+ protected static boolean nSetRectToRect(long nObject, RectF src, RectF dst, int stf) {
+ return MatrixNatives.nSetRectToRect(nObject, src, dst, stf);
+ }
+
+ @Implementation(minSdk = O)
+ 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)
+ protected static void nMapPoints(
+ long nObject,
+ float[] dst,
+ int dstIndex,
+ float[] src,
+ int srcIndex,
+ int ptCount,
+ boolean isPts) {
+ MatrixNatives.nMapPoints(nObject, dst, dstIndex, src, srcIndex, ptCount, isPts);
+ }
+
+ @Implementation(minSdk = O)
+ protected static boolean nMapRect(long nObject, RectF dst, RectF src) {
+ return MatrixNatives.nMapRect(nObject, dst, src);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nGetValues(long nObject, float[] values) {
+ MatrixNatives.nGetValues(nObject, values);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetValues(long nObject, float[] values) {
+ MatrixNatives.nSetValues(nObject, values);
+ }
+
+ @Implementation(minSdk = O)
+ protected static boolean nIsIdentity(long nObject) {
+ return MatrixNatives.nIsIdentity(nObject);
+ }
+
+ @Implementation(minSdk = O)
+ protected static boolean nIsAffine(long nObject) {
+ return MatrixNatives.nIsAffine(nObject);
+ }
+
+ @Implementation(minSdk = O)
+ protected static boolean nRectStaysRect(long nObject) {
+ return MatrixNatives.nRectStaysRect(nObject);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nReset(long nObject) {
+ MatrixNatives.nReset(nObject);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSet(long nObject, long nOther) {
+ MatrixNatives.nSet(nObject, nOther);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetTranslate(long nObject, float dx, float dy) {
+ MatrixNatives.nSetTranslate(nObject, dx, dy);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetScale(long nObject, float sx, float sy, float px, float py) {
+ MatrixNatives.nSetScale(nObject, sx, sy, px, py);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetScale(long nObject, float sx, float sy) {
+ MatrixNatives.nSetScale(nObject, sx, sy);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetRotate(long nObject, float degrees, float px, float py) {
+ MatrixNatives.nSetRotate(nObject, degrees, px, py);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetRotate(long nObject, float degrees) {
+ MatrixNatives.nSetRotate(nObject, degrees);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetSinCos(
+ long nObject, float sinValue, float cosValue, float px, float py) {
+ MatrixNatives.nSetSinCos(nObject, sinValue, cosValue, px, py);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetSinCos(long nObject, float sinValue, float cosValue) {
+ MatrixNatives.nSetSinCos(nObject, sinValue, cosValue);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetSkew(long nObject, float kx, float ky, float px, float py) {
+ MatrixNatives.nSetSkew(nObject, kx, ky, px, py);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetSkew(long nObject, float kx, float ky) {
+ MatrixNatives.nSetSkew(nObject, kx, ky);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetConcat(long nObject, long nA, long nB) {
+ MatrixNatives.nSetConcat(nObject, nA, nB);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nPreTranslate(long nObject, float dx, float dy) {
+ MatrixNatives.nPreTranslate(nObject, dx, dy);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nPreScale(long nObject, float sx, float sy, float px, float py) {
+ MatrixNatives.nPreScale(nObject, sx, sy, px, py);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nPreScale(long nObject, float sx, float sy) {
+ MatrixNatives.nPreScale(nObject, sx, sy);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nPreRotate(long nObject, float degrees, float px, float py) {
+ MatrixNatives.nPreRotate(nObject, degrees, px, py);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nPreRotate(long nObject, float degrees) {
+ MatrixNatives.nPreRotate(nObject, degrees);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nPreSkew(long nObject, float kx, float ky, float px, float py) {
+ MatrixNatives.nPreSkew(nObject, kx, ky, px, py);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nPreSkew(long nObject, float kx, float ky) {
+ MatrixNatives.nPreSkew(nObject, kx, ky);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nPreConcat(long nObject, long nOtherMatrix) {
+ MatrixNatives.nPreConcat(nObject, nOtherMatrix);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nPostTranslate(long nObject, float dx, float dy) {
+ MatrixNatives.nPostTranslate(nObject, dx, dy);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nPostScale(long nObject, float sx, float sy, float px, float py) {
+ MatrixNatives.nPostScale(nObject, sx, sy, px, py);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nPostScale(long nObject, float sx, float sy) {
+ MatrixNatives.nPostScale(nObject, sx, sy);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nPostRotate(long nObject, float degrees, float px, float py) {
+ MatrixNatives.nPostRotate(nObject, degrees, px, py);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nPostRotate(long nObject, float degrees) {
+ MatrixNatives.nPostRotate(nObject, degrees);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nPostSkew(long nObject, float kx, float ky, float px, float py) {
+ MatrixNatives.nPostSkew(nObject, kx, ky, px, py);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nPostSkew(long nObject, float kx, float ky) {
+ MatrixNatives.nPostSkew(nObject, kx, ky);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nPostConcat(long nObject, long nOtherMatrix) {
+ MatrixNatives.nPostConcat(nObject, nOtherMatrix);
+ }
+
+ @Implementation(minSdk = O)
+ protected static boolean nInvert(long nObject, long nInverse) {
+ return MatrixNatives.nInvert(nObject, nInverse);
+ }
+
+ @Implementation(minSdk = O)
+ protected static float nMapRadius(long nObject, float radius) {
+ return MatrixNatives.nMapRadius(nObject, radius);
+ }
+
+ @Implementation(minSdk = O)
+ protected static boolean nEquals(long nA, long nB) {
+ return MatrixNatives.nEquals(nA, nB);
+ }
+
+ @Override
+ public List<String> getPreOperations() {
+ throw new UnsupportedOperationException("Legacy ShadowMatrix APIs are not supported");
+ }
+
+ @Override
+ public List<String> getPostOperations() {
+ throw new UnsupportedOperationException("Legacy ShadowMatrix APIs are not supported");
+ }
+
+ @Override
+ public Map<String, String> getSetOperations() {
+ throw new UnsupportedOperationException("Legacy ShadowMatrix APIs are not supported");
+ }
+
+ @Override
+ public String getDescription() {
+ throw new UnsupportedOperationException("Legacy ShadowMatrix APIs are not supported");
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMeasuredParagraph.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMeasuredParagraph.java
new file mode 100644
index 000000000..130cd7b9e
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMeasuredParagraph.java
@@ -0,0 +1,73 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.P;
+
+import android.graphics.Rect;
+import android.text.MeasuredParagraph;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.MeasuredTextBuilderNatives;
+import org.robolectric.nativeruntime.MeasuredTextNatives;
+import org.robolectric.shadows.ShadowNativeMeasuredParagraph.Picker;
+
+/** Shadow for {@link MeasuredParagraph} that is backed by native code */
+@Implements(value = MeasuredParagraph.class, minSdk = P, maxSdk = P, shadowPicker = Picker.class)
+public class ShadowNativeMeasuredParagraph {
+ @Implementation
+ protected static long nInitBuilder() {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return MeasuredTextBuilderNatives.nInitBuilder();
+ }
+
+ @Implementation
+ protected static void nAddStyleRun(
+ long nativeBuilderPtr, long paintPtr, int start, int end, boolean isRtl) {
+ MeasuredTextBuilderNatives.nAddStyleRun(nativeBuilderPtr, paintPtr, start, end, isRtl);
+ }
+
+ @Implementation
+ protected static void nAddReplacementRun(
+ long nativeBuilderPtr, long paintPtr, int start, int end, float width) {
+ MeasuredTextBuilderNatives.nAddReplacementRun(nativeBuilderPtr, paintPtr, start, end, width);
+ }
+
+ @Implementation
+ protected static long nBuildNativeMeasuredParagraph(
+ long nativeBuilderPtr, char[] text, boolean computeHyphenation, boolean computeLayout) {
+ return MeasuredTextBuilderNatives.nBuildMeasuredText(
+ nativeBuilderPtr, 0, text, computeHyphenation, computeLayout);
+ }
+
+ @Implementation
+ protected static void nFreeBuilder(long nativeBuilderPtr) {
+ MeasuredTextBuilderNatives.nFreeBuilder(nativeBuilderPtr);
+ }
+
+ @Implementation
+ protected static float nGetWidth(long nativePtr, int start, int end) {
+ return MeasuredTextNatives.nGetWidth(nativePtr, start, end);
+ }
+
+ @Implementation
+ protected static long nGetReleaseFunc() {
+ return MeasuredTextNatives.nGetReleaseFunc();
+ }
+
+ @Implementation
+ protected static int nGetMemoryUsage(long nativePtr) {
+ return MeasuredTextNatives.nGetMemoryUsage(nativePtr);
+ }
+
+ @Implementation
+ protected static void nGetBounds(long nativePtr, char[] buf, int start, int end, Rect rect) {
+ MeasuredTextNatives.nGetBounds(nativePtr, buf, start, end, rect);
+ }
+
+ /** Shadow picker for {@link MeasuredParagraph}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(ShadowMeasuredParagraph.class, ShadowNativeMeasuredParagraph.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMeasuredText.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMeasuredText.java
new file mode 100644
index 000000000..5b82a6cf5
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeMeasuredText.java
@@ -0,0 +1,135 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.Q;
+import static android.os.Build.VERSION_CODES.S_V2;
+import static android.os.Build.VERSION_CODES.TIRAMISU;
+
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.graphics.Rect;
+import android.graphics.text.MeasuredText;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.MeasuredTextBuilderNatives;
+import org.robolectric.nativeruntime.MeasuredTextNatives;
+import org.robolectric.shadows.ShadowNativeMeasuredText.Picker;
+
+/** Shadow for {@link MeasuredText} that is backed by native code */
+@Implements(value = MeasuredText.class, minSdk = Q, shadowPicker = Picker.class)
+public class ShadowNativeMeasuredText {
+ @Implementation
+ 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
+ protected static /* Non Zero */ long nGetReleaseFunc() {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return MeasuredTextNatives.nGetReleaseFunc();
+ }
+
+ @Implementation
+ protected static int nGetMemoryUsage(/* Non Zero */ long nativePtr) {
+ return MeasuredTextNatives.nGetMemoryUsage(nativePtr);
+ }
+
+ @Implementation
+ protected static void nGetBounds(long nativePtr, char[] buf, int start, int end, Rect rect) {
+ MeasuredTextNatives.nGetBounds(nativePtr, buf, start, end, rect);
+ }
+
+ @Implementation
+ protected static float nGetCharWidthAt(long nativePtr, int offset) {
+ return MeasuredTextNatives.nGetCharWidthAt(nativePtr, offset);
+ }
+
+ /** Shadow for {@link MeasuredText.Builder} that is backed by native code */
+ @Implements(
+ value = MeasuredText.Builder.class,
+ minSdk = Q,
+ shadowPicker = ShadowNativeMeasuredTextBuilder.Picker.class)
+ public static class ShadowNativeMeasuredTextBuilder {
+ @Implementation
+ protected static /* Non Zero */ long nInitBuilder() {
+ return MeasuredTextBuilderNatives.nInitBuilder();
+ }
+
+ @Implementation(maxSdk = S_V2)
+ protected static void nAddStyleRun(
+ /* Non Zero */ long nativeBuilderPtr,
+ /* Non Zero */ long paintPtr,
+ @IntRange(from = 0) int start,
+ @IntRange(from = 0) int end,
+ boolean isRtl) {
+ MeasuredTextBuilderNatives.nAddStyleRun(nativeBuilderPtr, paintPtr, start, end, isRtl);
+ }
+
+ @Implementation(minSdk = TIRAMISU)
+ protected static void nAddStyleRun(
+ /* Non Zero */ long nativeBuilderPtr,
+ /* Non Zero */ long paintPtr,
+ int lineBreakStyle,
+ int lineBreakWordStyle,
+ int start,
+ int end,
+ boolean isRtl) {
+ MeasuredTextBuilderNatives.nAddStyleRun(nativeBuilderPtr, paintPtr, start, end, isRtl);
+ }
+
+ @Implementation
+ protected static void nAddReplacementRun(
+ /* Non Zero */ long nativeBuilderPtr,
+ /* Non Zero */ long paintPtr,
+ @IntRange(from = 0) int start,
+ @IntRange(from = 0) int end,
+ @FloatRange(from = 0) float width) {
+ MeasuredTextBuilderNatives.nAddReplacementRun(nativeBuilderPtr, paintPtr, start, end, width);
+ }
+
+ @Implementation(maxSdk = S_V2)
+ protected static long nBuildMeasuredText(
+ /* Non Zero */ long nativeBuilderPtr,
+ long hintMtPtr,
+ char[] text,
+ boolean computeHyphenation,
+ boolean computeLayout) {
+ return MeasuredTextBuilderNatives.nBuildMeasuredText(
+ nativeBuilderPtr, hintMtPtr, text, computeHyphenation, computeLayout);
+ }
+
+ @Implementation(minSdk = TIRAMISU)
+ protected static long nBuildMeasuredText(
+ /* Non Zero */ long nativeBuilderPtr,
+ long hintMtPtr,
+ char[] text,
+ boolean computeHyphenation,
+ boolean computeLayout,
+ boolean fastHyphenationMode) {
+ return MeasuredTextBuilderNatives.nBuildMeasuredText(
+ nativeBuilderPtr, hintMtPtr, text, computeHyphenation, computeLayout);
+ }
+
+ @Implementation
+ protected static void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr) {
+ MeasuredTextBuilderNatives.nFreeBuilder(nativeBuilderPtr);
+ }
+
+ /** Shadow picker for {@link MeasuredText.Builder}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(
+ org.robolectric.shadows.ShadowMeasuredTextBuilder.class,
+ ShadowNativeMeasuredText.ShadowNativeMeasuredTextBuilder.class);
+ }
+ }
+ }
+
+ /** Shadow picker for {@link MeasuredText}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(null, ShadowNativeMeasuredText.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeNativeInterpolatorFactory.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeNativeInterpolatorFactory.java
new file mode 100644
index 000000000..21c80e5c1
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeNativeInterpolatorFactory.java
@@ -0,0 +1,85 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.R;
+
+import android.graphics.animation.NativeInterpolatorFactory;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.NativeInterpolatorFactoryNatives;
+import org.robolectric.shadows.ShadowNativeNativeInterpolatorFactory.Picker;
+
+/** Shadow for {@link NativeInterpolatorFactory} that is backed by native code */
+@Implements(
+ value = NativeInterpolatorFactory.class,
+ minSdk = R,
+ shadowPicker = Picker.class,
+ isInAndroidSdk = false)
+public class ShadowNativeNativeInterpolatorFactory {
+
+ static {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ }
+
+ @Implementation
+ protected static long createAccelerateDecelerateInterpolator() {
+ return NativeInterpolatorFactoryNatives.createAccelerateDecelerateInterpolator();
+ }
+
+ @Implementation
+ protected static long createAccelerateInterpolator(float factor) {
+ return NativeInterpolatorFactoryNatives.createAccelerateInterpolator(factor);
+ }
+
+ @Implementation
+ protected static long createAnticipateInterpolator(float tension) {
+ return NativeInterpolatorFactoryNatives.createAnticipateInterpolator(tension);
+ }
+
+ @Implementation
+ protected static long createAnticipateOvershootInterpolator(float tension) {
+ return NativeInterpolatorFactoryNatives.createAnticipateOvershootInterpolator(tension);
+ }
+
+ @Implementation
+ protected static long createBounceInterpolator() {
+ return NativeInterpolatorFactoryNatives.createBounceInterpolator();
+ }
+
+ @Implementation
+ protected static long createCycleInterpolator(float cycles) {
+ return NativeInterpolatorFactoryNatives.createCycleInterpolator(cycles);
+ }
+
+ @Implementation
+ protected static long createDecelerateInterpolator(float factor) {
+ return NativeInterpolatorFactoryNatives.createDecelerateInterpolator(factor);
+ }
+
+ @Implementation
+ protected static long createLinearInterpolator() {
+ return NativeInterpolatorFactoryNatives.createLinearInterpolator();
+ }
+
+ @Implementation
+ protected static long createOvershootInterpolator(float tension) {
+ return NativeInterpolatorFactoryNatives.createOvershootInterpolator(tension);
+ }
+
+ @Implementation
+ protected static long createPathInterpolator(float[] x, float[] y) {
+ return NativeInterpolatorFactoryNatives.createPathInterpolator(x, y);
+ }
+
+ @Implementation
+ protected static long createLutInterpolator(float[] values) {
+ return NativeInterpolatorFactoryNatives.createLutInterpolator(values);
+ }
+
+ /** Shadow picker for {@link NativeInterpolatorFactory}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(null, ShadowNativeNativeInterpolatorFactory.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeNinePatch.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeNinePatch.java
new file mode 100644
index 000000000..11e5ba867
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeNinePatch.java
@@ -0,0 +1,50 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+import static android.os.Build.VERSION_CODES.Q;
+
+import android.graphics.NinePatch;
+import android.graphics.Rect;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.NinePatchNatives;
+import org.robolectric.shadows.ShadowNativeNinePatch.Picker;
+
+/** Shadow for {@link NinePatch} that is backed by native code */
+@Implements(
+ value = NinePatch.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ isInAndroidSdk = false)
+public class ShadowNativeNinePatch {
+
+ @Implementation
+ protected static boolean isNinePatchChunk(byte[] chunk) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return NinePatchNatives.isNinePatchChunk(chunk);
+ }
+
+ @Implementation
+ protected static long validateNinePatchChunk(byte[] chunk) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return NinePatchNatives.validateNinePatchChunk(chunk);
+ }
+
+ @Implementation
+ protected static void nativeFinalize(long chunk) {
+ NinePatchNatives.nativeFinalize(chunk);
+ }
+
+ @Implementation(minSdk = Q)
+ protected static long nativeGetTransparentRegion(long bitmapHandle, long chunk, Rect location) {
+ return NinePatchNatives.nativeGetTransparentRegion(bitmapHandle, chunk, location);
+ }
+
+ /** Shadow picker for {@link NinePatch}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(ShadowNinePatch.class, ShadowNativeNinePatch.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePaint.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePaint.java
new file mode 100644
index 000000000..94fadb5ab
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePaint.java
@@ -0,0 +1,847 @@
+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.TIRAMISU;
+
+import android.graphics.Paint;
+import android.graphics.Paint.FontMetrics;
+import android.graphics.Paint.FontMetricsInt;
+import android.graphics.Rect;
+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;
+
+/** Shadow for {@link Paint} that is backed by native code */
+@Implements(
+ minSdk = O,
+ value = Paint.class,
+ looseSignatures = true,
+ shadowPicker = Picker.class,
+ isInAndroidSdk = false)
+public class ShadowNativePaint {
+
+ // nGetTextRunCursor methods are non-static
+ private PaintNatives paintNatives = new PaintNatives();
+
+ @Implementation(minSdk = O)
+ protected static long nGetNativeFinalizer() {
+ return PaintNatives.nGetNativeFinalizer();
+ }
+
+ @Implementation(minSdk = O)
+ protected static long nInit() {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ // This native code calls Typeface::resolveDefault, which requires Typeface clinit to have run.
+ ShadowNativeTypeface.ensureInitialized();
+ return PaintNatives.nInit();
+ }
+
+ @Implementation(minSdk = O, maxSdk = P)
+ protected static int nGetHyphenEdit(long paintPtr) {
+ return PaintNatives.nGetEndHyphenEdit(paintPtr);
+ }
+
+ @Implementation(minSdk = O, maxSdk = P)
+ protected static void nSetHyphenEdit(long paintPtr, int hyphen) {
+ PaintNatives.nSetStartHyphenEdit(paintPtr, 0);
+ PaintNatives.nSetEndHyphenEdit(paintPtr, hyphen);
+ }
+
+ @Implementation(minSdk = O)
+ protected static long nInitWithPaint(long paint) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return PaintNatives.nInitWithPaint(paint);
+ }
+
+ @Implementation(minSdk = P)
+ protected static int nBreakText(
+ long nObject,
+ char[] text,
+ int index,
+ int count,
+ float maxWidth,
+ int bidiFlags,
+ float[] measuredWidth) {
+ return PaintNatives.nBreakText(nObject, text, index, count, maxWidth, bidiFlags, measuredWidth);
+ }
+
+ @Implementation(minSdk = P)
+ protected static int nBreakText(
+ long nObject,
+ String text,
+ boolean measureForwards,
+ float maxWidth,
+ int bidiFlags,
+ float[] measuredWidth) {
+ return PaintNatives.nBreakText(
+ nObject, text, measureForwards, maxWidth, bidiFlags, measuredWidth);
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static int nBreakText(
+ long nObject,
+ long typefacePtr,
+ char[] text,
+ int index,
+ int count,
+ float maxWidth,
+ int bidiFlags,
+ float[] measuredWidth) {
+ return PaintNatives.nBreakText(
+ nObject, typefacePtr, text, index, count, maxWidth, bidiFlags, measuredWidth);
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static int nBreakText(
+ long nObject,
+ long typefacePtr,
+ String text,
+ boolean measureForwards,
+ float maxWidth,
+ int bidiFlags,
+ float[] measuredWidth) {
+ return PaintNatives.nBreakText(
+ nObject, typefacePtr, text, measureForwards, maxWidth, bidiFlags, measuredWidth);
+ }
+
+ @Implementation(minSdk = P)
+ protected static float nGetTextAdvances(
+ long paintPtr,
+ char[] text,
+ int index,
+ int count,
+ int contextIndex,
+ int contextCount,
+ int bidiFlags,
+ float[] advances,
+ int advancesIndex) {
+ return PaintNatives.nGetTextAdvances(
+ paintPtr,
+ text,
+ index,
+ count,
+ contextIndex,
+ contextCount,
+ bidiFlags,
+ advances,
+ advancesIndex);
+ }
+
+ @Implementation(minSdk = P)
+ protected static float nGetTextAdvances(
+ long paintPtr,
+ String text,
+ int start,
+ int end,
+ int contextStart,
+ int contextEnd,
+ int bidiFlags,
+ float[] advances,
+ int advancesIndex) {
+ return PaintNatives.nGetTextAdvances(
+ paintPtr, text, start, end, contextStart, contextEnd, bidiFlags, advances, advancesIndex);
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static float nGetTextAdvances(
+ long paintPtr,
+ long typefacePtr,
+ char[] text,
+ int index,
+ int count,
+ int contextIndex,
+ int contextCount,
+ int bidiFlags,
+ float[] advances,
+ int advancesIndex) {
+ return PaintNatives.nGetTextAdvances(
+ paintPtr,
+ typefacePtr,
+ text,
+ index,
+ count,
+ contextIndex,
+ contextCount,
+ bidiFlags,
+ advances,
+ advancesIndex);
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static float nGetTextAdvances(
+ long paintPtr,
+ long typefacePtr,
+ String text,
+ int index,
+ int count,
+ int contextIndex,
+ int contextCount,
+ int bidiFlags,
+ float[] advances,
+ int advancesIndex) {
+ return PaintNatives.nGetTextAdvances(
+ paintPtr,
+ typefacePtr,
+ text,
+ index,
+ count,
+ contextIndex,
+ contextCount,
+ bidiFlags,
+ advances,
+ advancesIndex);
+ }
+
+ @Implementation(minSdk = P)
+ protected int nGetTextRunCursor(
+ long paintPtr,
+ char[] text,
+ int contextStart,
+ int contextLength,
+ int dir,
+ int offset,
+ int cursorOpt) {
+ return paintNatives.nGetTextRunCursor(
+ paintPtr, text, contextStart, contextLength, dir, offset, cursorOpt);
+ }
+
+ @Implementation(minSdk = P)
+ protected int nGetTextRunCursor(
+ long paintPtr,
+ String text,
+ int contextStart,
+ int contextEnd,
+ int dir,
+ int offset,
+ int cursorOpt) {
+ return paintNatives.nGetTextRunCursor(
+ paintPtr, text, contextStart, contextEnd, dir, offset, cursorOpt);
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected int nGetTextRunCursor(
+ long paintPtr,
+ long typefacePtr,
+ char[] text,
+ int contextStart,
+ int contextLength,
+ int dir,
+ int offset,
+ int cursorOpt) {
+ return paintNatives.nGetTextRunCursor(
+ paintPtr, typefacePtr, text, contextStart, contextLength, dir, offset, cursorOpt);
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected int nGetTextRunCursor(
+ long paintPtr,
+ long typefacePtr,
+ String text,
+ int contextStart,
+ int contextEnd,
+ int dir,
+ int offset,
+ int cursorOpt) {
+ return paintNatives.nGetTextRunCursor(
+ paintPtr, typefacePtr, text, contextStart, contextEnd, dir, offset, cursorOpt);
+ }
+
+ @Implementation(minSdk = P)
+ protected static void nGetTextPath(
+ long paintPtr,
+ int bidiFlags,
+ char[] text,
+ int index,
+ int count,
+ float x,
+ float y,
+ long path) {
+ PaintNatives.nGetTextPath(paintPtr, bidiFlags, text, index, count, x, y, path);
+ }
+
+ @Implementation(minSdk = P)
+ 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);
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static void nGetTextPath(
+ long paintPtr,
+ long typefacePtr,
+ int bidiFlags,
+ char[] text,
+ int index,
+ int count,
+ float x,
+ float y,
+ long path) {
+ PaintNatives.nGetTextPath(paintPtr, typefacePtr, bidiFlags, text, index, count, x, y, path);
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static void nGetTextPath(
+ long paintPtr,
+ long typefacePtr,
+ int bidiFlags,
+ String text,
+ int start,
+ int end,
+ float x,
+ float y,
+ long path) {
+ PaintNatives.nGetTextPath(paintPtr, typefacePtr, bidiFlags, text, start, end, x, y, path);
+ }
+
+ @Implementation(minSdk = P)
+ protected static void nGetStringBounds(
+ long nativePaint, String text, int start, int end, int bidiFlags, Rect bounds) {
+ PaintNatives.nGetStringBounds(nativePaint, text, start, end, bidiFlags, bounds);
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static void nGetStringBounds(
+ long nativePaint,
+ long typefacePtr,
+ String text,
+ int start,
+ int end,
+ int bidiFlags,
+ Rect bounds) {
+ PaintNatives.nGetStringBounds(nativePaint, typefacePtr, text, start, end, bidiFlags, bounds);
+ }
+
+ @Implementation(minSdk = O, maxSdk = P)
+ protected static int nGetColor(long paintPtr) {
+ return PaintNatives.nGetColor(paintPtr);
+ }
+
+ @Implementation(minSdk = O, maxSdk = P)
+ protected static int nGetAlpha(long paintPtr) {
+ return PaintNatives.nGetAlpha(paintPtr);
+ }
+
+ @Implementation(minSdk = P)
+ protected static void nGetCharArrayBounds(
+ long nativePaint, char[] text, int index, int count, int bidiFlags, Rect bounds) {
+ PaintNatives.nGetCharArrayBounds(nativePaint, text, index, count, bidiFlags, bounds);
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static void nGetCharArrayBounds(
+ long nativePaint,
+ long typefacePtr,
+ char[] text,
+ int index,
+ int count,
+ int bidiFlags,
+ Rect bounds) {
+ PaintNatives.nGetCharArrayBounds(
+ nativePaint, typefacePtr, text, index, count, bidiFlags, bounds);
+ }
+
+ @Implementation(minSdk = P)
+ protected static boolean nHasGlyph(long paintPtr, int bidiFlags, String string) {
+ return PaintNatives.nHasGlyph(paintPtr, bidiFlags, string);
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static boolean nHasGlyph(
+ long paintPtr, long typefacePtr, int bidiFlags, String string) {
+ return PaintNatives.nHasGlyph(paintPtr, typefacePtr, bidiFlags, string);
+ }
+
+ @Implementation(minSdk = P)
+ protected static float nGetRunAdvance(
+ long paintPtr,
+ char[] text,
+ int start,
+ int end,
+ int contextStart,
+ int contextEnd,
+ boolean isRtl,
+ int offset) {
+ return PaintNatives.nGetRunAdvance(
+ paintPtr, text, start, end, contextStart, contextEnd, isRtl, offset);
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static float nGetRunAdvance(
+ long paintPtr,
+ long typefacePtr,
+ char[] text,
+ int start,
+ int end,
+ int contextStart,
+ int contextEnd,
+ boolean isRtl,
+ int offset) {
+ return PaintNatives.nGetRunAdvance(
+ paintPtr, text, start, end, contextStart, contextEnd, isRtl, offset);
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static int nGetOffsetForAdvance(
+ long paintPtr,
+ long typefacePtr,
+ char[] text,
+ int start,
+ int end,
+ int contextStart,
+ int contextEnd,
+ boolean isRtl,
+ float advance) {
+ return PaintNatives.nGetOffsetForAdvance(
+ paintPtr, typefacePtr, text, start, end, contextStart, contextEnd, isRtl, advance);
+ }
+
+ @Implementation(minSdk = P)
+ protected static int nGetOffsetForAdvance(
+ long paintPtr,
+ char[] text,
+ int start,
+ int end,
+ int contextStart,
+ int contextEnd,
+ boolean isRtl,
+ float advance) {
+ return PaintNatives.nGetOffsetForAdvance(
+ paintPtr, text, start, end, contextStart, contextEnd, isRtl, advance);
+ }
+
+ @Implementation(minSdk = O)
+ protected static int nSetTextLocales(long paintPtr, String locales) {
+ return PaintNatives.nSetTextLocales(paintPtr, locales);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetFontFeatureSettings(long paintPtr, String settings) {
+ PaintNatives.nSetFontFeatureSettings(paintPtr, settings);
+ }
+
+ @Implementation(minSdk = P)
+ protected static float nGetFontMetrics(long paintPtr, FontMetrics metrics) {
+ return PaintNatives.nGetFontMetrics(paintPtr, metrics);
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static float nGetFontMetrics(long paintPtr, long typefacePtr, FontMetrics metrics) {
+ return PaintNatives.nGetFontMetrics(paintPtr, typefacePtr, metrics);
+ }
+
+ @Implementation(minSdk = P)
+ protected static int nGetFontMetricsInt(long paintPtr, FontMetricsInt fmi) {
+ return PaintNatives.nGetFontMetricsInt(paintPtr, fmi);
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static int nGetFontMetricsInt(long paintPtr, long typefacePtr, FontMetricsInt fmi) {
+ return PaintNatives.nGetFontMetricsInt(paintPtr, typefacePtr, fmi);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nReset(long paintPtr) {
+ PaintNatives.nReset(paintPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSet(long paintPtrDest, long paintPtrSrc) {
+ PaintNatives.nSet(paintPtrDest, paintPtrSrc);
+ }
+
+ @Implementation(minSdk = O)
+ protected static int nGetStyle(long paintPtr) {
+ return PaintNatives.nGetStyle(paintPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetStyle(long paintPtr, int style) {
+ PaintNatives.nSetStyle(paintPtr, style);
+ }
+
+ @Implementation(minSdk = O)
+ protected static int nGetStrokeCap(long paintPtr) {
+ return PaintNatives.nGetStrokeCap(paintPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetStrokeCap(long paintPtr, int cap) {
+ PaintNatives.nSetStrokeCap(paintPtr, cap);
+ }
+
+ @Implementation(minSdk = O)
+ protected static int nGetStrokeJoin(long paintPtr) {
+ return PaintNatives.nGetStrokeJoin(paintPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetStrokeJoin(long paintPtr, int join) {
+ PaintNatives.nSetStrokeJoin(paintPtr, join);
+ }
+
+ @Implementation(minSdk = O)
+ protected static boolean nGetFillPath(long paintPtr, long src, long dst) {
+ return PaintNatives.nGetFillPath(paintPtr, src, dst);
+ }
+
+ @Implementation(minSdk = O)
+ protected static long nSetShader(long paintPtr, long shader) {
+ return PaintNatives.nSetShader(paintPtr, shader);
+ }
+
+ @Implementation(minSdk = O)
+ protected static long nSetColorFilter(long paintPtr, long filter) {
+ return PaintNatives.nSetColorFilter(paintPtr, filter);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetXfermode(long paintPtr, int xfermode) {
+ PaintNatives.nSetXfermode(paintPtr, xfermode);
+ }
+
+ @Implementation(minSdk = O)
+ protected static long nSetPathEffect(long paintPtr, long effect) {
+ return PaintNatives.nSetPathEffect(paintPtr, effect);
+ }
+
+ @Implementation(minSdk = O)
+ protected static long nSetMaskFilter(long paintPtr, long maskfilter) {
+ return PaintNatives.nSetMaskFilter(paintPtr, maskfilter);
+ }
+
+ @Implementation(minSdk = P)
+ protected static void nSetTypeface(long paintPtr, long typeface) {
+ PaintNatives.nSetTypeface(paintPtr, typeface);
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static Object nSetTypeface(Object paintPtr, Object typeface) {
+ PaintNatives.nSetTypeface((long) paintPtr, (long) typeface);
+ return paintPtr;
+ }
+
+ @Implementation(minSdk = O)
+ protected static int nGetTextAlign(long paintPtr) {
+ return PaintNatives.nGetTextAlign(paintPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetTextAlign(long paintPtr, int align) {
+ PaintNatives.nSetTextAlign(paintPtr, align);
+ }
+
+ @Implementation(minSdk = P)
+ protected static void nSetTextLocalesByMinikinLocaleListId(
+ long paintPtr, int mMinikinLocaleListId) {
+ PaintNatives.nSetTextLocalesByMinikinLocaleListId(paintPtr, mMinikinLocaleListId);
+ }
+
+ @Implementation(minSdk = Q)
+ protected static void nSetShadowLayer(
+ long paintPtr,
+ float radius,
+ float dx,
+ float dy,
+ long colorSpaceHandle,
+ @ColorLong long shadowColor) {
+ PaintNatives.nSetShadowLayer(paintPtr, radius, dx, dy, colorSpaceHandle, shadowColor);
+ }
+
+ @Implementation(minSdk = O, maxSdk = P)
+ protected static void nSetShadowLayer(
+ long paintPtr, float radius, float dx, float dy, int color) {
+ PaintNatives.nSetShadowLayer(paintPtr, radius, dx, dy, color);
+ }
+
+ @Implementation(minSdk = O)
+ protected static boolean nHasShadowLayer(long paintPtr) {
+ return PaintNatives.nHasShadowLayer(paintPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static float nGetLetterSpacing(long paintPtr) {
+ return PaintNatives.nGetLetterSpacing(paintPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetLetterSpacing(long paintPtr, float letterSpacing) {
+ PaintNatives.nSetLetterSpacing(paintPtr, letterSpacing);
+ }
+
+ @Implementation(minSdk = O)
+ protected static float nGetWordSpacing(long paintPtr) {
+ return PaintNatives.nGetWordSpacing(paintPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetWordSpacing(long paintPtr, float wordSpacing) {
+ PaintNatives.nSetWordSpacing(paintPtr, wordSpacing);
+ }
+
+ @Implementation(minSdk = Q)
+ protected static int nGetStartHyphenEdit(long paintPtr) {
+ return PaintNatives.nGetStartHyphenEdit(paintPtr);
+ }
+
+ @Implementation(minSdk = Q)
+ protected static int nGetEndHyphenEdit(long paintPtr) {
+ return PaintNatives.nGetEndHyphenEdit(paintPtr);
+ }
+
+ @Implementation(minSdk = Q)
+ protected static void nSetStartHyphenEdit(long paintPtr, int hyphen) {
+ PaintNatives.nSetStartHyphenEdit(paintPtr, hyphen);
+ }
+
+ @Implementation(minSdk = Q)
+ protected static void nSetEndHyphenEdit(long paintPtr, int hyphen) {
+ PaintNatives.nSetEndHyphenEdit(paintPtr, hyphen);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetStrokeMiter(long paintPtr, float miter) {
+ PaintNatives.nSetStrokeMiter(paintPtr, miter);
+ }
+
+ @Implementation(minSdk = O)
+ protected static float nGetStrokeMiter(long paintPtr) {
+ return PaintNatives.nGetStrokeMiter(paintPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetStrokeWidth(long paintPtr, float width) {
+ PaintNatives.nSetStrokeWidth(paintPtr, width);
+ }
+
+ @Implementation(minSdk = O)
+ protected static float nGetStrokeWidth(long paintPtr) {
+ return PaintNatives.nGetStrokeWidth(paintPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetAlpha(long paintPtr, int a) {
+ PaintNatives.nSetAlpha(paintPtr, a);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetDither(long paintPtr, boolean dither) {
+ PaintNatives.nSetDither(paintPtr, dither);
+ }
+
+ @Implementation(minSdk = O)
+ protected static int nGetFlags(long paintPtr) {
+ return PaintNatives.nGetFlags(paintPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetFlags(long paintPtr, int flags) {
+ PaintNatives.nSetFlags(paintPtr, flags);
+ }
+
+ @Implementation(minSdk = O)
+ protected static int nGetHinting(long paintPtr) {
+ return PaintNatives.nGetHinting(paintPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetHinting(long paintPtr, int mode) {
+ PaintNatives.nSetHinting(paintPtr, mode);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetAntiAlias(long paintPtr, boolean aa) {
+ PaintNatives.nSetAntiAlias(paintPtr, aa);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetLinearText(long paintPtr, boolean linearText) {
+ PaintNatives.nSetLinearText(paintPtr, linearText);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetSubpixelText(long paintPtr, boolean subpixelText) {
+ PaintNatives.nSetSubpixelText(paintPtr, subpixelText);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetUnderlineText(long paintPtr, boolean underlineText) {
+ PaintNatives.nSetUnderlineText(paintPtr, underlineText);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetFakeBoldText(long paintPtr, boolean fakeBoldText) {
+ PaintNatives.nSetFakeBoldText(paintPtr, fakeBoldText);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetFilterBitmap(long paintPtr, boolean filter) {
+ PaintNatives.nSetFilterBitmap(paintPtr, filter);
+ }
+
+ @Implementation(minSdk = Q)
+ protected static void nSetColor(long paintPtr, long colorSpaceHandle, @ColorLong long color) {
+ PaintNatives.nSetColor(paintPtr, colorSpaceHandle, color);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetColor(long paintPtr, @ColorInt int color) {
+ PaintNatives.nSetColor(paintPtr, color);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetStrikeThruText(long paintPtr, boolean strikeThruText) {
+ PaintNatives.nSetStrikeThruText(paintPtr, strikeThruText);
+ }
+
+ @Implementation(minSdk = O)
+ protected static boolean nIsElegantTextHeight(long paintPtr) {
+ return PaintNatives.nIsElegantTextHeight(paintPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetElegantTextHeight(long paintPtr, boolean elegant) {
+ PaintNatives.nSetElegantTextHeight(paintPtr, elegant);
+ }
+
+ @Implementation(minSdk = O)
+ protected static float nGetTextSize(long paintPtr) {
+ return PaintNatives.nGetTextSize(paintPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static float nGetTextScaleX(long paintPtr) {
+ return PaintNatives.nGetTextScaleX(paintPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetTextScaleX(long paintPtr, float scaleX) {
+ PaintNatives.nSetTextScaleX(paintPtr, scaleX);
+ }
+
+ @Implementation(minSdk = O)
+ protected static float nGetTextSkewX(long paintPtr) {
+ return PaintNatives.nGetTextSkewX(paintPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetTextSkewX(long paintPtr, float skewX) {
+ PaintNatives.nSetTextSkewX(paintPtr, skewX);
+ }
+
+ @Implementation(minSdk = P)
+ protected static float nAscent(long paintPtr) {
+ return PaintNatives.nAscent(paintPtr);
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static float nAscent(long paintPtr, long typefacePtr) {
+ return PaintNatives.nAscent(paintPtr, typefacePtr);
+ }
+
+ @Implementation(minSdk = P)
+ protected static float nDescent(long paintPtr) {
+ return PaintNatives.nDescent(paintPtr);
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static float nDescent(long paintPtr, long typefacePtr) {
+ return PaintNatives.nDescent(paintPtr, typefacePtr);
+ }
+
+ @Implementation(minSdk = P)
+ protected static float nGetUnderlinePosition(long paintPtr) {
+ return PaintNatives.nGetUnderlinePosition(paintPtr);
+ }
+
+ @Implementation(minSdk = P)
+ protected static float nGetUnderlineThickness(long paintPtr) {
+ return PaintNatives.nGetUnderlineThickness(paintPtr);
+ }
+
+ @Implementation(minSdk = P)
+ protected static float nGetStrikeThruPosition(long paintPtr) {
+ return PaintNatives.nGetStrikeThruPosition(paintPtr);
+ }
+
+ @Implementation(minSdk = P)
+ protected static float nGetStrikeThruThickness(long paintPtr) {
+ return PaintNatives.nGetStrikeThruThickness(paintPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetTextSize(long paintPtr, float textSize) {
+ PaintNatives.nSetTextSize(paintPtr, textSize);
+ }
+
+ @Implementation(minSdk = P)
+ protected static boolean nEqualsForTextMeasurement(long leftPaintPtr, long rightPaintPtr) {
+ return PaintNatives.nEqualsForTextMeasurement(leftPaintPtr, rightPaintPtr);
+ }
+
+ @Implementation(minSdk = TIRAMISU)
+ protected static void nGetFontMetricsIntForText(
+ long paintPtr,
+ char[] text,
+ int start,
+ int count,
+ int ctxStart,
+ int ctxCount,
+ boolean isRtl,
+ FontMetricsInt outMetrics) {
+ PaintNatives.nGetFontMetricsIntForText(
+ paintPtr, text, start, count, ctxStart, ctxCount, isRtl, outMetrics);
+ }
+
+ @Implementation(minSdk = TIRAMISU)
+ protected static void nGetFontMetricsIntForText(
+ long paintPtr,
+ String text,
+ int start,
+ int count,
+ int ctxStart,
+ int ctxCount,
+ boolean isRtl,
+ FontMetricsInt outMetrics) {
+ PaintNatives.nGetFontMetricsIntForText(
+ paintPtr, text, start, count, ctxStart, ctxCount, isRtl, outMetrics);
+ }
+
+ @Implementation(minSdk = 10000)
+ protected static float nGetRunCharacterAdvance(
+ long paintPtr,
+ char[] text,
+ int start,
+ int end,
+ int contextStart,
+ int contextEnd,
+ boolean isRtl,
+ int offset,
+ float[] advances,
+ int advancesIndex) {
+ return PaintNatives.nGetRunCharacterAdvance(
+ paintPtr,
+ text,
+ start,
+ end,
+ contextStart,
+ contextEnd,
+ isRtl,
+ offset,
+ advances,
+ advancesIndex);
+ }
+
+ /** Shadow picker for {@link Paint}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(ShadowPaint.class, ShadowNativePaint.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePath.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePath.java
new file mode 100644
index 000000000..c162df5a5
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePath.java
@@ -0,0 +1,243 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+import static android.os.Build.VERSION_CODES.P;
+
+import android.graphics.Path;
+import android.graphics.RectF;
+import java.util.List;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.PathNatives;
+
+/** Shadow for {@link Path} that is backed by native code */
+@Implements(value = Path.class, minSdk = O, isInAndroidSdk = false)
+public class ShadowNativePath extends ShadowPath {
+
+ @Implementation(minSdk = O)
+ protected static long nInit() {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return PathNatives.nInit();
+ }
+
+ @Implementation(minSdk = O)
+ protected static long nInit(long nPath) {
+ // Required for pre-P.
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return PathNatives.nInit(nPath);
+ }
+
+ @Implementation(minSdk = P)
+ protected static long nGetFinalizer() {
+ // Required for pre-P.
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return PathNatives.nGetFinalizer();
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSet(long nativeDst, long nSrc) {
+ PathNatives.nSet(nativeDst, nSrc);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nComputeBounds(long nPath, RectF bounds) {
+ PathNatives.nComputeBounds(nPath, bounds);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nIncReserve(long nPath, int extraPtCount) {
+ PathNatives.nIncReserve(nPath, extraPtCount);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nMoveTo(long nPath, float x, float y) {
+ PathNatives.nMoveTo(nPath, x, y);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nRMoveTo(long nPath, float dx, float dy) {
+ PathNatives.nRMoveTo(nPath, dx, dy);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nLineTo(long nPath, float x, float y) {
+ PathNatives.nLineTo(nPath, x, y);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nRLineTo(long nPath, float dx, float dy) {
+ PathNatives.nRLineTo(nPath, dx, dy);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nQuadTo(long nPath, float x1, float y1, float x2, float y2) {
+ PathNatives.nQuadTo(nPath, x1, y1, x2, y2);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nRQuadTo(long nPath, float dx1, float dy1, float dx2, float dy2) {
+ PathNatives.nRQuadTo(nPath, dx1, dy1, dx2, dy2);
+ }
+
+ @Implementation(minSdk = O)
+ 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)
+ 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)
+ protected static void nArcTo(
+ long nPath,
+ float left,
+ float top,
+ float right,
+ float bottom,
+ float startAngle,
+ float sweepAngle,
+ boolean forceMoveTo) {
+ PathNatives.nArcTo(nPath, left, top, right, bottom, startAngle, sweepAngle, forceMoveTo);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nClose(long nPath) {
+ PathNatives.nClose(nPath);
+ }
+
+ @Implementation(minSdk = O)
+ 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)
+ 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)
+ protected static void nAddCircle(long nPath, float x, float y, float radius, int dir) {
+ PathNatives.nAddCircle(nPath, x, y, radius, dir);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nAddArc(
+ long nPath,
+ float left,
+ float top,
+ float right,
+ float bottom,
+ float startAngle,
+ float sweepAngle) {
+ PathNatives.nAddArc(nPath, left, top, right, bottom, startAngle, sweepAngle);
+ }
+
+ @Implementation(minSdk = O)
+ 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)
+ 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)
+ protected static void nAddPath(long nPath, long src, float dx, float dy) {
+ PathNatives.nAddPath(nPath, src, dx, dy);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nAddPath(long nPath, long src) {
+ PathNatives.nAddPath(nPath, src);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nAddPath(long nPath, long src, long matrix) {
+ PathNatives.nAddPath(nPath, src, matrix);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nOffset(long nPath, float dx, float dy) {
+ PathNatives.nOffset(nPath, dx, dy);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetLastPoint(long nPath, float dx, float dy) {
+ PathNatives.nSetLastPoint(nPath, dx, dy);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nTransform(long nPath, long matrix, long dstPath) {
+ PathNatives.nTransform(nPath, matrix, dstPath);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nTransform(long nPath, long matrix) {
+ PathNatives.nTransform(nPath, matrix);
+ }
+
+ @Implementation(minSdk = O)
+ protected static boolean nOp(long path1, long path2, int op, long result) {
+ return PathNatives.nOp(path1, path2, op, result);
+ }
+
+ @Implementation(minSdk = O)
+ protected static boolean nIsRect(long nPath, RectF rect) {
+ return PathNatives.nIsRect(nPath, rect);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nReset(long nPath) {
+ PathNatives.nReset(nPath);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nRewind(long nPath) {
+ PathNatives.nRewind(nPath);
+ }
+
+ @Implementation(minSdk = O)
+ protected static boolean nIsEmpty(long nPath) {
+ return PathNatives.nIsEmpty(nPath);
+ }
+
+ @Implementation(minSdk = O)
+ protected static boolean nIsConvex(long nPath) {
+ return PathNatives.nIsConvex(nPath);
+ }
+
+ @Implementation(minSdk = O)
+ protected static int nGetFillType(long nPath) {
+ return PathNatives.nGetFillType(nPath);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetFillType(long nPath, int ft) {
+ PathNatives.nSetFillType(nPath, ft);
+ }
+
+ @Implementation(minSdk = O)
+ protected static float[] nApproximate(long nPath, float error) {
+ return PathNatives.nApproximate(nPath, error);
+ }
+
+ @Override
+ public List<Point> getPoints() {
+ throw new UnsupportedOperationException("Legacy ShadowPath description APIs are not supported");
+ }
+
+ @Override
+ public void fillBounds(RectF bounds) {
+ 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
new file mode 100644
index 000000000..b72b0b231
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathDashPathEffect.java
@@ -0,0 +1,28 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+
+import android.graphics.PathDashPathEffect;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.PathDashPathEffectNatives;
+import org.robolectric.shadows.ShadowNativePathDashPathEffect.Picker;
+
+/** Shadow for {@link PathDashPathEffect} that is backed by native code */
+@Implements(value = PathDashPathEffect.class, minSdk = O, shadowPicker = Picker.class)
+public class ShadowNativePathDashPathEffect {
+
+ @Implementation(minSdk = O)
+ protected static long nativeCreate(long nativePath, float advance, float phase, int nativeStyle) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return PathDashPathEffectNatives.nativeCreate(nativePath, advance, phase, nativeStyle);
+ }
+
+ /** Shadow picker for {@link PathDashPathEffect}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(null, ShadowNativePathDashPathEffect.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathEffect.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathEffect.java
new file mode 100644
index 000000000..440eb2642
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathEffect.java
@@ -0,0 +1,26 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+
+import android.graphics.PathEffect;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.PathEffectNatives;
+import org.robolectric.shadows.ShadowNativePathEffect.Picker;
+
+/** Shadow for {@link PathEffect} that is backed by native code */
+@Implements(value = PathEffect.class, minSdk = O, shadowPicker = Picker.class)
+public class ShadowNativePathEffect {
+
+ @Implementation(minSdk = O)
+ protected static void nativeDestructor(long nativePatheffect) {
+ PathEffectNatives.nativeDestructor(nativePatheffect);
+ }
+
+ /** Shadow picker for {@link PathEffect}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(null, ShadowNativePathEffect.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathMeasure.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathMeasure.java
new file mode 100644
index 000000000..fd450e912
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathMeasure.java
@@ -0,0 +1,76 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+
+import android.graphics.PathMeasure;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.PathMeasureNatives;
+import org.robolectric.shadows.ShadowNativePathMeasure.Picker;
+
+/** Shadow for {@link PathMeasure} that is backed by native code */
+@Implements(
+ value = PathMeasure.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ isInAndroidSdk = false)
+public class ShadowNativePathMeasure {
+
+ @Implementation(minSdk = O)
+ protected static long native_create(long nativePath, boolean forceClosed) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return PathMeasureNatives.native_create(nativePath, forceClosed);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void native_setPath(long nativeInstance, long nativePath, boolean forceClosed) {
+ PathMeasureNatives.native_setPath(nativeInstance, nativePath, forceClosed);
+ }
+
+ @Implementation(minSdk = O)
+ protected static float native_getLength(long nativeInstance) {
+ return PathMeasureNatives.native_getLength(nativeInstance);
+ }
+
+ @Implementation(minSdk = O)
+ protected static boolean native_getPosTan(
+ long nativeInstance, float distance, float[] pos, float[] tan) {
+ return PathMeasureNatives.native_getPosTan(nativeInstance, distance, pos, tan);
+ }
+
+ @Implementation(minSdk = O)
+ protected static boolean native_getMatrix(
+ long nativeInstance, float distance, long nativeMatrix, int flags) {
+ return PathMeasureNatives.native_getMatrix(nativeInstance, distance, nativeMatrix, flags);
+ }
+
+ @Implementation(minSdk = O)
+ 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)
+ protected static boolean native_isClosed(long nativeInstance) {
+ return PathMeasureNatives.native_isClosed(nativeInstance);
+ }
+
+ @Implementation(minSdk = O)
+ protected static boolean native_nextContour(long nativeInstance) {
+ return PathMeasureNatives.native_nextContour(nativeInstance);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void native_destroy(long nativeInstance) {
+ PathMeasureNatives.native_destroy(nativeInstance);
+ }
+
+ /** Shadow picker for {@link PathMeasure}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(ShadowPathMeasure.class, ShadowNativePathMeasure.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathParser.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathParser.java
new file mode 100644
index 000000000..cf83491ad
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePathParser.java
@@ -0,0 +1,76 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+
+import android.util.PathParser;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.PathParserNatives;
+import org.robolectric.shadows.ShadowNativePathParser.Picker;
+
+/** Shadow for {@link PathParser} that is backed by native code */
+@Implements(
+ value = PathParser.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ isInAndroidSdk = false)
+public class ShadowNativePathParser {
+
+ static {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nParseStringForPath(long pathPtr, String pathString, int stringLength) {
+ PathParserNatives.nParseStringForPath(pathPtr, pathString, stringLength);
+ }
+
+ @Implementation(minSdk = O)
+ protected static long nCreatePathDataFromString(String pathString, int stringLength) {
+ return PathParserNatives.nCreatePathDataFromString(pathString, stringLength);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nCreatePathFromPathData(long outPathPtr, long pathData) {
+ PathParserNatives.nCreatePathFromPathData(outPathPtr, pathData);
+ }
+
+ @Implementation(minSdk = O)
+ protected static long nCreateEmptyPathData() {
+ return PathParserNatives.nCreateEmptyPathData();
+ }
+
+ @Implementation(minSdk = O)
+ protected static long nCreatePathData(long nativePtr) {
+ return PathParserNatives.nCreatePathData(nativePtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static boolean nInterpolatePathData(
+ long outDataPtr, long fromDataPtr, long toDataPtr, float fraction) {
+ return PathParserNatives.nInterpolatePathData(outDataPtr, fromDataPtr, toDataPtr, fraction);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nFinalize(long nativePtr) {
+ PathParserNatives.nFinalize(nativePtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static boolean nCanMorph(long fromDataPtr, long toDataPtr) {
+ return PathParserNatives.nCanMorph(fromDataPtr, toDataPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetPathData(long outDataPtr, long fromDataPtr) {
+ PathParserNatives.nSetPathData(outDataPtr, fromDataPtr);
+ }
+
+ /** Shadow picker for {@link PathParser}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(ShadowPathParser.class, ShadowNativePathParser.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePicture.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePicture.java
new file mode 100644
index 000000000..493076c28
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePicture.java
@@ -0,0 +1,72 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+
+import android.graphics.Picture;
+import java.io.InputStream;
+import java.io.OutputStream;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.PictureNatives;
+import org.robolectric.shadows.ShadowNativePicture.Picker;
+
+/** Shadow for {@link Picture} that is backed by native code */
+@Implements(value = Picture.class, minSdk = O, shadowPicker = Picker.class, isInAndroidSdk = false)
+public class ShadowNativePicture {
+
+ @Implementation
+ protected static long nativeConstructor(long nativeSrcOr0) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return PictureNatives.nativeConstructor(nativeSrcOr0);
+ }
+
+ @Implementation
+ protected static long nativeCreateFromStream(InputStream stream, byte[] storage) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return PictureNatives.nativeCreateFromStream(stream, storage);
+ }
+
+ @Implementation
+ protected static int nativeGetWidth(long nativePicture) {
+ return PictureNatives.nativeGetWidth(nativePicture);
+ }
+
+ @Implementation
+ protected static int nativeGetHeight(long nativePicture) {
+ return PictureNatives.nativeGetHeight(nativePicture);
+ }
+
+ @Implementation
+ protected static long nativeBeginRecording(long nativeCanvas, int w, int h) {
+ return PictureNatives.nativeBeginRecording(nativeCanvas, w, h);
+ }
+
+ @Implementation
+ protected static void nativeEndRecording(long nativeCanvas) {
+ PictureNatives.nativeEndRecording(nativeCanvas);
+ }
+
+ @Implementation
+ protected static void nativeDraw(long nativeCanvas, long nativePicture) {
+ PictureNatives.nativeDraw(nativeCanvas, nativePicture);
+ }
+
+ @Implementation
+ protected static boolean nativeWriteToStream(
+ long nativePicture, OutputStream stream, byte[] storage) {
+ return PictureNatives.nativeWriteToStream(nativePicture, stream, storage);
+ }
+
+ @Implementation
+ protected static void nativeDestructor(long nativePicture) {
+ PictureNatives.nativeDestructor(nativePicture);
+ }
+
+ /** Shadow picker for {@link Picture}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(ShadowPicture.class, ShadowNativePicture.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePorterDuffColorFilter.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePorterDuffColorFilter.java
new file mode 100644
index 000000000..837ab51d3
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePorterDuffColorFilter.java
@@ -0,0 +1,39 @@
+package org.robolectric.shadows;
+
+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 android.graphics.PorterDuffColorFilter;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.PorterDuffColorFilterNatives;
+import org.robolectric.shadows.ShadowNativePorterDuffColorFilter.Picker;
+
+/** Shadow for {@link PorterDuffColorFilter} that is backed by native code */
+@Implements(
+ value = PorterDuffColorFilter.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ isInAndroidSdk = false)
+public class ShadowNativePorterDuffColorFilter extends ShadowPorterDuffColorFilter {
+
+ @Implementation(minSdk = Q)
+ protected static long native_CreateBlendModeFilter(int srcColor, int blendmode) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return PorterDuffColorFilterNatives.native_CreateBlendModeFilter(srcColor, blendmode);
+ }
+
+ @Implementation(minSdk = O, maxSdk = P)
+ protected static long native_CreatePorterDuffFilter(int srcColor, int porterDuffMode) {
+ return native_CreateBlendModeFilter(srcColor, porterDuffMode);
+ }
+
+ /** Shadow picker for {@link PorterDuffColorFilter}. */
+ public static final class Picker extends GraphicsShadowPicker<ShadowPorterDuffColorFilter> {
+ public Picker() {
+ super(ShadowPorterDuffColorFilter.class, ShadowNativePorterDuffColorFilter.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
new file mode 100644
index 000000000..d9a8f7d5d
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativePropertyValuesHolder.java
@@ -0,0 +1,85 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+
+import android.animation.PropertyValuesHolder;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.PropertyValuesHolderNatives;
+import org.robolectric.shadows.ShadowNativePropertyValuesHolder.Picker;
+
+/** Shadow for {@link PropertyValuesHolder} that is backed by native code */
+@Implements(value = PropertyValuesHolder.class, minSdk = O, shadowPicker = Picker.class)
+public class ShadowNativePropertyValuesHolder {
+
+ @Implementation
+ protected static long nGetIntMethod(Class<?> targetClass, String methodName) {
+ return PropertyValuesHolderNatives.nGetIntMethod(targetClass, methodName);
+ }
+
+ @Implementation
+ protected static long nGetFloatMethod(Class<?> targetClass, String methodName) {
+ return PropertyValuesHolderNatives.nGetFloatMethod(targetClass, methodName);
+ }
+
+ @Implementation
+ protected static long nGetMultipleIntMethod(
+ Class<?> targetClass, String methodName, int numParams) {
+ return PropertyValuesHolderNatives.nGetMultipleIntMethod(targetClass, methodName, numParams);
+ }
+
+ @Implementation
+ protected static long nGetMultipleFloatMethod(
+ Class<?> targetClass, String methodName, int numParams) {
+ return PropertyValuesHolderNatives.nGetMultipleFloatMethod(targetClass, methodName, numParams);
+ }
+
+ @Implementation
+ protected static void nCallIntMethod(Object target, long methodID, int arg) {
+ PropertyValuesHolderNatives.nCallIntMethod(target, methodID, arg);
+ }
+
+ @Implementation
+ protected static void nCallFloatMethod(Object target, long methodID, float arg) {
+ PropertyValuesHolderNatives.nCallFloatMethod(target, methodID, arg);
+ }
+
+ @Implementation
+ protected static void nCallTwoIntMethod(Object target, long methodID, int arg1, int arg2) {
+ PropertyValuesHolderNatives.nCallTwoIntMethod(target, methodID, arg1, arg2);
+ }
+
+ @Implementation
+ 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
+ protected static void nCallMultipleIntMethod(Object target, long methodID, int[] args) {
+ PropertyValuesHolderNatives.nCallMultipleIntMethod(target, methodID, args);
+ }
+
+ @Implementation
+ protected static void nCallTwoFloatMethod(Object target, long methodID, float arg1, float arg2) {
+ PropertyValuesHolderNatives.nCallTwoFloatMethod(target, methodID, arg1, arg2);
+ }
+
+ @Implementation
+ 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
+ protected static void nCallMultipleFloatMethod(Object target, long methodID, float[] args) {
+ PropertyValuesHolderNatives.nCallMultipleFloatMethod(target, methodID, args);
+ }
+
+ /** Shadow picker for {@link PropertyValuesHolder}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(null, ShadowNativePropertyValuesHolder.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRadialGradient.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRadialGradient.java
new file mode 100644
index 000000000..07d2a724c
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRadialGradient.java
@@ -0,0 +1,83 @@
+package org.robolectric.shadows;
+
+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.S;
+
+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;
+
+/** Shadow for {@link RadialGradient} that is backed by native code */
+@Implements(value = RadialGradient.class, minSdk = O, shadowPicker = Picker.class)
+public class ShadowNativeRadialGradient {
+
+ @Implementation(minSdk = S)
+ protected static long nativeCreate(
+ long matrix,
+ float startX,
+ float startY,
+ float startRadius,
+ float endX,
+ float endY,
+ float endRadius,
+ @ColorLong long[] colors,
+ float[] positions,
+ int tileMode,
+ long colorSpaceHandle) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return RadialGradientNatives.nativeCreate(
+ matrix,
+ startX,
+ startY,
+ startRadius,
+ endX,
+ endY,
+ endRadius,
+ colors,
+ positions,
+ tileMode,
+ colorSpaceHandle);
+ }
+
+ @Implementation(minSdk = Q, maxSdk = R)
+ protected static long nativeCreate(
+ long matrix,
+ float x,
+ float y,
+ float radius,
+ @ColorLong long[] colors,
+ float[] positions,
+ int tileMode,
+ long colorSpaceHandle) {
+ return nativeCreate(
+ matrix, x, y, 0, x, y, radius, colors, positions, tileMode, colorSpaceHandle);
+ }
+
+ @Implementation(minSdk = O, maxSdk = P)
+ protected static long nativeCreate1(
+ long matrix, float x, float y, float radius, int[] colors, float[] positions, int tileMode) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return RadialGradientNatives.nativeCreate1(matrix, x, y, radius, colors, positions, tileMode);
+ }
+
+ @Implementation(minSdk = O, maxSdk = P)
+ protected static long nativeCreate2(
+ long matrix, float x, float y, float radius, int color0, int color1, int tileMode) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return RadialGradientNatives.nativeCreate2(matrix, x, y, radius, color0, color1, tileMode);
+ }
+
+ /** Shadow picker for {@link RadialGradient}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(null, ShadowNativeRadialGradient.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRecordingCanvas.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRecordingCanvas.java
new file mode 100644
index 000000000..537419c86
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRecordingCanvas.java
@@ -0,0 +1,112 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.Q;
+import static android.os.Build.VERSION_CODES.S;
+
+import android.graphics.RecordingCanvas;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.RecordingCanvasNatives;
+import org.robolectric.shadows.ShadowNativeRecordingCanvas.Picker;
+
+/** Shadow for {@link RecordingCanvas} that is backed by native code */
+@Implements(value = RecordingCanvas.class, minSdk = Q, shadowPicker = Picker.class)
+public class ShadowNativeRecordingCanvas extends ShadowNativeBaseRecordingCanvas {
+
+ @Implementation
+ protected static long nCreateDisplayListCanvas(long node, int width, int height) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return RecordingCanvasNatives.nCreateDisplayListCanvas(node, width, height);
+ }
+
+ @Implementation
+ protected static void nResetDisplayListCanvas(long canvas, long node, int width, int height) {
+ RecordingCanvasNatives.nResetDisplayListCanvas(canvas, node, width, height);
+ }
+
+ @Implementation
+ protected static int nGetMaximumTextureWidth() {
+ return RecordingCanvasNatives.nGetMaximumTextureWidth();
+ }
+
+ @Implementation
+ protected static int nGetMaximumTextureHeight() {
+ return RecordingCanvasNatives.nGetMaximumTextureHeight();
+ }
+
+ @Implementation(minSdk = S)
+ protected static void nEnableZ(long renderer, boolean enableZ) {
+ RecordingCanvasNatives.nEnableZ(renderer, enableZ);
+ }
+
+ @Implementation(minSdk = S)
+ protected static void nFinishRecording(long renderer, long renderNode) {
+ RecordingCanvasNatives.nFinishRecording(renderer, renderNode);
+ }
+
+ @Implementation
+ protected static void nDrawRenderNode(long renderer, long renderNode) {
+ RecordingCanvasNatives.nDrawRenderNode(renderer, renderNode);
+ }
+
+ @Implementation
+ protected static void nDrawTextureLayer(long renderer, long layer) {
+ RecordingCanvasNatives.nDrawTextureLayer(renderer, layer);
+ }
+
+ @Implementation
+ protected static void nDrawCircle(
+ long renderer, long propCx, long propCy, long propRadius, long propPaint) {
+ RecordingCanvasNatives.nDrawCircle(renderer, propCx, propCy, propRadius, propPaint);
+ }
+
+ @Implementation(minSdk = S)
+ protected static void nDrawRipple(
+ long renderer,
+ long propCx,
+ long propCy,
+ long propRadius,
+ long propPaint,
+ long propProgress,
+ long turbulencePhase,
+ int color,
+ long runtimeEffect) {
+ RecordingCanvasNatives.nDrawRipple(
+ renderer,
+ propCx,
+ propCy,
+ propRadius,
+ propPaint,
+ propProgress,
+ turbulencePhase,
+ color,
+ runtimeEffect);
+ }
+
+ @Implementation
+ protected static void nDrawRoundRect(
+ long renderer,
+ long propLeft,
+ long propTop,
+ long propRight,
+ long propBottom,
+ long propRx,
+ long propRy,
+ long propPaint) {
+ RecordingCanvasNatives.nDrawRoundRect(
+ renderer, propLeft, propTop, propRight, propBottom, propRx, propRy, propPaint);
+ }
+
+ @Implementation
+ protected static void nDrawWebViewFunctor(long canvas, int functor) {
+ RecordingCanvasNatives.nDrawWebViewFunctor(canvas, functor);
+ }
+
+ /** Shadow picker for {@link RecordingCanvas}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(ShadowRecordingCanvas.class, ShadowNativeRecordingCanvas.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRegion.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRegion.java
new file mode 100644
index 000000000..6c855c48b
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRegion.java
@@ -0,0 +1,184 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+import static org.robolectric.shadow.api.Shadow.invokeConstructor;
+import static org.robolectric.util.reflector.Reflector.reflector;
+
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.Parcel;
+import com.google.errorprone.annotations.DoNotCall;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.RealObject;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.RegionNatives;
+import org.robolectric.shadows.ShadowNativeRegion.Picker;
+import org.robolectric.util.ReflectionHelpers.ClassParameter;
+import org.robolectric.util.reflector.Accessor;
+import org.robolectric.util.reflector.ForType;
+
+/** Shadow for {@link Region} that is backed by native code */
+@Implements(value = Region.class, minSdk = O, shadowPicker = Picker.class, isInAndroidSdk = false)
+public class ShadowNativeRegion {
+
+ RegionNatives regionNatives = new RegionNatives();
+ @RealObject Region realRegion;
+
+ @Implementation(minSdk = O)
+ protected void __constructor__(long ni) {
+ invokeConstructor(Region.class, realRegion, ClassParameter.from(long.class, ni));
+ regionNatives.mNativeRegion = ni;
+ }
+
+ @Implementation(minSdk = O)
+ protected void __constructor__(int left, int top, int right, int bottom) {
+ invokeConstructor(
+ Region.class,
+ realRegion,
+ ClassParameter.from(int.class, left),
+ ClassParameter.from(int.class, top),
+ ClassParameter.from(int.class, right),
+ ClassParameter.from(int.class, bottom));
+ regionNatives.mNativeRegion = reflector(RegionReflector.class, realRegion).getNativeRegion();
+ }
+
+ @Implementation(minSdk = O)
+ protected void __constructor__(Rect rect) {
+ invokeConstructor(Region.class, realRegion, ClassParameter.from(Rect.class, rect));
+ regionNatives.mNativeRegion = reflector(RegionReflector.class, realRegion).getNativeRegion();
+ }
+
+ @Implementation(minSdk = O)
+ protected static boolean nativeEquals(long nativeR1, long nativeR2) {
+ return RegionNatives.nativeEquals(nativeR1, nativeR2);
+ }
+
+ @Implementation(minSdk = O)
+ protected static long nativeConstructor() {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return RegionNatives.nativeConstructor();
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nativeDestructor(long nativeRegion) {
+ RegionNatives.nativeDestructor(nativeRegion);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nativeSetRegion(long nativeDst, long nativeSrc) {
+ RegionNatives.nativeSetRegion(nativeDst, nativeSrc);
+ }
+
+ @Implementation(minSdk = O)
+ 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)
+ protected static boolean nativeSetPath(long nativeDst, long nativePath, long nativeClip) {
+ return RegionNatives.nativeSetPath(nativeDst, nativePath, nativeClip);
+ }
+
+ @Implementation(minSdk = O)
+ protected static boolean nativeGetBounds(long nativeRegion, Rect rect) {
+ return RegionNatives.nativeGetBounds(nativeRegion, rect);
+ }
+
+ @Implementation(minSdk = O)
+ protected static boolean nativeGetBoundaryPath(long nativeRegion, long nativePath) {
+ return RegionNatives.nativeGetBoundaryPath(nativeRegion, nativePath);
+ }
+
+ @Implementation(minSdk = O)
+ 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)
+ protected static boolean nativeOp(long nativeDst, Rect rect, long nativeRegion, int op) {
+ return RegionNatives.nativeOp(nativeDst, rect, nativeRegion, op);
+ }
+
+ @Implementation(minSdk = O)
+ 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)
+ protected static long nativeCreateFromParcel(Parcel p) {
+ throw new UnsupportedOperationException();
+ }
+
+ @DoNotCall("Always throws java.lang.UnsupportedOperationException")
+ @Implementation(minSdk = O)
+ protected static boolean nativeWriteToParcel(long nativeRegion, Parcel p) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Implementation(minSdk = O)
+ protected static String nativeToString(long nativeRegion) {
+ return RegionNatives.nativeToString(nativeRegion);
+ }
+
+ @Implementation(minSdk = O)
+ protected boolean isEmpty() {
+ return regionNatives.isEmpty();
+ }
+
+ @Implementation(minSdk = O)
+ protected boolean isRect() {
+ return regionNatives.isRect();
+ }
+
+ @Implementation(minSdk = O)
+ protected boolean isComplex() {
+ return regionNatives.isComplex();
+ }
+
+ @Implementation(minSdk = O)
+ protected boolean contains(int x, int y) {
+ return regionNatives.contains(x, y);
+ }
+
+ @Implementation(minSdk = O)
+ protected boolean quickContains(int left, int top, int right, int bottom) {
+ return regionNatives.quickContains(left, top, right, bottom);
+ }
+
+ @Implementation(minSdk = O)
+ protected boolean quickReject(int left, int top, int right, int bottom) {
+ return regionNatives.quickReject(left, top, right, bottom);
+ }
+
+ @Implementation(minSdk = O)
+ protected boolean quickReject(Region rgn) {
+ return regionNatives.quickReject(rgn);
+ }
+
+ @Implementation(minSdk = O)
+ protected void translate(int dx, int dy, Region dst) {
+ regionNatives.translate(dx, dy, dst);
+ }
+
+ @Implementation(minSdk = O)
+ protected void scale(float scale, Region dst) {
+ regionNatives.scale(scale, dst);
+ }
+
+ @ForType(Region.class)
+ interface RegionReflector {
+ @Accessor("mNativeRegion")
+ long getNativeRegion();
+ }
+
+ /** Shadow picker for {@link Region}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(ShadowRegion.class, ShadowNativeRegion.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRegionIterator.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRegionIterator.java
new file mode 100644
index 000000000..b47bd420c
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRegionIterator.java
@@ -0,0 +1,39 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+
+import android.graphics.Rect;
+import android.graphics.RegionIterator;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.RegionIteratorNatives;
+import org.robolectric.shadows.ShadowNativeRegionIterator.Picker;
+
+/** Shadow for {@link RegionIterator} that is backed by native code */
+@Implements(value = RegionIterator.class, minSdk = O, shadowPicker = Picker.class)
+public class ShadowNativeRegionIterator {
+
+ @Implementation(minSdk = O)
+ protected static long nativeConstructor(long nativeRegion) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return RegionIteratorNatives.nativeConstructor(nativeRegion);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nativeDestructor(long nativeIter) {
+ RegionIteratorNatives.nativeDestructor(nativeIter);
+ }
+
+ @Implementation(minSdk = O)
+ protected static boolean nativeNext(long nativeIter, Rect r) {
+ return RegionIteratorNatives.nativeNext(nativeIter, r);
+ }
+
+ /** Shadow picker for {@link RegionIterator}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(null, ShadowNativeRegionIterator.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderEffect.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderEffect.java
new file mode 100644
index 000000000..0535803db
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderEffect.java
@@ -0,0 +1,77 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+import static android.os.Build.VERSION_CODES.S;
+
+import android.graphics.RenderEffect;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.RenderEffectNatives;
+import org.robolectric.shadows.ShadowNativeRenderEffect.Picker;
+
+/** Shadow for {@link RenderEffect} that is backed by native code */
+@Implements(value = RenderEffect.class, minSdk = O, shadowPicker = Picker.class)
+public class ShadowNativeRenderEffect {
+ static {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ }
+
+ @Implementation(minSdk = S)
+ protected static long nativeCreateOffsetEffect(float offsetX, float offsetY, long nativeInput) {
+ return RenderEffectNatives.nativeCreateOffsetEffect(offsetX, offsetY, nativeInput);
+ }
+
+ @Implementation(minSdk = S)
+ protected static long nativeCreateBlurEffect(
+ float radiusX, float radiusY, long nativeInput, int edgeTreatment) {
+ return RenderEffectNatives.nativeCreateBlurEffect(radiusX, radiusY, nativeInput, edgeTreatment);
+ }
+
+ @Implementation(minSdk = S)
+ protected static long nativeCreateBitmapEffect(
+ long bitmapHandle,
+ float srcLeft,
+ float srcTop,
+ float srcRight,
+ float srcBottom,
+ float dstLeft,
+ float dstTop,
+ float dstRight,
+ float dstBottom) {
+ return RenderEffectNatives.nativeCreateBitmapEffect(
+ bitmapHandle, srcLeft, srcTop, srcRight, srcBottom, dstLeft, dstTop, dstRight, dstBottom);
+ }
+
+ @Implementation(minSdk = S)
+ protected static long nativeCreateColorFilterEffect(long colorFilter, long nativeInput) {
+ return RenderEffectNatives.nativeCreateColorFilterEffect(colorFilter, nativeInput);
+ }
+
+ @Implementation(minSdk = S)
+ protected static long nativeCreateBlendModeEffect(long dst, long src, int blendmode) {
+ return RenderEffectNatives.nativeCreateBlendModeEffect(dst, src, blendmode);
+ }
+
+ @Implementation(minSdk = S)
+ protected static long nativeCreateChainEffect(long outer, long inner) {
+ return RenderEffectNatives.nativeCreateChainEffect(outer, inner);
+ }
+
+ @Implementation(minSdk = S)
+ protected static long nativeCreateShaderEffect(long shader) {
+ return RenderEffectNatives.nativeCreateShaderEffect(shader);
+ }
+
+ @Implementation(minSdk = S)
+ protected static long nativeGetFinalizer() {
+ return RenderEffectNatives.nativeGetFinalizer();
+ }
+
+ /** Shadow picker for {@link RenderEffect}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(null, ShadowNativeRenderEffect.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderNode.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderNode.java
new file mode 100644
index 000000000..4b11355f7
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderNode.java
@@ -0,0 +1,468 @@
+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 android.graphics.RenderNode;
+import android.graphics.RenderNode.PositionUpdateListener;
+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;
+
+/** Shadow for {@link RenderNode} that is backed by native code */
+@Implements(value = RenderNode.class, minSdk = Q, shadowPicker = Picker.class)
+public class ShadowNativeRenderNode {
+ @Implementation
+ protected static long nCreate(String name) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return RenderNodeNatives.nCreate(name);
+ }
+
+ @Implementation
+ protected static long nGetNativeFinalizer() {
+ return RenderNodeNatives.nGetNativeFinalizer();
+ }
+
+ @Implementation
+ protected static void nOutput(long renderNode) {
+ RenderNodeNatives.nOutput(renderNode);
+ }
+
+ @Implementation(minSdk = R)
+ protected static int nGetUsageSize(long renderNode) {
+ return RenderNodeNatives.nGetUsageSize(renderNode);
+ }
+
+ @Implementation(minSdk = R)
+ protected static int nGetAllocatedSize(long renderNode) {
+ return RenderNodeNatives.nGetAllocatedSize(renderNode);
+ }
+
+ @Implementation(maxSdk = S_V2)
+ protected static void nRequestPositionUpdates(long renderNode, PositionUpdateListener callback) {
+ RenderNodeNatives.nRequestPositionUpdates(renderNode, callback);
+ }
+
+ @Implementation
+ protected static void nAddAnimator(long renderNode, long animatorPtr) {
+ RenderNodeNatives.nAddAnimator(renderNode, animatorPtr);
+ }
+
+ @Implementation
+ protected static void nEndAllAnimators(long renderNode) {
+ RenderNodeNatives.nEndAllAnimators(renderNode);
+ }
+
+ @Implementation(minSdk = S)
+ protected static void nDiscardDisplayList(long renderNode) {
+ RenderNodeNatives.nDiscardDisplayList(renderNode);
+ }
+
+ @Implementation
+ protected static boolean nIsValid(long renderNode) {
+ return RenderNodeNatives.nIsValid(renderNode);
+ }
+
+ @Implementation
+ protected static void nGetTransformMatrix(long renderNode, long nativeMatrix) {
+ RenderNodeNatives.nGetTransformMatrix(renderNode, nativeMatrix);
+ }
+
+ @Implementation
+ protected static void nGetInverseTransformMatrix(long renderNode, long nativeMatrix) {
+ RenderNodeNatives.nGetInverseTransformMatrix(renderNode, nativeMatrix);
+ }
+
+ @Implementation
+ protected static boolean nHasIdentityMatrix(long renderNode) {
+ return RenderNodeNatives.nHasIdentityMatrix(renderNode);
+ }
+
+ @Implementation
+ protected static boolean nOffsetTopAndBottom(long renderNode, int offset) {
+ return RenderNodeNatives.nOffsetTopAndBottom(renderNode, offset);
+ }
+
+ @Implementation
+ protected static boolean nOffsetLeftAndRight(long renderNode, int offset) {
+ return RenderNodeNatives.nOffsetLeftAndRight(renderNode, offset);
+ }
+
+ @Implementation
+ protected static boolean nSetLeftTopRightBottom(
+ long renderNode, int left, int top, int right, int bottom) {
+ return RenderNodeNatives.nSetLeftTopRightBottom(renderNode, left, top, right, bottom);
+ }
+
+ @Implementation
+ protected static boolean nSetLeft(long renderNode, int left) {
+ return RenderNodeNatives.nSetLeft(renderNode, left);
+ }
+
+ @Implementation
+ protected static boolean nSetTop(long renderNode, int top) {
+ return RenderNodeNatives.nSetTop(renderNode, top);
+ }
+
+ @Implementation
+ protected static boolean nSetRight(long renderNode, int right) {
+ return RenderNodeNatives.nSetRight(renderNode, right);
+ }
+
+ @Implementation
+ protected static boolean nSetBottom(long renderNode, int bottom) {
+ return RenderNodeNatives.nSetBottom(renderNode, bottom);
+ }
+
+ @Implementation
+ protected static int nGetLeft(long renderNode) {
+ return RenderNodeNatives.nGetLeft(renderNode);
+ }
+
+ @Implementation
+ protected static int nGetTop(long renderNode) {
+ return RenderNodeNatives.nGetTop(renderNode);
+ }
+
+ @Implementation
+ protected static int nGetRight(long renderNode) {
+ return RenderNodeNatives.nGetRight(renderNode);
+ }
+
+ @Implementation
+ protected static int nGetBottom(long renderNode) {
+ return RenderNodeNatives.nGetBottom(renderNode);
+ }
+
+ @Implementation
+ protected static boolean nSetCameraDistance(long renderNode, float distance) {
+ return RenderNodeNatives.nSetCameraDistance(renderNode, distance);
+ }
+
+ @Implementation
+ protected static boolean nSetPivotY(long renderNode, float pivotY) {
+ return RenderNodeNatives.nSetPivotY(renderNode, pivotY);
+ }
+
+ @Implementation
+ protected static boolean nSetPivotX(long renderNode, float pivotX) {
+ return RenderNodeNatives.nSetPivotX(renderNode, pivotX);
+ }
+
+ @Implementation
+ protected static boolean nResetPivot(long renderNode) {
+ return RenderNodeNatives.nResetPivot(renderNode);
+ }
+
+ @Implementation
+ protected static boolean nSetLayerType(long renderNode, int layerType) {
+ return RenderNodeNatives.nSetLayerType(renderNode, layerType);
+ }
+
+ @Implementation
+ protected static int nGetLayerType(long renderNode) {
+ return RenderNodeNatives.nGetLayerType(renderNode);
+ }
+
+ @Implementation
+ protected static boolean nSetLayerPaint(long renderNode, long paint) {
+ return RenderNodeNatives.nSetLayerPaint(renderNode, paint);
+ }
+
+ @Implementation
+ protected static boolean nSetClipToBounds(long renderNode, boolean clipToBounds) {
+ return RenderNodeNatives.nSetClipToBounds(renderNode, clipToBounds);
+ }
+
+ @Implementation
+ protected static boolean nGetClipToBounds(long renderNode) {
+ return RenderNodeNatives.nGetClipToBounds(renderNode);
+ }
+
+ @Implementation
+ protected static boolean nSetClipBounds(
+ long renderNode, int left, int top, int right, int bottom) {
+ return RenderNodeNatives.nSetClipBounds(renderNode, left, top, right, bottom);
+ }
+
+ @Implementation
+ protected static boolean nSetClipBoundsEmpty(long renderNode) {
+ return RenderNodeNatives.nSetClipBoundsEmpty(renderNode);
+ }
+
+ @Implementation
+ protected static boolean nSetProjectBackwards(long renderNode, boolean shouldProject) {
+ return RenderNodeNatives.nSetProjectBackwards(renderNode, shouldProject);
+ }
+
+ @Implementation
+ protected static boolean nSetProjectionReceiver(long renderNode, boolean shouldReceive) {
+ return RenderNodeNatives.nSetProjectionReceiver(renderNode, shouldReceive);
+ }
+
+ @Implementation
+ 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)
+ protected static boolean nSetOutlinePath(long renderNode, long nativePath, float alpha) {
+ return RenderNodeNatives.nSetOutlinePath(renderNode, nativePath, alpha);
+ }
+
+ @Implementation
+ protected static boolean nSetOutlineEmpty(long renderNode) {
+ return RenderNodeNatives.nSetOutlineEmpty(renderNode);
+ }
+
+ @Implementation
+ protected static boolean nSetOutlineNone(long renderNode) {
+ return RenderNodeNatives.nSetOutlineNone(renderNode);
+ }
+
+ @Implementation(minSdk = S)
+ protected static boolean nClearStretch(long renderNode) {
+ return RenderNodeNatives.nClearStretch(renderNode);
+ }
+
+ @Implementation(minSdk = S)
+ protected static boolean nStretch(
+ long renderNode, float vecX, float vecY, float maxStretchX, float maxStretchY) {
+ return RenderNodeNatives.nStretch(renderNode, vecX, vecY, maxStretchX, maxStretchY);
+ }
+
+ @Implementation
+ protected static boolean nHasShadow(long renderNode) {
+ return RenderNodeNatives.nHasShadow(renderNode);
+ }
+
+ @Implementation
+ protected static boolean nSetSpotShadowColor(long renderNode, int color) {
+ return RenderNodeNatives.nSetSpotShadowColor(renderNode, color);
+ }
+
+ @Implementation
+ protected static boolean nSetAmbientShadowColor(long renderNode, int color) {
+ return RenderNodeNatives.nSetAmbientShadowColor(renderNode, color);
+ }
+
+ @Implementation
+ protected static int nGetSpotShadowColor(long renderNode) {
+ return RenderNodeNatives.nGetSpotShadowColor(renderNode);
+ }
+
+ @Implementation
+ protected static int nGetAmbientShadowColor(long renderNode) {
+ return RenderNodeNatives.nGetAmbientShadowColor(renderNode);
+ }
+
+ @Implementation
+ protected static boolean nSetClipToOutline(long renderNode, boolean clipToOutline) {
+ return RenderNodeNatives.nSetClipToOutline(renderNode, clipToOutline);
+ }
+
+ @Implementation
+ protected static boolean nSetRevealClip(
+ long renderNode, boolean shouldClip, float x, float y, float radius) {
+ return RenderNodeNatives.nSetRevealClip(renderNode, shouldClip, x, y, radius);
+ }
+
+ @Implementation
+ protected static boolean nSetAlpha(long renderNode, float alpha) {
+ return RenderNodeNatives.nSetAlpha(renderNode, alpha);
+ }
+
+ @Implementation(minSdk = S)
+ protected static boolean nSetRenderEffect(long renderNode, long renderEffect) {
+ return RenderNodeNatives.nSetRenderEffect(renderNode, renderEffect);
+ }
+
+ @Implementation
+ protected static boolean nSetHasOverlappingRendering(
+ long renderNode, boolean hasOverlappingRendering) {
+ return RenderNodeNatives.nSetHasOverlappingRendering(renderNode, hasOverlappingRendering);
+ }
+
+ @Implementation
+ protected static void nSetUsageHint(long renderNode, int usageHint) {
+ RenderNodeNatives.nSetUsageHint(renderNode, usageHint);
+ }
+
+ @Implementation
+ protected static boolean nSetElevation(long renderNode, float lift) {
+ return RenderNodeNatives.nSetElevation(renderNode, lift);
+ }
+
+ @Implementation
+ protected static boolean nSetTranslationX(long renderNode, float translationX) {
+ return RenderNodeNatives.nSetTranslationX(renderNode, translationX);
+ }
+
+ @Implementation
+ protected static boolean nSetTranslationY(long renderNode, float translationY) {
+ return RenderNodeNatives.nSetTranslationY(renderNode, translationY);
+ }
+
+ @Implementation
+ protected static boolean nSetTranslationZ(long renderNode, float translationZ) {
+ return RenderNodeNatives.nSetTranslationZ(renderNode, translationZ);
+ }
+
+ @Implementation
+ protected static boolean nSetRotation(long renderNode, float rotation) {
+ return RenderNodeNatives.nSetRotation(renderNode, rotation);
+ }
+
+ @Implementation
+ protected static boolean nSetRotationX(long renderNode, float rotationX) {
+ return RenderNodeNatives.nSetRotationX(renderNode, rotationX);
+ }
+
+ @Implementation
+ protected static boolean nSetRotationY(long renderNode, float rotationY) {
+ return RenderNodeNatives.nSetRotationY(renderNode, rotationY);
+ }
+
+ @Implementation
+ protected static boolean nSetScaleX(long renderNode, float scaleX) {
+ return RenderNodeNatives.nSetScaleX(renderNode, scaleX);
+ }
+
+ @Implementation
+ protected static boolean nSetScaleY(long renderNode, float scaleY) {
+ return RenderNodeNatives.nSetScaleY(renderNode, scaleY);
+ }
+
+ @Implementation
+ protected static boolean nSetStaticMatrix(long renderNode, long nativeMatrix) {
+ return RenderNodeNatives.nSetStaticMatrix(renderNode, nativeMatrix);
+ }
+
+ @Implementation
+ protected static boolean nSetAnimationMatrix(long renderNode, long animationMatrix) {
+ return RenderNodeNatives.nSetAnimationMatrix(renderNode, animationMatrix);
+ }
+
+ @Implementation
+ protected static boolean nHasOverlappingRendering(long renderNode) {
+ return RenderNodeNatives.nHasOverlappingRendering(renderNode);
+ }
+
+ @Implementation
+ protected static boolean nGetAnimationMatrix(long renderNode, long animationMatrix) {
+ return RenderNodeNatives.nGetAnimationMatrix(renderNode, animationMatrix);
+ }
+
+ @Implementation
+ protected static boolean nGetClipToOutline(long renderNode) {
+ return RenderNodeNatives.nGetClipToOutline(renderNode);
+ }
+
+ @Implementation
+ protected static float nGetAlpha(long renderNode) {
+ return RenderNodeNatives.nGetAlpha(renderNode);
+ }
+
+ @Implementation
+ protected static float nGetCameraDistance(long renderNode) {
+ return RenderNodeNatives.nGetCameraDistance(renderNode);
+ }
+
+ @Implementation
+ protected static float nGetScaleX(long renderNode) {
+ return RenderNodeNatives.nGetScaleX(renderNode);
+ }
+
+ @Implementation
+ protected static float nGetScaleY(long renderNode) {
+ return RenderNodeNatives.nGetScaleY(renderNode);
+ }
+
+ @Implementation
+ protected static float nGetElevation(long renderNode) {
+ return RenderNodeNatives.nGetElevation(renderNode);
+ }
+
+ @Implementation
+ protected static float nGetTranslationX(long renderNode) {
+ return RenderNodeNatives.nGetTranslationX(renderNode);
+ }
+
+ @Implementation
+ protected static float nGetTranslationY(long renderNode) {
+ return RenderNodeNatives.nGetTranslationY(renderNode);
+ }
+
+ @Implementation
+ protected static float nGetTranslationZ(long renderNode) {
+ return RenderNodeNatives.nGetTranslationZ(renderNode);
+ }
+
+ @Implementation
+ protected static float nGetRotation(long renderNode) {
+ return RenderNodeNatives.nGetRotation(renderNode);
+ }
+
+ @Implementation
+ protected static float nGetRotationX(long renderNode) {
+ return RenderNodeNatives.nGetRotationX(renderNode);
+ }
+
+ @Implementation
+ protected static float nGetRotationY(long renderNode) {
+ return RenderNodeNatives.nGetRotationY(renderNode);
+ }
+
+ @Implementation
+ protected static boolean nIsPivotExplicitlySet(long renderNode) {
+ return RenderNodeNatives.nIsPivotExplicitlySet(renderNode);
+ }
+
+ @Implementation
+ protected static float nGetPivotX(long renderNode) {
+ return RenderNodeNatives.nGetPivotX(renderNode);
+ }
+
+ @Implementation
+ protected static float nGetPivotY(long renderNode) {
+ return RenderNodeNatives.nGetPivotY(renderNode);
+ }
+
+ @Implementation
+ protected static int nGetWidth(long renderNode) {
+ return RenderNodeNatives.nGetWidth(renderNode);
+ }
+
+ @Implementation
+ protected static int nGetHeight(long renderNode) {
+ return RenderNodeNatives.nGetHeight(renderNode);
+ }
+
+ @Implementation
+ protected static boolean nSetAllowForceDark(long renderNode, boolean allowForceDark) {
+ return RenderNodeNatives.nSetAllowForceDark(renderNode, allowForceDark);
+ }
+
+ @Implementation
+ protected static boolean nGetAllowForceDark(long renderNode) {
+ return RenderNodeNatives.nGetAllowForceDark(renderNode);
+ }
+
+ @Implementation
+ protected static long nGetUniqueId(long renderNode) {
+ return RenderNodeNatives.nGetUniqueId(renderNode);
+ }
+
+ /** Shadow picker for {@link RenderNode}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(ShadowRenderNodeQ.class, ShadowNativeRenderNode.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderNodeAnimator.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderNodeAnimator.java
new file mode 100644
index 000000000..5b3c32a1c
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderNodeAnimator.java
@@ -0,0 +1,96 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.R;
+
+import android.graphics.animation.RenderNodeAnimator;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.RenderNodeAnimatorNatives;
+import org.robolectric.shadows.ShadowNativeRenderNodeAnimator.Picker;
+
+/** Shadow for {@link RenderNodeAnimator} that is backed by native code */
+@Implements(
+ value = RenderNodeAnimator.class,
+ minSdk = R,
+ shadowPicker = Picker.class,
+ isInAndroidSdk = false)
+public class ShadowNativeRenderNodeAnimator {
+ @Implementation
+ protected static long nCreateAnimator(int property, float finalValue) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return RenderNodeAnimatorNatives.nCreateAnimator(property, finalValue);
+ }
+
+ @Implementation
+ protected static long nCreateCanvasPropertyFloatAnimator(long canvasProperty, float finalValue) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return RenderNodeAnimatorNatives.nCreateCanvasPropertyFloatAnimator(canvasProperty, finalValue);
+ }
+
+ @Implementation
+ protected static long nCreateCanvasPropertyPaintAnimator(
+ long canvasProperty, int paintField, float finalValue) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return RenderNodeAnimatorNatives.nCreateCanvasPropertyPaintAnimator(
+ canvasProperty, paintField, finalValue);
+ }
+
+ @Implementation
+ protected static long nCreateRevealAnimator(int x, int y, float startRadius, float endRadius) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return RenderNodeAnimatorNatives.nCreateRevealAnimator(x, y, startRadius, endRadius);
+ }
+
+ @Implementation
+ protected static void nSetStartValue(long nativePtr, float startValue) {
+ RenderNodeAnimatorNatives.nSetStartValue(nativePtr, startValue);
+ }
+
+ @Implementation
+ protected static void nSetDuration(long nativePtr, long duration) {
+ RenderNodeAnimatorNatives.nSetDuration(nativePtr, duration);
+ }
+
+ @Implementation
+ protected static long nGetDuration(long nativePtr) {
+ return RenderNodeAnimatorNatives.nGetDuration(nativePtr);
+ }
+
+ @Implementation
+ protected static void nSetStartDelay(long nativePtr, long startDelay) {
+ RenderNodeAnimatorNatives.nSetStartDelay(nativePtr, startDelay);
+ }
+
+ @Implementation
+ protected static void nSetInterpolator(long animPtr, long interpolatorPtr) {
+ RenderNodeAnimatorNatives.nSetInterpolator(animPtr, interpolatorPtr);
+ }
+
+ @Implementation
+ protected static void nSetAllowRunningAsync(long animPtr, boolean mayRunAsync) {
+ RenderNodeAnimatorNatives.nSetAllowRunningAsync(animPtr, mayRunAsync);
+ }
+
+ @Implementation
+ protected static void nSetListener(long animPtr, RenderNodeAnimator listener) {
+ RenderNodeAnimatorNatives.nSetListener(animPtr, listener);
+ }
+
+ @Implementation
+ protected static void nStart(long animPtr) {
+ RenderNodeAnimatorNatives.nStart(animPtr);
+ }
+
+ @Implementation
+ protected static void nEnd(long animPtr) {
+ RenderNodeAnimatorNatives.nEnd(animPtr);
+ }
+
+ /** Shadow picker for {@link RenderNodeAnimator}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(ShadowRenderNodeAnimatorR.class, ShadowNativeRenderNodeAnimator.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderNodeAnimatorQ.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderNodeAnimatorQ.java
new file mode 100644
index 000000000..f83378285
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderNodeAnimatorQ.java
@@ -0,0 +1,100 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+import static android.os.Build.VERSION_CODES.Q;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.RenderNodeAnimatorNatives;
+import org.robolectric.shadows.ShadowNativeRenderNodeAnimatorQ.Picker;
+
+/**
+ * Shadow for {@link android.view.RenderNodeAnimator} for Android Q and below that is backed by
+ * native code
+ */
+@Implements(
+ className = "android.view.RenderNodeAnimator",
+ minSdk = O,
+ maxSdk = Q,
+ looseSignatures = true,
+ shadowPicker = Picker.class)
+public class ShadowNativeRenderNodeAnimatorQ {
+ @Implementation
+ protected static long nCreateAnimator(int property, float finalValue) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return RenderNodeAnimatorNatives.nCreateAnimator(property, finalValue);
+ }
+
+ @Implementation
+ protected static long nCreateCanvasPropertyFloatAnimator(long canvasProperty, float finalValue) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return RenderNodeAnimatorNatives.nCreateCanvasPropertyFloatAnimator(canvasProperty, finalValue);
+ }
+
+ @Implementation
+ protected static long nCreateCanvasPropertyPaintAnimator(
+ long canvasProperty, int paintField, float finalValue) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return RenderNodeAnimatorNatives.nCreateCanvasPropertyPaintAnimator(
+ canvasProperty, paintField, finalValue);
+ }
+
+ @Implementation
+ protected static long nCreateRevealAnimator(int x, int y, float startRadius, float endRadius) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return RenderNodeAnimatorNatives.nCreateRevealAnimator(x, y, startRadius, endRadius);
+ }
+
+ @Implementation
+ protected static void nSetStartValue(long nativePtr, float startValue) {
+ RenderNodeAnimatorNatives.nSetStartValue(nativePtr, startValue);
+ }
+
+ @Implementation
+ protected static void nSetDuration(long nativePtr, long duration) {
+ RenderNodeAnimatorNatives.nSetDuration(nativePtr, duration);
+ }
+
+ @Implementation
+ protected static long nGetDuration(long nativePtr) {
+ return RenderNodeAnimatorNatives.nGetDuration(nativePtr);
+ }
+
+ @Implementation
+ protected static void nSetStartDelay(long nativePtr, long startDelay) {
+ RenderNodeAnimatorNatives.nSetStartDelay(nativePtr, startDelay);
+ }
+
+ @Implementation
+ protected static void nSetInterpolator(long animPtr, long interpolatorPtr) {
+ RenderNodeAnimatorNatives.nSetInterpolator(animPtr, interpolatorPtr);
+ }
+
+ @Implementation
+ protected static void nSetAllowRunningAsync(long animPtr, boolean mayRunAsync) {
+ RenderNodeAnimatorNatives.nSetAllowRunningAsync(animPtr, mayRunAsync);
+ }
+
+ @Implementation
+ protected static void nSetListener(Object animPtr, Object listener) {
+ RenderNodeAnimatorNatives.nSetListener((long) animPtr, listener);
+ }
+
+ @Implementation
+ protected static void nStart(long animPtr) {
+ RenderNodeAnimatorNatives.nStart(animPtr);
+ }
+
+ @Implementation
+ protected static void nEnd(long animPtr) {
+ RenderNodeAnimatorNatives.nEnd(animPtr);
+ }
+
+ /** Shadow picker for {@link android.view.RenderNodeAnimator}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(ShadowRenderNodeAnimator.class, ShadowNativeRenderNodeAnimatorQ.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderNodeOP.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderNodeOP.java
new file mode 100644
index 000000000..f17650ec0
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRenderNodeOP.java
@@ -0,0 +1,465 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+import static android.os.Build.VERSION_CODES.P;
+import static org.robolectric.util.reflector.Reflector.reflector;
+
+import android.graphics.Canvas;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.RealObject;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.RenderNodeNatives;
+import org.robolectric.shadows.ShadowNativeRenderNodeOP.Picker;
+import org.robolectric.util.reflector.Accessor;
+import org.robolectric.util.reflector.ForType;
+
+/** Shadow for {@link android.view.RenderNode} that is backed by native code */
+@Implements(
+ className = "android.view.RenderNode",
+ minSdk = O,
+ maxSdk = P,
+ looseSignatures = true,
+ shadowPicker = Picker.class)
+public class ShadowNativeRenderNodeOP {
+ @RealObject Object realRenderNode;
+
+ @Implementation
+ protected static long nCreate(String name) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return RenderNodeNatives.nCreate(name);
+ }
+
+ @Implementation
+ protected static long nGetNativeFinalizer() {
+ return RenderNodeNatives.nGetNativeFinalizer();
+ }
+
+ @Implementation
+ protected static void nOutput(long renderNode) {
+ RenderNodeNatives.nOutput(renderNode);
+ }
+
+ @Implementation
+ protected static void nAddAnimator(long renderNode, long animatorPtr) {
+ RenderNodeNatives.nAddAnimator(renderNode, animatorPtr);
+ }
+
+ @Implementation
+ protected static void nEndAllAnimators(long renderNode) {
+ RenderNodeNatives.nEndAllAnimators(renderNode);
+ }
+
+ @Implementation
+ protected static boolean nIsValid(long renderNode) {
+ return RenderNodeNatives.nIsValid(renderNode);
+ }
+
+ @Implementation
+ protected static void nGetTransformMatrix(long renderNode, long nativeMatrix) {
+ RenderNodeNatives.nGetTransformMatrix(renderNode, nativeMatrix);
+ }
+
+ @Implementation
+ protected static void nGetInverseTransformMatrix(long renderNode, long nativeMatrix) {
+ RenderNodeNatives.nGetInverseTransformMatrix(renderNode, nativeMatrix);
+ }
+
+ @Implementation
+ protected static boolean nHasIdentityMatrix(long renderNode) {
+ return RenderNodeNatives.nHasIdentityMatrix(renderNode);
+ }
+
+ @Implementation
+ protected static boolean nOffsetTopAndBottom(long renderNode, int offset) {
+ return RenderNodeNatives.nOffsetTopAndBottom(renderNode, offset);
+ }
+
+ @Implementation
+ protected static boolean nOffsetLeftAndRight(long renderNode, int offset) {
+ return RenderNodeNatives.nOffsetLeftAndRight(renderNode, offset);
+ }
+
+ @Implementation
+ protected static boolean nSetLeftTopRightBottom(
+ long renderNode, int left, int top, int right, int bottom) {
+ return RenderNodeNatives.nSetLeftTopRightBottom(renderNode, left, top, right, bottom);
+ }
+
+ @Implementation
+ protected static boolean nSetLeft(long renderNode, int left) {
+ return RenderNodeNatives.nSetLeft(renderNode, left);
+ }
+
+ @Implementation
+ protected static boolean nSetTop(long renderNode, int top) {
+ return RenderNodeNatives.nSetTop(renderNode, top);
+ }
+
+ @Implementation
+ protected static boolean nSetRight(long renderNode, int right) {
+ return RenderNodeNatives.nSetRight(renderNode, right);
+ }
+
+ @Implementation
+ protected static boolean nSetBottom(long renderNode, int bottom) {
+ return RenderNodeNatives.nSetBottom(renderNode, bottom);
+ }
+
+ @Implementation
+ protected static int nGetLeft(long renderNode) {
+ return RenderNodeNatives.nGetLeft(renderNode);
+ }
+
+ @Implementation
+ protected static int nGetTop(long renderNode) {
+ return RenderNodeNatives.nGetTop(renderNode);
+ }
+
+ @Implementation
+ protected static int nGetRight(long renderNode) {
+ return RenderNodeNatives.nGetRight(renderNode);
+ }
+
+ @Implementation
+ protected static int nGetBottom(long renderNode) {
+ return RenderNodeNatives.nGetBottom(renderNode);
+ }
+
+ @Implementation
+ protected static boolean nSetCameraDistance(long renderNode, float distance) {
+ return RenderNodeNatives.nSetCameraDistance(renderNode, distance);
+ }
+
+ @Implementation
+ protected static boolean nSetPivotY(long renderNode, float pivotY) {
+ return RenderNodeNatives.nSetPivotY(renderNode, pivotY);
+ }
+
+ @Implementation
+ protected static boolean nSetPivotX(long renderNode, float pivotX) {
+ return RenderNodeNatives.nSetPivotX(renderNode, pivotX);
+ }
+
+ @Implementation
+ protected static boolean nResetPivot(long renderNode) {
+ return RenderNodeNatives.nResetPivot(renderNode);
+ }
+
+ @Implementation
+ protected static boolean nSetLayerType(long renderNode, int layerType) {
+ return RenderNodeNatives.nSetLayerType(renderNode, layerType);
+ }
+
+ @Implementation
+ protected static int nGetLayerType(long renderNode) {
+ return RenderNodeNatives.nGetLayerType(renderNode);
+ }
+
+ @Implementation
+ protected static boolean nSetLayerPaint(long renderNode, long paint) {
+ return RenderNodeNatives.nSetLayerPaint(renderNode, paint);
+ }
+
+ @Implementation
+ protected static boolean nSetClipToBounds(long renderNode, boolean clipToBounds) {
+ return RenderNodeNatives.nSetClipToBounds(renderNode, clipToBounds);
+ }
+
+ @Implementation
+ protected static boolean nGetClipToBounds(long renderNode) {
+ return RenderNodeNatives.nGetClipToBounds(renderNode);
+ }
+
+ @Implementation
+ protected static boolean nSetClipBounds(
+ long renderNode, int left, int top, int right, int bottom) {
+ return RenderNodeNatives.nSetClipBounds(renderNode, left, top, right, bottom);
+ }
+
+ @Implementation
+ protected static boolean nSetClipBoundsEmpty(long renderNode) {
+ return RenderNodeNatives.nSetClipBoundsEmpty(renderNode);
+ }
+
+ @Implementation
+ protected static boolean nSetProjectBackwards(long renderNode, boolean shouldProject) {
+ return RenderNodeNatives.nSetProjectBackwards(renderNode, shouldProject);
+ }
+
+ @Implementation
+ protected static boolean nSetProjectionReceiver(long renderNode, boolean shouldReceive) {
+ return RenderNodeNatives.nSetProjectionReceiver(renderNode, shouldReceive);
+ }
+
+ @Implementation
+ 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
+ protected static boolean nSetOutlineEmpty(long renderNode) {
+ return RenderNodeNatives.nSetOutlineEmpty(renderNode);
+ }
+
+ @Implementation
+ protected static boolean nSetOutlineNone(long renderNode) {
+ return RenderNodeNatives.nSetOutlineNone(renderNode);
+ }
+
+ @Implementation
+ protected static boolean nHasShadow(long renderNode) {
+ return RenderNodeNatives.nHasShadow(renderNode);
+ }
+
+ @Implementation
+ protected static boolean nSetSpotShadowColor(long renderNode, int color) {
+ return RenderNodeNatives.nSetSpotShadowColor(renderNode, color);
+ }
+
+ @Implementation
+ protected static boolean nSetAmbientShadowColor(long renderNode, int color) {
+ return RenderNodeNatives.nSetAmbientShadowColor(renderNode, color);
+ }
+
+ @Implementation
+ protected static int nGetSpotShadowColor(long renderNode) {
+ return RenderNodeNatives.nGetSpotShadowColor(renderNode);
+ }
+
+ @Implementation
+ protected static int nGetAmbientShadowColor(long renderNode) {
+ return RenderNodeNatives.nGetAmbientShadowColor(renderNode);
+ }
+
+ @Implementation
+ protected static boolean nSetClipToOutline(long renderNode, boolean clipToOutline) {
+ return RenderNodeNatives.nSetClipToOutline(renderNode, clipToOutline);
+ }
+
+ @Implementation
+ protected static boolean nSetRevealClip(
+ long renderNode, boolean shouldClip, float x, float y, float radius) {
+ return RenderNodeNatives.nSetRevealClip(renderNode, shouldClip, x, y, radius);
+ }
+
+ @Implementation
+ protected static boolean nSetAlpha(long renderNode, float alpha) {
+ return RenderNodeNatives.nSetAlpha(renderNode, alpha);
+ }
+
+ @Implementation
+ protected static boolean nSetHasOverlappingRendering(
+ long renderNode, boolean hasOverlappingRendering) {
+ return RenderNodeNatives.nSetHasOverlappingRendering(renderNode, hasOverlappingRendering);
+ }
+
+ @Implementation
+ protected static void nSetUsageHint(long renderNode, int usageHint) {
+ RenderNodeNatives.nSetUsageHint(renderNode, usageHint);
+ }
+
+ @Implementation
+ protected static boolean nSetElevation(long renderNode, float lift) {
+ return RenderNodeNatives.nSetElevation(renderNode, lift);
+ }
+
+ @Implementation
+ protected static boolean nSetTranslationX(long renderNode, float translationX) {
+ return RenderNodeNatives.nSetTranslationX(renderNode, translationX);
+ }
+
+ @Implementation
+ protected static boolean nSetTranslationY(long renderNode, float translationY) {
+ return RenderNodeNatives.nSetTranslationY(renderNode, translationY);
+ }
+
+ @Implementation
+ protected static boolean nSetTranslationZ(long renderNode, float translationZ) {
+ return RenderNodeNatives.nSetTranslationZ(renderNode, translationZ);
+ }
+
+ @Implementation
+ protected static boolean nSetRotation(long renderNode, float rotation) {
+ return RenderNodeNatives.nSetRotation(renderNode, rotation);
+ }
+
+ @Implementation
+ protected static boolean nSetRotationX(long renderNode, float rotationX) {
+ return RenderNodeNatives.nSetRotationX(renderNode, rotationX);
+ }
+
+ @Implementation
+ protected static boolean nSetRotationY(long renderNode, float rotationY) {
+ return RenderNodeNatives.nSetRotationY(renderNode, rotationY);
+ }
+
+ @Implementation
+ protected static boolean nSetScaleX(long renderNode, float scaleX) {
+ return RenderNodeNatives.nSetScaleX(renderNode, scaleX);
+ }
+
+ @Implementation
+ protected static boolean nSetScaleY(long renderNode, float scaleY) {
+ return RenderNodeNatives.nSetScaleY(renderNode, scaleY);
+ }
+
+ @Implementation
+ protected static boolean nSetStaticMatrix(long renderNode, long nativeMatrix) {
+ return RenderNodeNatives.nSetStaticMatrix(renderNode, nativeMatrix);
+ }
+
+ @Implementation
+ protected static boolean nSetAnimationMatrix(long renderNode, long animationMatrix) {
+ return RenderNodeNatives.nSetAnimationMatrix(renderNode, animationMatrix);
+ }
+
+ @Implementation
+ protected static boolean nHasOverlappingRendering(long renderNode) {
+ return RenderNodeNatives.nHasOverlappingRendering(renderNode);
+ }
+
+ @Implementation
+ protected static boolean nGetAnimationMatrix(long renderNode, long animationMatrix) {
+ return RenderNodeNatives.nGetAnimationMatrix(renderNode, animationMatrix);
+ }
+
+ @Implementation
+ protected static boolean nGetClipToOutline(long renderNode) {
+ return RenderNodeNatives.nGetClipToOutline(renderNode);
+ }
+
+ @Implementation
+ protected static float nGetAlpha(long renderNode) {
+ return RenderNodeNatives.nGetAlpha(renderNode);
+ }
+
+ @Implementation
+ protected static float nGetCameraDistance(long renderNode) {
+ return RenderNodeNatives.nGetCameraDistance(renderNode);
+ }
+
+ @Implementation
+ protected static float nGetScaleX(long renderNode) {
+ return RenderNodeNatives.nGetScaleX(renderNode);
+ }
+
+ @Implementation
+ protected static float nGetScaleY(long renderNode) {
+ return RenderNodeNatives.nGetScaleY(renderNode);
+ }
+
+ @Implementation
+ protected static float nGetElevation(long renderNode) {
+ return RenderNodeNatives.nGetElevation(renderNode);
+ }
+
+ @Implementation
+ protected static float nGetTranslationX(long renderNode) {
+ return RenderNodeNatives.nGetTranslationX(renderNode);
+ }
+
+ @Implementation
+ protected static float nGetTranslationY(long renderNode) {
+ return RenderNodeNatives.nGetTranslationY(renderNode);
+ }
+
+ @Implementation
+ protected static float nGetTranslationZ(long renderNode) {
+ return RenderNodeNatives.nGetTranslationZ(renderNode);
+ }
+
+ @Implementation
+ protected static float nGetRotation(long renderNode) {
+ return RenderNodeNatives.nGetRotation(renderNode);
+ }
+
+ @Implementation
+ protected static float nGetRotationX(long renderNode) {
+ return RenderNodeNatives.nGetRotationX(renderNode);
+ }
+
+ @Implementation
+ protected static float nGetRotationY(long renderNode) {
+ return RenderNodeNatives.nGetRotationY(renderNode);
+ }
+
+ @Implementation
+ protected static boolean nIsPivotExplicitlySet(long renderNode) {
+ return RenderNodeNatives.nIsPivotExplicitlySet(renderNode);
+ }
+
+ @Implementation
+ protected static float nGetPivotX(long renderNode) {
+ return RenderNodeNatives.nGetPivotX(renderNode);
+ }
+
+ @Implementation
+ protected static float nGetPivotY(long renderNode) {
+ return RenderNodeNatives.nGetPivotY(renderNode);
+ }
+
+ @Implementation
+ protected static int nGetWidth(long renderNode) {
+ return RenderNodeNatives.nGetWidth(renderNode);
+ }
+
+ @Implementation
+ protected static int nGetHeight(long renderNode) {
+ return RenderNodeNatives.nGetHeight(renderNode);
+ }
+
+ @Implementation
+ protected static boolean nSetAllowForceDark(long renderNode, boolean allowForceDark) {
+ return RenderNodeNatives.nSetAllowForceDark(renderNode, allowForceDark);
+ }
+
+ @Implementation
+ protected static boolean nGetAllowForceDark(long renderNode) {
+ return RenderNodeNatives.nGetAllowForceDark(renderNode);
+ }
+
+ @Implementation
+ protected static long nGetUniqueId(long renderNode) {
+ return RenderNodeNatives.nGetUniqueId(renderNode);
+ }
+
+ // In APIs Q+, RenderNodes are used to maintain DisplayLists instead of through DisplayListCanvas.
+ // In APIs O-P, this function would call the version of nFinishRecording that didn't use a
+ // RenderNode at all and instead returned a DisplayList that would need to be moved.
+ // To bridge the two implementations, the end(..) function here uses the API Q+ version so that
+ // the RenderNode is marked as valid when isValid() is called.
+ @Implementation
+ protected void end(Object canvas) {
+ long nativeRenderNode =
+ reflector(RenderNodeOpReflector.class, realRenderNode).getNativeRenderNode();
+ long nativeCanvasWrapper = reflector(CanvasReflector.class, canvas).getNativeCanvasWrapper();
+ ShadowNativeRecordingCanvas.nFinishRecording(nativeCanvasWrapper, nativeRenderNode);
+ reflector(DisplayListCanvasReflector.class, canvas).recycle();
+ }
+
+ @ForType(className = "android.view.RenderNode")
+ interface RenderNodeOpReflector {
+ @Accessor("mNativeRenderNode")
+ long getNativeRenderNode();
+ }
+
+ @ForType(className = "android.view.DisplayListCanvas")
+ interface DisplayListCanvasReflector {
+ void recycle();
+ }
+
+ @ForType(Canvas.class)
+ interface CanvasReflector {
+ long getNativeCanvasWrapper();
+ }
+
+ /** Shadow picker for {@link android.view.RenderNode}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(ShadowRenderNode.class, ShadowNativeRenderNodeOP.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRuntimeShader.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRuntimeShader.java
new file mode 100644
index 000000000..9793d1d68
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeRuntimeShader.java
@@ -0,0 +1,180 @@
+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.S_V2;
+import static android.os.Build.VERSION_CODES.TIRAMISU;
+
+import android.graphics.RuntimeShader;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.RealObject;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.RuntimeShaderNatives;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowNativeRuntimeShader.Picker;
+import org.robolectric.util.ReflectionHelpers.ClassParameter;
+
+/** Shadow for {@link RuntimeShader} that is backed by native code */
+@Implements(value = RuntimeShader.class, minSdk = O, shadowPicker = Picker.class)
+public class ShadowNativeRuntimeShader {
+
+ @RealObject RuntimeShader runtimeShader;
+
+ private static final String RIPPLE_SHADER_UNIFORMS_31 =
+ "uniform vec2 in_origin;\n"
+ + "uniform vec2 in_touch;\n"
+ + "uniform float in_progress;\n"
+ + "uniform float in_maxRadius;\n"
+ + "uniform vec2 in_resolutionScale;\n"
+ + "uniform vec2 in_noiseScale;\n"
+ + "uniform float in_hasMask;\n"
+ + "uniform float in_noisePhase;\n"
+ + "uniform float in_turbulencePhase;\n"
+ + "uniform vec2 in_tCircle1;\n"
+ + "uniform vec2 in_tCircle2;\n"
+ + "uniform vec2 in_tCircle3;\n"
+ + "uniform vec2 in_tRotation1;\n"
+ + "uniform vec2 in_tRotation2;\n"
+ + "uniform vec2 in_tRotation3;\n"
+ + "uniform vec4 in_color;\n"
+ + "uniform vec4 in_sparkleColor;\n"
+ + "uniform shader in_shader;\n";
+ private static final String RIPPLE_SHADER_LIB_31 =
+ "float triangleNoise(vec2 n) {\n"
+ + " n = fract(n * vec2(5.3987, 5.4421));\n"
+ + " n += dot(n.yx, n.xy + vec2(21.5351, 14.3137));\n"
+ + " float xy = n.x * n.y;\n"
+ + " return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0;\n"
+ + "}"
+ + "const float PI = 3.1415926535897932384626;\n"
+ + "\n"
+ + "float threshold(float v, float l, float h) {\n"
+ + " return step(l, v) * (1.0 - step(h, v));\n"
+ + "}\n"
+ + "float sparkles(vec2 uv, float t) {\n"
+ + " float n = triangleNoise(uv);\n"
+ + " float s = 0.0;\n"
+ + " for (float i = 0; i < 4; i += 1) {\n"
+ + " float l = i * 0.1;\n"
+ + " float h = l + 0.05;\n"
+ + " float o = sin(PI * (t + 0.35 * i));\n"
+ + " s += threshold(n + o, l, h);\n"
+ + " }\n"
+ + " return saturate(s) * in_sparkleColor.a;\n"
+ + "}\n"
+ + "float softCircle(vec2 uv, vec2 xy, float radius, float blur) {\n"
+ + " float blurHalf = blur * 0.5;\n"
+ + " float d = distance(uv, xy);\n"
+ + " return 1. - smoothstep(1. - blurHalf, 1. + blurHalf, d / radius);\n"
+ + "}\n"
+ + "float softRing(vec2 uv, vec2 xy, float radius, float progress, float blur) {\n"
+ + " float thickness = 0.05 * radius;\n"
+ + " float currentRadius = radius * progress;\n"
+ + " float circle_outer = softCircle(uv, xy, currentRadius + thickness, blur);\n"
+ + " float circle_inner = softCircle(uv, xy, max(currentRadius - thickness, 0.), "
+ + " blur);\n"
+ + " return saturate(circle_outer - circle_inner);\n"
+ + "}\n"
+ + "float subProgress(float start, float end, float progress) {\n"
+ + " float sub = clamp(progress, start, end);\n"
+ + " return (sub - start) / (end - start); \n"
+ + "}\n"
+ + "mat2 rotate2d(vec2 rad){\n"
+ + " return mat2(rad.x, -rad.y, rad.y, rad.x);\n"
+ + "}\n"
+ + "float circle_grid(vec2 resolution, vec2 coord, float time, vec2 center,\n"
+ + " vec2 rotation, float cell_diameter) {\n"
+ + " coord = rotate2d(rotation) * (center - coord) + center;\n"
+ + " coord = mod(coord, cell_diameter) / resolution;\n"
+ + " float normal_radius = cell_diameter / resolution.y * 0.5;\n"
+ + " float radius = 0.65 * normal_radius;\n"
+ + " return softCircle(coord, vec2(normal_radius), radius, radius * 50.0);\n"
+ + "}\n"
+ + "float turbulence(vec2 uv, float t) {\n"
+ + " const vec2 scale = vec2(0.8);\n"
+ + " uv = uv * scale;\n"
+ + " float g1 = circle_grid(scale, uv, t, in_tCircle1, in_tRotation1, 0.17);\n"
+ + " float g2 = circle_grid(scale, uv, t, in_tCircle2, in_tRotation2, 0.2);\n"
+ + " float g3 = circle_grid(scale, uv, t, in_tCircle3, in_tRotation3, 0.275);\n"
+ + " float v = (g1 * g1 + g2 - g3) * 0.5;\n"
+ + " return saturate(0.45 + 0.8 * v);\n"
+ + "}\n";
+ private static final String RIPPLE_SHADER_MAIN_31 =
+ "vec4 main(vec2 p) {\n"
+ + " float fadeIn = subProgress(0., 0.13, in_progress);\n"
+ + " float scaleIn = subProgress(0., 1.0, in_progress);\n"
+ + " float fadeOutNoise = subProgress(0.4, 0.5, in_progress);\n"
+ + " float fadeOutRipple = subProgress(0.4, 1., in_progress);\n"
+ + " vec2 center = mix(in_touch, in_origin, saturate(in_progress * 2.0));\n"
+ + " float ring = softRing(p, center, in_maxRadius, scaleIn, 1.);\n"
+ + " float alpha = min(fadeIn, 1. - fadeOutNoise);\n"
+ + " vec2 uv = p * in_resolutionScale;\n"
+ + " vec2 densityUv = uv - mod(uv, in_noiseScale);\n"
+ + " float turbulence = turbulence(uv, in_turbulencePhase);\n"
+ + " float sparkleAlpha = sparkles(densityUv, in_noisePhase) * ring * alpha "
+ + "* turbulence;\n"
+ + " float fade = min(fadeIn, 1. - fadeOutRipple);\n"
+ + " float waveAlpha = softCircle(p, center, in_maxRadius * scaleIn, 1.) * fade "
+ + "* in_color.a;\n"
+ + " vec4 waveColor = vec4(in_color.rgb * waveAlpha, waveAlpha);\n"
+ + " vec4 sparkleColor = vec4(in_sparkleColor.rgb * in_sparkleColor.a, "
+ + "in_sparkleColor.a);\n"
+ + " float mask = in_hasMask == 1. ? sample(in_shader, p).a > 0. ? 1. : 0. : 1.;\n"
+ + " return mix(waveColor, sparkleColor, sparkleAlpha) * mask;\n"
+ + "}";
+ private static final String RIPPLE_SHADER_31 =
+ RIPPLE_SHADER_UNIFORMS_31 + RIPPLE_SHADER_LIB_31 + RIPPLE_SHADER_MAIN_31;
+
+ @Implementation(minSdk = TIRAMISU)
+ 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
+ // RippleShader SKSL from T in S.
+ // TODO(hoisie): Delete this shadow method when RNG is updated to use native libraries from T+.
+ try {
+ if (Class.forName("android.graphics.drawable.RippleShader").isInstance(runtimeShader)) {
+ sksl = RIPPLE_SHADER_31;
+ }
+ } catch (ClassNotFoundException e) {
+ throw new AssertionError(e);
+ }
+ Shadow.invokeConstructor(
+ RuntimeShader.class, runtimeShader, ClassParameter.from(String.class, sksl));
+ }
+
+ @Implementation(minSdk = R)
+ protected static long nativeGetFinalizer() {
+ return RuntimeShaderNatives.nativeGetFinalizer();
+ }
+
+ @Implementation(minSdk = S)
+ protected static long nativeCreateBuilder(String sksl) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return RuntimeShaderNatives.nativeCreateBuilder(sksl);
+ }
+
+ @Implementation(minSdk = S, maxSdk = S_V2)
+ protected static long nativeCreateShader(long shaderBuilder, long matrix, boolean isOpaque) {
+ return RuntimeShaderNatives.nativeCreateShader(shaderBuilder, matrix, isOpaque);
+ }
+
+ @Implementation(minSdk = S, maxSdk = S_V2)
+ protected static void nativeUpdateUniforms(
+ long shaderBuilder, String uniformName, float[] uniforms) {
+ RuntimeShaderNatives.nativeUpdateUniforms(shaderBuilder, uniformName, uniforms);
+ }
+
+ @Implementation(minSdk = S)
+ protected static void nativeUpdateShader(long shaderBuilder, String shaderName, long shader) {
+ RuntimeShaderNatives.nativeUpdateShader(shaderBuilder, shaderName, shader);
+ }
+
+ /** Shadow picker for {@link RuntimeShader}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(null, ShadowNativeRuntimeShader.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeShader.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeShader.java
new file mode 100644
index 000000000..1a216f8ab
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeShader.java
@@ -0,0 +1,28 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+
+import android.graphics.Shader;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.ShaderNatives;
+import org.robolectric.shadows.ShadowNativeShader.Picker;
+
+/** Shadow for {@link Shader} that is backed by native code */
+@Implements(value = Shader.class, minSdk = O, shadowPicker = Picker.class)
+public class ShadowNativeShader {
+
+ @Implementation(minSdk = O)
+ protected static long nativeGetFinalizer() {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return ShaderNatives.nativeGetFinalizer();
+ }
+
+ /** Shadow picker for {@link Shader}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(null, ShadowNativeShader.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeStaticLayout.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeStaticLayout.java
new file mode 100644
index 000000000..04504d30e
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeStaticLayout.java
@@ -0,0 +1,330 @@
+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 org.robolectric.util.reflector.Reflector.reflector;
+
+import android.graphics.Paint;
+import android.text.StaticLayout;
+import android.text.TextPaint;
+import java.nio.ByteBuffer;
+import java.util.Locale;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.LineBreakerNatives;
+import org.robolectric.nativeruntime.MeasuredTextBuilderNatives;
+import org.robolectric.nativeruntime.MeasuredTextNatives;
+import org.robolectric.nativeruntime.NativeAllocationRegistryNatives;
+import org.robolectric.res.android.NativeObjRegistry;
+import org.robolectric.shadows.ShadowNativeStaticLayout.Picker;
+import org.robolectric.util.reflector.Accessor;
+import org.robolectric.util.reflector.ForType;
+
+/**
+ * Shadow for {@link StaticLayout} that is backed by native code for Android O-P. In Android Q, the
+ * native methods relate to text layout were heavily refactored and moved to MeasuredText and
+ * LineBreaker.
+ */
+@Implements(
+ value = StaticLayout.class,
+ minSdk = O,
+ maxSdk = P,
+ looseSignatures = true,
+ shadowPicker = Picker.class)
+public class ShadowNativeStaticLayout {
+
+ // Only used for the O/O_MR1 adapter logic.
+ static final NativeObjRegistry<NativeStaticLayoutSetup> nativeObjectRegistry =
+ new NativeObjRegistry<>(NativeStaticLayoutSetup.class);
+
+ @Implementation(minSdk = P, maxSdk = P)
+ protected static long nInit(
+ int breakStrategy,
+ int hyphenationFrequency,
+ boolean isJustified,
+ int[] indents,
+ int[] leftPaddings,
+ int[] rightPaddings) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return LineBreakerNatives.nInit(breakStrategy, hyphenationFrequency, isJustified, indents);
+ }
+
+ @Implementation(minSdk = P, maxSdk = P)
+ protected static void nFinish(long nativePtr) {
+ LineBreakerNatives.nFinishP(nativePtr);
+ }
+
+ /**
+ * This has to use looseSignatures due to {@code recycle} param with non-public type {@code
+ * android.text.StaticLayout$LineBreaks}.
+ */
+ @Implementation(minSdk = P, maxSdk = P)
+ protected static int nComputeLineBreaks(
+ Object nativePtr,
+ Object text,
+ Object measuredTextPtr,
+ Object length,
+ Object firstWidth,
+ Object firstWidthLineCount,
+ Object restWidth,
+ Object variableTabStopsObject,
+ Object defaultTabStop,
+ Object indentsOffset,
+ Object recycle,
+ Object recycleLength,
+ Object recycleBreaks,
+ Object recycleWidths,
+ Object recycleAscents,
+ Object recycleDescents,
+ Object recycleFlags,
+ Object charWidths) {
+
+ return LineBreakerNatives.nComputeLineBreaksP(
+ (long) nativePtr,
+ (char[]) text,
+ (long) measuredTextPtr,
+ (int) length,
+ (float) firstWidth,
+ (int) firstWidthLineCount,
+ (float) restWidth,
+ intsToFloat((int[]) variableTabStopsObject),
+ ((Number) defaultTabStop).floatValue(),
+ (int) indentsOffset,
+ recycle,
+ (int) recycleLength,
+ (int[]) recycleBreaks,
+ (float[]) recycleWidths,
+ (float[]) recycleAscents,
+ (float[]) recycleDescents,
+ (int[]) recycleFlags,
+ (float[]) charWidths);
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static long nNewBuilder() {
+ return nativeObjectRegistry.register(new NativeStaticLayoutSetup());
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static void nFreeBuilder(long nativePtr) {
+ NativeStaticLayoutSetup setup = nativeObjectRegistry.getNativeObject(nativePtr);
+
+ NativeAllocationRegistryNatives.applyFreeFunction(
+ LineBreakerNatives.nGetReleaseResultFunc(), setup.lineBreakerResultPtr);
+
+ nativeObjectRegistry.unregister(nativePtr);
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static void nFinishBuilder(long nativePtr) {
+ // No-op
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static long nLoadHyphenator(ByteBuffer buf, int offset, int minPrefix, int minSuffix) {
+ // nLoadHyphenator is not supported
+ return 0;
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static void nSetLocale(long nativePtr, String locale, long nativeHyphenator) {
+ NativeStaticLayoutSetup setup = nativeObjectRegistry.getNativeObject(nativePtr);
+ setup.localePaint.setTextLocale(Locale.forLanguageTag(locale));
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static void nSetIndents(long nativePtr, int[] indents) {
+ NativeStaticLayoutSetup setup = nativeObjectRegistry.getNativeObject(nativePtr);
+ setup.indents = indents;
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static void nSetupParagraph(
+ long nativePtr,
+ char[] text,
+ int length,
+ float firstWidth,
+ int firstWidthLineCount,
+ float restWidth,
+ int[] variableTabStops,
+ int defaultTabStop,
+ int breakStrategy,
+ int hyphenationFrequency,
+ boolean isJustified) {
+ NativeStaticLayoutSetup setup = nativeObjectRegistry.getNativeObject(nativePtr);
+ setup.text = text;
+ setup.length = length;
+ setup.firstWidth = firstWidth;
+ setup.firstWidthLineCount = firstWidthLineCount;
+ setup.restWidth = restWidth;
+ setup.variableTabStops = variableTabStops;
+ setup.defaultTabStop = defaultTabStop;
+ setup.breakStrategy = breakStrategy;
+ setup.hyphenationFrequency = hyphenationFrequency;
+ setup.isJustified = isJustified;
+ setup.measuredTextBuilderPtr = MeasuredTextBuilderNatives.nInitBuilder();
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static float nAddStyleRun(
+ long nativePtr, long nativePaint, long nativeTypeface, int start, int end, boolean isRtl) {
+ NativeStaticLayoutSetup setup = nativeObjectRegistry.getNativeObject(nativePtr);
+
+ MeasuredTextBuilderNatives.nAddStyleRun(
+ setup.measuredTextBuilderPtr, nativePaint, start, end, isRtl);
+ return 0f;
+ }
+
+ @Implementation
+ protected static void nAddMeasuredRun(long nativePtr, int start, int end, float[] widths) {
+ NativeStaticLayoutSetup setup = nativeObjectRegistry.getNativeObject(nativePtr);
+ MeasuredTextBuilderNatives.nAddStyleRun(
+ setup.measuredTextBuilderPtr, setup.localePaint.getNativeInstance(), start, end, false);
+ }
+
+ @Implementation
+ protected static void nAddReplacementRun(long nativePtr, int start, int end, float width) {
+ NativeStaticLayoutSetup setup = nativeObjectRegistry.getNativeObject(nativePtr);
+ MeasuredTextBuilderNatives.nAddReplacementRun(
+ setup.measuredTextBuilderPtr, setup.localePaint.getNativeInstance(), start, end, width);
+ }
+
+ @Implementation
+ protected static void nGetWidths(long nativePtr, float[] widths) {
+ // Returns the width of each char in the text.
+ NativeStaticLayoutSetup setup = nativeObjectRegistry.getNativeObject(nativePtr);
+ setup.measuredTextPtr =
+ MeasuredTextBuilderNatives.nBuildMeasuredText(
+ setup.measuredTextBuilderPtr, 0, setup.text, false, false);
+ for (int i = 0; i < setup.text.length; i++) {
+ widths[i] = MeasuredTextNatives.nGetCharWidthAt(setup.measuredTextPtr, i);
+ }
+ MeasuredTextBuilderNatives.nFreeBuilder(setup.measuredTextBuilderPtr);
+ }
+
+ /**
+ * This has to use looseSignatures due to {@code recycle} param with non-public type {@code
+ * android.text.StaticLayout$LineBreaks}.
+ */
+ @Implementation
+ protected static int nComputeLineBreaks(
+ Object /*long*/ nativePtr,
+ Object /*LineBreaks*/ recycle,
+ Object /*int[]*/ recycleBreaksObject,
+ Object /*float[]*/ recycleWidthsObject,
+ Object /*int[]*/ recycleFlagsObject,
+ Object /*int*/ recycleLength) {
+
+ int[] recycleBreaks = (int[]) recycleBreaksObject;
+ float[] recycleWidths = (float[]) recycleWidthsObject;
+ int[] recycleFlags = (int[]) recycleFlagsObject;
+
+ NativeStaticLayoutSetup setup = nativeObjectRegistry.getNativeObject((long) nativePtr);
+
+ long lineBreakerBuilderPtr =
+ LineBreakerNatives.nInit(
+ setup.breakStrategy, setup.hyphenationFrequency, setup.isJustified, setup.indents);
+
+ setup.lineBreakerResultPtr =
+ LineBreakerNatives.nComputeLineBreaks(
+ lineBreakerBuilderPtr,
+ setup.text,
+ setup.measuredTextPtr,
+ setup.length,
+ setup.firstWidth,
+ setup.firstWidthLineCount,
+ setup.restWidth,
+ intsToFloat(setup.variableTabStops),
+ (float) setup.defaultTabStop,
+ 0);
+
+ int lineCount = LineBreakerNatives.nGetLineCount(setup.lineBreakerResultPtr);
+
+ if (lineCount > recycleBreaks.length) {
+ // resize the recycle objects
+ recycleBreaks = new int[lineCount];
+ recycleWidths = new float[lineCount];
+ recycleFlags = new int[lineCount];
+ reflector(LineBreaksReflector.class, recycle).setBreaks(recycleBreaks);
+ reflector(LineBreaksReflector.class, recycle).setWidths(recycleWidths);
+ reflector(LineBreaksReflector.class, recycle).setFlags(recycleFlags);
+ }
+
+ for (int i = 0; i < lineCount; i++) {
+ recycleBreaks[i] = LineBreakerNatives.nGetLineBreakOffset(setup.lineBreakerResultPtr, i);
+ recycleWidths[i] = LineBreakerNatives.nGetLineWidth(setup.lineBreakerResultPtr, i);
+ recycleFlags[i] = LineBreakerNatives.nGetLineFlag(setup.lineBreakerResultPtr, i);
+ }
+
+ // Release the pointers used for the builder, the result pointer is the only relevant pointer
+ // now.
+ NativeAllocationRegistryNatives.applyFreeFunction(
+ LineBreakerNatives.nGetReleaseFunc(), lineBreakerBuilderPtr);
+
+ NativeAllocationRegistryNatives.applyFreeFunction(
+ MeasuredTextNatives.nGetReleaseFunc(), setup.measuredTextPtr);
+
+ return lineCount;
+ }
+
+ static final class NativeStaticLayoutSetup {
+
+ char[] text;
+ int length;
+ float firstWidth;
+ int firstWidthLineCount;
+ float restWidth;
+ int[] variableTabStops;
+ int defaultTabStop;
+ int breakStrategy;
+ int hyphenationFrequency;
+ boolean isJustified;
+ int[] indents;
+ Paint localePaint = new TextPaint(); // TODO(hoisie): use `mPaint` from StaticLayout.Builder
+ long measuredTextBuilderPtr;
+ long measuredTextPtr;
+ long lineBreakerResultPtr;
+ }
+
+ private static float[] intsToFloat(int[] intArray) {
+ if (intArray == null) {
+ return null;
+ }
+ float[] floatArray = new float[intArray.length];
+
+ for (int i = 0; i < floatArray.length; i++) {
+ floatArray[i] = intArray[i];
+ }
+ return floatArray;
+ }
+
+ @ForType(className = "android.text.StaticLayout$LineBreaks")
+ interface LineBreaksReflector {
+ @Accessor("breaks")
+ int[] getBreaks();
+
+ @Accessor("breaks")
+ void setBreaks(int[] breaks);
+
+ @Accessor("widths")
+ float[] getWidths();
+
+ @Accessor("widths")
+ void setWidths(float[] widths);
+
+ @Accessor("flags")
+ int[] getFlags();
+
+ @Accessor("flags")
+ void setFlags(int[] flags);
+ }
+
+ /** Shadow picker for {@link StaticLayout}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(ShadowStaticLayout.class, ShadowNativeStaticLayout.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSumPathEffect.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSumPathEffect.java
new file mode 100644
index 000000000..2d436ae23
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSumPathEffect.java
@@ -0,0 +1,28 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+
+import android.graphics.SumPathEffect;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.SumPathEffectNatives;
+import org.robolectric.shadows.ShadowNativeSumPathEffect.Picker;
+
+/** Shadow for {@link SumPathEffect} that is backed by native code */
+@Implements(value = SumPathEffect.class, minSdk = O, shadowPicker = Picker.class)
+public class ShadowNativeSumPathEffect {
+
+ @Implementation(minSdk = O)
+ protected static long nativeCreate(long first, long second) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return SumPathEffectNatives.nativeCreate(first, second);
+ }
+
+ /** Shadow picker for {@link SumPathEffect}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(null, ShadowNativeSumPathEffect.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSurface.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSurface.java
new file mode 100644
index 000000000..fe789981a
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSurface.java
@@ -0,0 +1,147 @@
+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.Q;
+import static android.os.Build.VERSION_CODES.S;
+
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.SurfaceTexture;
+import android.hardware.HardwareBuffer;
+import android.os.Parcel;
+import android.view.Surface;
+import android.view.Surface.OutOfResourcesException;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.SurfaceNatives;
+import org.robolectric.shadows.ShadowNativeSurface.Picker;
+
+/** Shadow for {@link Surface} that is backed by native code */
+@Implements(value = Surface.class, minSdk = O, shadowPicker = Picker.class, isInAndroidSdk = false)
+public class ShadowNativeSurface {
+ @Implementation
+ protected static long nativeCreateFromSurfaceTexture(SurfaceTexture surfaceTexture)
+ throws OutOfResourcesException {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return SurfaceNatives.nativeCreateFromSurfaceTexture(surfaceTexture);
+ }
+
+ @Implementation
+ protected static long nativeCreateFromSurfaceControl(long surfaceControlNativeObject) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return SurfaceNatives.nativeCreateFromSurfaceControl(surfaceControlNativeObject);
+ }
+
+ @Implementation(minSdk = Q)
+ protected static long nativeGetFromSurfaceControl(
+ long surfaceObject, long surfaceControlNativeObject) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return SurfaceNatives.nativeGetFromSurfaceControl(surfaceObject, surfaceControlNativeObject);
+ }
+
+ @Implementation(minSdk = S)
+ protected static long nativeGetFromBlastBufferQueue(
+ long surfaceObject, long blastBufferQueueNativeObject) {
+ return SurfaceNatives.nativeGetFromBlastBufferQueue(
+ surfaceObject, blastBufferQueueNativeObject);
+ }
+
+ @Implementation
+ protected static long nativeLockCanvas(long nativeObject, Canvas canvas, Rect dirty)
+ throws OutOfResourcesException {
+ return SurfaceNatives.nativeLockCanvas(nativeObject, canvas, dirty);
+ }
+
+ @Implementation
+ protected static void nativeUnlockCanvasAndPost(long nativeObject, Canvas canvas) {
+ SurfaceNatives.nativeUnlockCanvasAndPost(nativeObject, canvas);
+ }
+
+ @Implementation
+ protected static void nativeRelease(long nativeObject) {
+ SurfaceNatives.nativeRelease(nativeObject);
+ }
+
+ @Implementation
+ protected static boolean nativeIsValid(long nativeObject) {
+ return SurfaceNatives.nativeIsValid(nativeObject);
+ }
+
+ @Implementation
+ protected static boolean nativeIsConsumerRunningBehind(long nativeObject) {
+ return SurfaceNatives.nativeIsConsumerRunningBehind(nativeObject);
+ }
+
+ @Implementation
+ protected static long nativeReadFromParcel(long nativeObject, Parcel source) {
+ return SurfaceNatives.nativeReadFromParcel(nativeObject, source);
+ }
+
+ @Implementation
+ protected static void nativeWriteToParcel(long nativeObject, Parcel dest) {
+ SurfaceNatives.nativeWriteToParcel(nativeObject, dest);
+ }
+
+ @Implementation
+ protected static void nativeAllocateBuffers(long nativeObject) {
+ SurfaceNatives.nativeAllocateBuffers(nativeObject);
+ }
+
+ @Implementation
+ protected static int nativeGetWidth(long nativeObject) {
+ return SurfaceNatives.nativeGetWidth(nativeObject);
+ }
+
+ @Implementation
+ protected static int nativeGetHeight(long nativeObject) {
+ return SurfaceNatives.nativeGetHeight(nativeObject);
+ }
+
+ @Implementation
+ protected static long nativeGetNextFrameNumber(long nativeObject) {
+ return SurfaceNatives.nativeGetNextFrameNumber(nativeObject);
+ }
+
+ @Implementation
+ protected static int nativeSetScalingMode(long nativeObject, int scalingMode) {
+ return SurfaceNatives.nativeSetScalingMode(nativeObject, scalingMode);
+ }
+
+ @Implementation
+ protected static int nativeForceScopedDisconnect(long nativeObject) {
+ return SurfaceNatives.nativeForceScopedDisconnect(nativeObject);
+ }
+
+ @Implementation(minSdk = S)
+ protected static int nativeAttachAndQueueBufferWithColorSpace(
+ long nativeObject, HardwareBuffer buffer, int colorSpaceId) {
+ return SurfaceNatives.nativeAttachAndQueueBufferWithColorSpace(
+ nativeObject, buffer, colorSpaceId);
+ }
+
+ @Implementation(minSdk = O_MR1)
+ protected static int nativeSetSharedBufferModeEnabled(long nativeObject, boolean enabled) {
+ return SurfaceNatives.nativeSetSharedBufferModeEnabled(nativeObject, enabled);
+ }
+
+ @Implementation(minSdk = O_MR1)
+ protected static int nativeSetAutoRefreshEnabled(long nativeObject, boolean enabled) {
+ return SurfaceNatives.nativeSetAutoRefreshEnabled(nativeObject, enabled);
+ }
+
+ @Implementation(minSdk = S)
+ protected static int nativeSetFrameRate(
+ long nativeObject, float frameRate, int compatibility, int changeFrameRateStrategy) {
+ return SurfaceNatives.nativeSetFrameRate(
+ nativeObject, frameRate, compatibility, changeFrameRateStrategy);
+ }
+
+ /** Shadow picker for {@link Surface}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(ShadowSurface.class, ShadowNativeSurface.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSweepGradient.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSweepGradient.java
new file mode 100644
index 000000000..d51300f1c
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSweepGradient.java
@@ -0,0 +1,44 @@
+package org.robolectric.shadows;
+
+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 android.graphics.SweepGradient;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.SweepGradientNatives;
+import org.robolectric.shadows.ShadowNativeSweepGradient.Picker;
+
+/** Shadow for {@link SweepGradient} that is backed by native code */
+@Implements(value = SweepGradient.class, minSdk = O, shadowPicker = Picker.class)
+public class ShadowNativeSweepGradient {
+
+ @Implementation(minSdk = Q)
+ protected static long nativeCreate(
+ long matrix, float x, float y, long[] colors, float[] positions, long colorSpaceHandle) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return SweepGradientNatives.nativeCreate(matrix, x, y, colors, positions, colorSpaceHandle);
+ }
+
+ @Implementation(minSdk = O, maxSdk = P)
+ protected static long nativeCreate1(
+ long matrix, float x, float y, int[] colors, float[] positions) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return SweepGradientNatives.nativeCreate1(matrix, x, y, colors, positions);
+ }
+
+ @Implementation(minSdk = O, maxSdk = P)
+ protected static long nativeCreate2(long matrix, float x, float y, int color0, int color1) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return SweepGradientNatives.nativeCreate2(matrix, x, y, color0, color1);
+ }
+
+ /** Shadow picker for {@link SweepGradient}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(null, ShadowNativeSweepGradient.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSystemFonts.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSystemFonts.java
new file mode 100644
index 000000000..f154df3f0
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeSystemFonts.java
@@ -0,0 +1,125 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.Q;
+import static android.os.Build.VERSION_CODES.S;
+import static org.robolectric.util.reflector.Reflector.reflector;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.fonts.Font;
+import android.graphics.fonts.FontCustomizationParser;
+import android.graphics.fonts.FontFamily;
+import android.graphics.fonts.SystemFonts;
+import android.os.Build;
+import android.os.Build.VERSION_CODES;
+import android.text.FontConfig;
+import android.util.ArrayMap;
+import android.util.Log;
+import com.google.common.base.Preconditions;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.util.ArrayList;
+import java.util.Map;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowNativeSystemFonts.Picker;
+import org.robolectric.util.reflector.Direct;
+import org.robolectric.util.reflector.ForType;
+import org.robolectric.util.reflector.Static;
+
+/**
+ * Shadow for {@link SystemFonts} for the Robolectric native runtime. It supports getting system
+ * font config using a custom fonts path.
+ */
+@Implements(
+ value = SystemFonts.class,
+ minSdk = Build.VERSION_CODES.Q,
+ isInAndroidSdk = false,
+ shadowPicker = Picker.class)
+public class ShadowNativeSystemFonts {
+ @Implementation(minSdk = S)
+ protected static FontConfig getSystemFontConfigInternal(
+ String fontsXml,
+ String systemFontDir,
+ String oemXml,
+ String productFontDir,
+ Map<String, File> updatableFontMap,
+ long lastModifiedDate,
+ int configVersion) {
+ 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");
+ return reflector(SystemFontsReflector.class)
+ .getSystemFontConfigInternal(
+ fontDir + "fonts.xml",
+ fontDir,
+ null,
+ null,
+ updatableFontMap,
+ lastModifiedDate,
+ configVersion);
+ }
+
+ @Implementation(maxSdk = VERSION_CODES.R)
+ public static FontConfig.Alias[] buildSystemFallback(
+ String xmlPath,
+ String systemFontDir,
+ FontCustomizationParser.Result oemCustomization,
+ ArrayMap<String, FontFamily[]> fallbackMap,
+ ArrayList<Font> availableFonts) {
+ 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");
+ return reflector(SystemFontsReflector.class)
+ .buildSystemFallback(
+ fontDir + "fonts.xml", fontDir, oemCustomization, fallbackMap, availableFonts);
+ }
+
+ @Implementation(minSdk = Q, maxSdk = Q)
+ @Nullable
+ protected static ByteBuffer mmap(@NonNull String fullPath) {
+ try (FileInputStream file = new FileInputStream(fullPath)) {
+ final FileChannel fileChannel = file.getChannel();
+ final long fontSize = fileChannel.size();
+ return fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize);
+ } catch (IOException e) {
+ Log.w("SystemFonts", e.getMessage());
+ return null;
+ }
+ }
+
+ @ForType(SystemFonts.class)
+ interface SystemFontsReflector {
+ @Static
+ @Direct
+ FontConfig getSystemFontConfigInternal(
+ String fontsXml,
+ String systemFontDir,
+ String oemXml,
+ String productFontDir,
+ Map<String, File> updatableFontMap,
+ long lastModifiedDate,
+ int configVersion);
+
+ @Static
+ @Direct
+ FontConfig.Alias[] buildSystemFallback(
+ String xmlPath,
+ String fontDir,
+ FontCustomizationParser.Result oemCustomization,
+ ArrayMap<String, FontFamily[]> fallbackMap,
+ ArrayList<Font> availableFonts);
+ }
+
+ /** Shadow picker for {@link SystemFonts}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(null, ShadowNativeSystemFonts.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeTableMaskFilter.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeTableMaskFilter.java
new file mode 100644
index 000000000..7d7c0a34c
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeTableMaskFilter.java
@@ -0,0 +1,44 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+
+import android.graphics.TableMaskFilter;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.TableMaskFilterNatives;
+import org.robolectric.shadows.ShadowNativeTableMaskFilter.Picker;
+
+/** Shadow for {@link TableMaskFilter} that is backed by native code */
+@Implements(
+ value = TableMaskFilter.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ isInAndroidSdk = false)
+public class ShadowNativeTableMaskFilter {
+
+ @Implementation(minSdk = O)
+ protected static long nativeNewTable(byte[] table) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return TableMaskFilterNatives.nativeNewTable(table);
+ }
+
+ @Implementation(minSdk = O)
+ protected static long nativeNewClip(int min, int max) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return TableMaskFilterNatives.nativeNewClip(min, max);
+ }
+
+ @Implementation(minSdk = O)
+ protected static long nativeNewGamma(float gamma) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return TableMaskFilterNatives.nativeNewGamma(gamma);
+ }
+
+ /** Shadow picker for {@link TableMaskFilter}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(null, ShadowNativeTableMaskFilter.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeThreadedRenderer.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeThreadedRenderer.java
new file mode 100644
index 000000000..e9ea4645e
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeThreadedRenderer.java
@@ -0,0 +1,183 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+import static android.os.Build.VERSION_CODES.P;
+
+import android.graphics.Bitmap;
+import android.view.ThreadedRenderer;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.HardwareRendererNatives;
+import org.robolectric.shadows.ShadowNativeThreadedRenderer.Picker;
+
+/** Shadow for {@link ThreadedRenderer} that is backed by native code */
+@Implements(value = ThreadedRenderer.class, minSdk = O, maxSdk = P, shadowPicker = Picker.class)
+public class ShadowNativeThreadedRenderer {
+
+ // ThreadedRenderer specific functions. These do not exist in HardwareRenderer
+ @Implementation
+ protected static boolean nSupportsOpenGL() {
+ return false;
+ }
+
+ // HardwareRenderer methods. These exist in both ThreadedRenderer and HardwareRenderer.
+ @Implementation
+ protected static void nRotateProcessStatsBuffer() {
+ HardwareRendererNatives.nRotateProcessStatsBuffer();
+ }
+
+ @Implementation
+ protected static void nSetProcessStatsBuffer(int fd) {
+ HardwareRendererNatives.nSetProcessStatsBuffer(fd);
+ }
+
+ @Implementation
+ protected static int nGetRenderThreadTid(long nativeProxy) {
+ return HardwareRendererNatives.nGetRenderThreadTid(nativeProxy);
+ }
+
+ @Implementation
+ protected static long nCreateRootRenderNode() {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return HardwareRendererNatives.nCreateRootRenderNode();
+ }
+
+ @Implementation
+ protected static long nCreateProxy(boolean translucent, long rootRenderNode) {
+ return HardwareRendererNatives.nCreateProxy(translucent, rootRenderNode);
+ }
+
+ @Implementation
+ protected static void nDeleteProxy(long nativeProxy) {
+ HardwareRendererNatives.nDeleteProxy(nativeProxy);
+ }
+
+ @Implementation
+ protected static boolean nLoadSystemProperties(long nativeProxy) {
+ return HardwareRendererNatives.nLoadSystemProperties(nativeProxy);
+ }
+
+ @Implementation
+ protected static void nSetName(long nativeProxy, String name) {
+ HardwareRendererNatives.nSetName(nativeProxy, name);
+ }
+
+ @Implementation
+ protected static void nSetStopped(long nativeProxy, boolean stopped) {
+ HardwareRendererNatives.nSetStopped(nativeProxy, stopped);
+ }
+
+ @Implementation
+ protected static void nSetOpaque(long nativeProxy, boolean opaque) {
+ HardwareRendererNatives.nSetOpaque(nativeProxy, opaque);
+ }
+
+ @Implementation
+ protected static int nSyncAndDrawFrame(long nativeProxy, long[] frameInfo, int size) {
+ return HardwareRendererNatives.nSyncAndDrawFrame(nativeProxy, frameInfo, size);
+ }
+
+ @Implementation
+ protected static void nDestroy(long nativeProxy, long rootRenderNode) {
+ HardwareRendererNatives.nDestroy(nativeProxy, rootRenderNode);
+ }
+
+ @Implementation
+ protected static void nRegisterAnimatingRenderNode(long rootRenderNode, long animatingNode) {
+ HardwareRendererNatives.nRegisterAnimatingRenderNode(rootRenderNode, animatingNode);
+ }
+
+ @Implementation
+ protected static void nRegisterVectorDrawableAnimator(long rootRenderNode, long animator) {
+ HardwareRendererNatives.nRegisterVectorDrawableAnimator(rootRenderNode, animator);
+ }
+
+ @Implementation
+ protected static long nCreateTextureLayer(long nativeProxy) {
+ return HardwareRendererNatives.nCreateTextureLayer(nativeProxy);
+ }
+
+ @Implementation
+ protected static void nBuildLayer(long nativeProxy, long node) {
+ HardwareRendererNatives.nBuildLayer(nativeProxy, node);
+ }
+
+ @Implementation
+ protected static void nPushLayerUpdate(long nativeProxy, long layer) {
+ HardwareRendererNatives.nPushLayerUpdate(nativeProxy, layer);
+ }
+
+ @Implementation
+ protected static void nCancelLayerUpdate(long nativeProxy, long layer) {
+ HardwareRendererNatives.nCancelLayerUpdate(nativeProxy, layer);
+ }
+
+ @Implementation
+ protected static void nDetachSurfaceTexture(long nativeProxy, long layer) {
+ HardwareRendererNatives.nDetachSurfaceTexture(nativeProxy, layer);
+ }
+
+ @Implementation
+ protected static void nDestroyHardwareResources(long nativeProxy) {
+ HardwareRendererNatives.nDestroyHardwareResources(nativeProxy);
+ }
+
+ @Implementation
+ protected static void nTrimMemory(int level) {
+ HardwareRendererNatives.nTrimMemory(level);
+ }
+
+ @Implementation
+ protected static void nOverrideProperty(String name, String value) {
+ HardwareRendererNatives.nOverrideProperty(name, value);
+ }
+
+ @Implementation
+ protected static void nFence(long nativeProxy) {
+ HardwareRendererNatives.nFence(nativeProxy);
+ }
+
+ @Implementation
+ protected static void nStopDrawing(long nativeProxy) {
+ HardwareRendererNatives.nStopDrawing(nativeProxy);
+ }
+
+ @Implementation
+ protected static void nNotifyFramePending(long nativeProxy) {
+ HardwareRendererNatives.nNotifyFramePending(nativeProxy);
+ }
+
+ @Implementation
+ protected static void nAddRenderNode(long nativeProxy, long rootRenderNode, boolean placeFront) {
+ HardwareRendererNatives.nAddRenderNode(nativeProxy, rootRenderNode, placeFront);
+ }
+
+ @Implementation
+ protected static void nRemoveRenderNode(long nativeProxy, long rootRenderNode) {
+ HardwareRendererNatives.nRemoveRenderNode(nativeProxy, rootRenderNode);
+ }
+
+ @Implementation
+ protected static void nDrawRenderNode(long nativeProxy, long rootRenderNode) {
+ HardwareRendererNatives.nDrawRenderNode(nativeProxy, rootRenderNode);
+ }
+
+ @Implementation
+ protected static void nSetContentDrawBounds(
+ long nativeProxy, int left, int top, int right, int bottom) {
+ HardwareRendererNatives.nSetContentDrawBounds(nativeProxy, left, top, right, bottom);
+ }
+
+ @Implementation
+ protected static Bitmap nCreateHardwareBitmap(long renderNode, int width, int height) {
+ return HardwareRendererNatives.nCreateHardwareBitmap(renderNode, width, height);
+ }
+
+ /** Shadow picker for {@link ThreadedRenderer}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(ShadowThreadedRenderer.class, ShadowNativeThreadedRenderer.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeTypeface.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeTypeface.java
new file mode 100644
index 000000000..0c7fcf630
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeTypeface.java
@@ -0,0 +1,291 @@
+package org.robolectric.shadows;
+
+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;
+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 org.robolectric.util.reflector.Reflector.reflector;
+
+import android.graphics.FontFamily;
+import android.graphics.Typeface;
+import android.graphics.fonts.FontVariationAxis;
+import android.text.FontConfig;
+import android.util.ArrayMap;
+import android.util.Log;
+import com.google.common.base.Preconditions;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.util.List;
+import java.util.Map;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.TypefaceNatives;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.util.reflector.Direct;
+import org.robolectric.util.reflector.ForType;
+import org.robolectric.util.reflector.Static;
+
+/** Shadow for {@link Typeface} that is backed by native code */
+@Implements(value = Typeface.class, looseSignatures = true, minSdk = O, isInAndroidSdk = false)
+public class ShadowNativeTypeface extends ShadowTypeface {
+
+ private static final String TAG = "ShadowNativeTypeface";
+
+ // Style value for building typeface.
+ 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();
+ }
+
+ @Implementation(minSdk = P, maxSdk = P)
+ protected static void buildSystemFallback(
+ String xmlPath,
+ String systemFontDir,
+ ArrayMap<String, Typeface> fontMap,
+ ArrayMap<String, FontFamily[]> fallbackMap) {
+ 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");
+ reflector(TypefaceReflector.class)
+ .buildSystemFallback(fontDir + "fonts.xml", fontDir, fontMap, fallbackMap);
+ }
+
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static File getSystemFontConfigLocation() {
+ // Ensure that the Robolectric native runtime is loaded in ordere to ensure that the
+ // `robolectric.nativeruntime.fontdir` system property is valid.
+ 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");
+ return new File(fontDir);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Implementation(minSdk = O, maxSdk = O_MR1)
+ protected static Object makeFamilyFromParsed(Object family, Object bufferForPathMap) {
+ FontConfigFamilyReflector reflector = reflector(FontConfigFamilyReflector.class, family);
+ Map<String, ByteBuffer> bufferForPath = (Map<String, ByteBuffer>) bufferForPathMap;
+
+ FontFamily fontFamily =
+ Shadow.newInstance(
+ FontFamily.class,
+ new Class<?>[] {String.class, int.class},
+ new Object[] {reflector.getLanguage(), reflector.getVariant()});
+ for (FontConfig.Font font : reflector.getFonts()) {
+ String fullPathName =
+ System.getProperty("robolectric.nativeruntime.fontdir")
+ + reflector(FontConfigFontReflector.class, font).getFontName();
+ ByteBuffer fontBuffer = bufferForPath.get(fullPathName);
+ if (fontBuffer == null) {
+ try (FileInputStream file = new FileInputStream(fullPathName)) {
+ FileChannel fileChannel = file.getChannel();
+ long fontSize = fileChannel.size();
+ fontBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize);
+ bufferForPath.put(fullPathName, fontBuffer);
+ } catch (IOException e) {
+ Log.w(TAG, "Error mapping font file " + fullPathName);
+ continue;
+ }
+ }
+ if (!fontFamily.addFontFromBuffer(
+ fontBuffer,
+ font.getTtcIndex(),
+ font.getAxes(),
+ font.getWeight(),
+ font.isItalic() ? STYLE_ITALIC : STYLE_NORMAL)) {
+ Log.e(TAG, "Error creating font " + fullPathName + "#" + font.getTtcIndex());
+ }
+ }
+ if (!fontFamily.freeze()) {
+ // Treat as system error since reaching here means that a system pre-installed font
+ // can't be used by our font stack.
+ Log.w(TAG, "Unable to load Family: " + reflector.getName() + ":" + reflector.getLanguage());
+ return null;
+ }
+ return fontFamily;
+ }
+
+ @Implementation(minSdk = LOLLIPOP)
+ protected static long nativeCreateFromTypeface(long nativeInstance, int style) {
+ return TypefaceNatives.nativeCreateFromTypeface(nativeInstance, style);
+ }
+
+ @Implementation(minSdk = O)
+ protected static long nativeCreateFromTypefaceWithExactStyle(
+ long nativeInstance, int weight, boolean italic) {
+ return TypefaceNatives.nativeCreateFromTypefaceWithExactStyle(nativeInstance, weight, italic);
+ }
+
+ @Implementation(minSdk = O)
+ protected static long nativeCreateFromTypefaceWithVariation(
+ long nativeInstance, List<FontVariationAxis> axes) {
+ return TypefaceNatives.nativeCreateFromTypefaceWithVariation(nativeInstance, axes);
+ }
+
+ @Implementation(minSdk = LOLLIPOP)
+ protected static long nativeCreateWeightAlias(long nativeInstance, int weight) {
+ return TypefaceNatives.nativeCreateWeightAlias(nativeInstance, weight);
+ }
+
+ @Implementation(minSdk = O, maxSdk = R)
+ protected static long nativeCreateFromArray(long[] familyArray, int weight, int italic) {
+ return TypefaceNatives.nativeCreateFromArray(familyArray, 0, weight, italic);
+ }
+
+ @Implementation(minSdk = S)
+ protected static long nativeCreateFromArray(
+ long[] familyArray, long fallbackTypeface, int weight, int italic) {
+ return TypefaceNatives.nativeCreateFromArray(familyArray, fallbackTypeface, weight, italic);
+ }
+
+ @Implementation(minSdk = O)
+ protected static int[] nativeGetSupportedAxes(long nativeInstance) {
+ return TypefaceNatives.nativeGetSupportedAxes(nativeInstance);
+ }
+
+ @Implementation(minSdk = LOLLIPOP)
+ protected static void nativeSetDefault(long nativePtr) {
+ TypefaceNatives.nativeSetDefault(nativePtr);
+ }
+
+ @Implementation(minSdk = LOLLIPOP)
+ protected static int nativeGetStyle(long nativePtr) {
+ return TypefaceNatives.nativeGetStyle(nativePtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static int nativeGetWeight(long nativePtr) {
+ return TypefaceNatives.nativeGetWeight(nativePtr);
+ }
+
+ @Implementation(minSdk = P)
+ protected static long nativeGetReleaseFunc() {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return TypefaceNatives.nativeGetReleaseFunc();
+ }
+
+ @Implementation(minSdk = S, maxSdk = TIRAMISU)
+ protected static int nativeGetFamilySize(long nativePtr) {
+ return TypefaceNatives.nativeGetFamilySize(nativePtr);
+ }
+
+ @Implementation(minSdk = S, maxSdk = TIRAMISU)
+ protected static long nativeGetFamily(long nativePtr, int index) {
+ return TypefaceNatives.nativeGetFamily(nativePtr, index);
+ }
+
+ @Implementation(minSdk = Q)
+ protected static void nativeRegisterGenericFamily(String str, long nativePtr) {
+ TypefaceNatives.nativeRegisterGenericFamily(str, nativePtr);
+ }
+
+ @Implementation(minSdk = S, maxSdk = TIRAMISU)
+ protected static int nativeWriteTypefaces(ByteBuffer buffer, long[] nativePtrs) {
+ return TypefaceNatives.nativeWriteTypefaces(buffer, nativePtrs);
+ }
+
+ @Implementation(minSdk = 10000)
+ protected static int nativeWriteTypefaces(ByteBuffer buffer, int position, long[] nativePtrs) {
+ return nativeWriteTypefaces(buffer, nativePtrs);
+ }
+
+ @Implementation(minSdk = S, maxSdk = TIRAMISU)
+ protected static long[] nativeReadTypefaces(ByteBuffer buffer) {
+ return TypefaceNatives.nativeReadTypefaces(buffer);
+ }
+
+ @Implementation(minSdk = 10000)
+ protected static long[] nativeReadTypefaces(ByteBuffer buffer, int position) {
+ return nativeReadTypefaces(buffer);
+ }
+
+ @Implementation(minSdk = S)
+ protected static void nativeForceSetStaticFinalField(String fieldName, Typeface typeface) {
+ TypefaceNatives.nativeForceSetStaticFinalField(fieldName, typeface);
+ }
+
+ @Implementation(minSdk = S)
+ protected static void nativeAddFontCollections(long nativePtr) {
+ TypefaceNatives.nativeAddFontCollections(nativePtr);
+ }
+
+ static void ensureInitialized() {
+ try {
+ // Forces static initialization. This should be called before any native code that calls
+ // Typeface::resolveDefault.
+ Class.forName("android.graphics.Typeface");
+ } catch (ClassNotFoundException e) {
+ throw new LinkageError("Unable to load Typeface", e);
+ }
+ }
+
+ @Override
+ public FontDesc getFontDescription() {
+ throw new UnsupportedOperationException(
+ "Legacy ShadowTypeface description APIs are not supported");
+ }
+
+ /**
+ * Shadow for {@link Typeface.Builder}. It is empty to avoid using the legacy {@link
+ * Typeface.Builder} shadow.
+ */
+ @Implements(
+ value = Typeface.Builder.class,
+ minSdk = P,
+ shadowPicker = ShadowNativeTypefaceBuilder.Picker.class,
+ isInAndroidSdk = false)
+ public static class ShadowNativeTypefaceBuilder {
+ /** Shadow picker for {@link Typeface.Builder}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(ShadowLegacyTypeface.ShadowBuilder.class, ShadowNativeTypefaceBuilder.class);
+ }
+ }
+ }
+
+ @ForType(Typeface.class)
+ interface TypefaceReflector {
+ @CanIgnoreReturnValue
+ @Static
+ @Direct
+ FontConfig.Alias[] buildSystemFallback(
+ String xmlPath,
+ String fontDir,
+ ArrayMap<String, Typeface> fontMap,
+ ArrayMap<String, FontFamily[]> fallbackMap);
+ }
+
+ @ForType(className = "android.text.FontConfig$Family")
+ interface FontConfigFamilyReflector {
+ String getLanguage();
+
+ int getVariant();
+
+ FontConfig.Font[] getFonts();
+
+ String getName();
+ }
+
+ @ForType(className = "android.text.FontConfig$Font")
+ interface FontConfigFontReflector {
+ String getFontName();
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeVectorDrawable.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeVectorDrawable.java
new file mode 100644
index 000000000..88a4a76d5
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeVectorDrawable.java
@@ -0,0 +1,343 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+import static android.os.Build.VERSION_CODES.Q;
+
+import android.graphics.Rect;
+import android.graphics.drawable.VectorDrawable;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.DefaultNativeRuntimeLoader;
+import org.robolectric.nativeruntime.VectorDrawableNatives;
+import org.robolectric.shadows.ShadowNativeVectorDrawable.Picker;
+
+/** Shadow for {@link VectorDrawable} that is backed by native code */
+@Implements(
+ value = VectorDrawable.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ isInAndroidSdk = false)
+public class ShadowNativeVectorDrawable extends ShadowDrawable {
+
+ @Implementation(minSdk = O)
+ protected static int nDraw(
+ long rendererPtr,
+ long canvasWrapperPtr,
+ long colorFilterPtr,
+ Rect bounds,
+ boolean needsMirroring,
+ boolean canReuseCache) {
+ return VectorDrawableNatives.nDraw(
+ rendererPtr, canvasWrapperPtr, colorFilterPtr, bounds, needsMirroring, canReuseCache);
+ }
+
+ @Implementation(minSdk = O)
+ protected static boolean nGetFullPathProperties(long pathPtr, byte[] properties, int length) {
+ return VectorDrawableNatives.nGetFullPathProperties(pathPtr, properties, length);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetName(long nodePtr, String name) {
+ VectorDrawableNatives.nSetName(nodePtr, name);
+ }
+
+ @Implementation(minSdk = O)
+ protected static boolean nGetGroupProperties(long groupPtr, float[] properties, int length) {
+ return VectorDrawableNatives.nGetGroupProperties(groupPtr, properties, length);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetPathString(long pathPtr, String pathString, int length) {
+ VectorDrawableNatives.nSetPathString(pathPtr, pathString, length);
+ }
+
+ @Implementation(minSdk = O)
+ protected static long nCreateTree(long rootGroupPtr) {
+ return VectorDrawableNatives.nCreateTree(rootGroupPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static long nCreateTreeFromCopy(long treeToCopy, long rootGroupPtr) {
+ return VectorDrawableNatives.nCreateTreeFromCopy(treeToCopy, rootGroupPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetRendererViewportSize(
+ long rendererPtr, float viewportWidth, float viewportHeight) {
+ VectorDrawableNatives.nSetRendererViewportSize(rendererPtr, viewportWidth, viewportHeight);
+ }
+
+ @Implementation(minSdk = O)
+ protected static boolean nSetRootAlpha(long rendererPtr, float alpha) {
+ return VectorDrawableNatives.nSetRootAlpha(rendererPtr, alpha);
+ }
+
+ @Implementation(minSdk = O)
+ protected static float nGetRootAlpha(long rendererPtr) {
+ return VectorDrawableNatives.nGetRootAlpha(rendererPtr);
+ }
+
+ @Implementation(minSdk = Q)
+ protected static void nSetAntiAlias(long rendererPtr, boolean aa) {
+ VectorDrawableNatives.nSetAntiAlias(rendererPtr, aa);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetAllowCaching(long rendererPtr, boolean allowCaching) {
+ VectorDrawableNatives.nSetAllowCaching(rendererPtr, allowCaching);
+ }
+
+ @Implementation(minSdk = O)
+ protected static long nCreateFullPath() {
+ return VectorDrawableNatives.nCreateFullPath();
+ }
+
+ @Implementation(minSdk = O)
+ protected static long nCreateFullPath(long nativeFullPathPtr) {
+ return VectorDrawableNatives.nCreateFullPath(nativeFullPathPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nUpdateFullPathProperties(
+ long pathPtr,
+ float strokeWidth,
+ int strokeColor,
+ float strokeAlpha,
+ int fillColor,
+ float fillAlpha,
+ float trimPathStart,
+ float trimPathEnd,
+ float trimPathOffset,
+ float strokeMiterLimit,
+ int strokeLineCap,
+ int strokeLineJoin,
+ int fillType) {
+ VectorDrawableNatives.nUpdateFullPathProperties(
+ pathPtr,
+ strokeWidth,
+ strokeColor,
+ strokeAlpha,
+ fillColor,
+ fillAlpha,
+ trimPathStart,
+ trimPathEnd,
+ trimPathOffset,
+ strokeMiterLimit,
+ strokeLineCap,
+ strokeLineJoin,
+ fillType);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nUpdateFullPathFillGradient(long pathPtr, long fillGradientPtr) {
+ VectorDrawableNatives.nUpdateFullPathFillGradient(pathPtr, fillGradientPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nUpdateFullPathStrokeGradient(long pathPtr, long strokeGradientPtr) {
+ VectorDrawableNatives.nUpdateFullPathStrokeGradient(pathPtr, strokeGradientPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static long nCreateClipPath() {
+ return VectorDrawableNatives.nCreateClipPath();
+ }
+
+ @Implementation(minSdk = O)
+ protected static long nCreateClipPath(long clipPathPtr) {
+ return VectorDrawableNatives.nCreateClipPath(clipPathPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static long nCreateGroup() {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return VectorDrawableNatives.nCreateGroup();
+ }
+
+ @Implementation(minSdk = O)
+ protected static long nCreateGroup(long groupPtr) {
+ DefaultNativeRuntimeLoader.injectAndLoad();
+ return VectorDrawableNatives.nCreateGroup(groupPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nUpdateGroupProperties(
+ long groupPtr,
+ float rotate,
+ float pivotX,
+ float pivotY,
+ float scaleX,
+ float scaleY,
+ float translateX,
+ float translateY) {
+ VectorDrawableNatives.nUpdateGroupProperties(
+ groupPtr, rotate, pivotX, pivotY, scaleX, scaleY, translateX, translateY);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nAddChild(long groupPtr, long nodePtr) {
+ VectorDrawableNatives.nAddChild(groupPtr, nodePtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static float nGetRotation(long groupPtr) {
+ return VectorDrawableNatives.nGetRotation(groupPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetRotation(long groupPtr, float rotation) {
+ VectorDrawableNatives.nSetRotation(groupPtr, rotation);
+ }
+
+ @Implementation(minSdk = O)
+ protected static float nGetPivotX(long groupPtr) {
+ return VectorDrawableNatives.nGetPivotX(groupPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetPivotX(long groupPtr, float pivotX) {
+ VectorDrawableNatives.nSetPivotX(groupPtr, pivotX);
+ }
+
+ @Implementation(minSdk = O)
+ protected static float nGetPivotY(long groupPtr) {
+ return VectorDrawableNatives.nGetPivotY(groupPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetPivotY(long groupPtr, float pivotY) {
+ VectorDrawableNatives.nSetPivotY(groupPtr, pivotY);
+ }
+
+ @Implementation(minSdk = O)
+ protected static float nGetScaleX(long groupPtr) {
+ return VectorDrawableNatives.nGetScaleX(groupPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetScaleX(long groupPtr, float scaleX) {
+ VectorDrawableNatives.nSetScaleX(groupPtr, scaleX);
+ }
+
+ @Implementation(minSdk = O)
+ protected static float nGetScaleY(long groupPtr) {
+ return VectorDrawableNatives.nGetScaleY(groupPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetScaleY(long groupPtr, float scaleY) {
+ VectorDrawableNatives.nSetScaleY(groupPtr, scaleY);
+ }
+
+ @Implementation(minSdk = O)
+ protected static float nGetTranslateX(long groupPtr) {
+ return VectorDrawableNatives.nGetTranslateX(groupPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetTranslateX(long groupPtr, float translateX) {
+ VectorDrawableNatives.nSetTranslateX(groupPtr, translateX);
+ }
+
+ @Implementation(minSdk = O)
+ protected static float nGetTranslateY(long groupPtr) {
+ return VectorDrawableNatives.nGetTranslateY(groupPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetTranslateY(long groupPtr, float translateY) {
+ VectorDrawableNatives.nSetTranslateY(groupPtr, translateY);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetPathData(long pathPtr, long pathDataPtr) {
+ VectorDrawableNatives.nSetPathData(pathPtr, pathDataPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static float nGetStrokeWidth(long pathPtr) {
+ return VectorDrawableNatives.nGetStrokeWidth(pathPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetStrokeWidth(long pathPtr, float width) {
+ VectorDrawableNatives.nSetStrokeWidth(pathPtr, width);
+ }
+
+ @Implementation(minSdk = O)
+ protected static int nGetStrokeColor(long pathPtr) {
+ return VectorDrawableNatives.nGetStrokeColor(pathPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetStrokeColor(long pathPtr, int strokeColor) {
+ VectorDrawableNatives.nSetStrokeColor(pathPtr, strokeColor);
+ }
+
+ @Implementation(minSdk = O)
+ protected static float nGetStrokeAlpha(long pathPtr) {
+ return VectorDrawableNatives.nGetStrokeAlpha(pathPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetStrokeAlpha(long pathPtr, float alpha) {
+ VectorDrawableNatives.nSetStrokeAlpha(pathPtr, alpha);
+ }
+
+ @Implementation(minSdk = O)
+ protected static int nGetFillColor(long pathPtr) {
+ return VectorDrawableNatives.nGetFillColor(pathPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetFillColor(long pathPtr, int fillColor) {
+ VectorDrawableNatives.nSetFillColor(pathPtr, fillColor);
+ }
+
+ @Implementation(minSdk = O)
+ protected static float nGetFillAlpha(long pathPtr) {
+ return VectorDrawableNatives.nGetFillAlpha(pathPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetFillAlpha(long pathPtr, float fillAlpha) {
+ VectorDrawableNatives.nSetFillAlpha(pathPtr, fillAlpha);
+ }
+
+ @Implementation(minSdk = O)
+ protected static float nGetTrimPathStart(long pathPtr) {
+ return VectorDrawableNatives.nGetTrimPathStart(pathPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetTrimPathStart(long pathPtr, float trimPathStart) {
+ VectorDrawableNatives.nSetTrimPathStart(pathPtr, trimPathStart);
+ }
+
+ @Implementation(minSdk = O)
+ protected static float nGetTrimPathEnd(long pathPtr) {
+ return VectorDrawableNatives.nGetTrimPathEnd(pathPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetTrimPathEnd(long pathPtr, float trimPathEnd) {
+ VectorDrawableNatives.nSetTrimPathEnd(pathPtr, trimPathEnd);
+ }
+
+ @Implementation(minSdk = O)
+ protected static float nGetTrimPathOffset(long pathPtr) {
+ return VectorDrawableNatives.nGetTrimPathOffset(pathPtr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nSetTrimPathOffset(long pathPtr, float trimPathOffset) {
+ VectorDrawableNatives.nSetTrimPathOffset(pathPtr, trimPathOffset);
+ }
+
+ /** Shadow picker for {@link VectorDrawable}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(ShadowVectorDrawable.class, ShadowNativeVectorDrawable.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeVirtualRefBasePtr.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeVirtualRefBasePtr.java
new file mode 100644
index 000000000..8343912c5
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNativeVirtualRefBasePtr.java
@@ -0,0 +1,35 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.O;
+
+import com.android.internal.util.VirtualRefBasePtr;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.nativeruntime.VirtualRefBasePtrNatives;
+import org.robolectric.shadows.ShadowNativeVirtualRefBasePtr.Picker;
+
+/** Shadow for {@link VirtualRefBasePtr} that is backed by native code */
+@Implements(
+ value = VirtualRefBasePtr.class,
+ minSdk = O,
+ shadowPicker = Picker.class,
+ isInAndroidSdk = false)
+public class ShadowNativeVirtualRefBasePtr {
+
+ @Implementation(minSdk = O)
+ protected static void nIncStrong(long ptr) {
+ VirtualRefBasePtrNatives.nIncStrong(ptr);
+ }
+
+ @Implementation(minSdk = O)
+ protected static void nDecStrong(long ptr) {
+ VirtualRefBasePtrNatives.nDecStrong(ptr);
+ }
+
+ /** Shadow picker for {@link VirtualRefBasePtr}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(ShadowVirtualRefBasePtr.class, ShadowNativeVirtualRefBasePtr.class);
+ }
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNetworkCapabilities.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNetworkCapabilities.java
index 39e3d9b02..4f03aa337 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNetworkCapabilities.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNetworkCapabilities.java
@@ -19,7 +19,7 @@ import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;
/** Robolectic provides overrides for fetching and updating transport. */
-@Implements(value = NetworkCapabilities.class, minSdk = LOLLIPOP)
+@Implements(value = NetworkCapabilities.class, minSdk = LOLLIPOP, looseSignatures = true)
public class ShadowNetworkCapabilities {
@RealObject protected NetworkCapabilities realNetworkCapabilities;
@@ -90,10 +90,12 @@ public class ShadowNetworkCapabilities {
/** Sets the LinkDownstreamBandwidthKbps of the NetworkCapabilities. */
@HiddenApi
- @Implementation(minSdk = Q)
- public NetworkCapabilities setLinkDownstreamBandwidthKbps(int kbps) {
+ @Implementation
+ public Object setLinkDownstreamBandwidthKbps(Object kbps) {
+ // Loose signatures is necessary because the return type of setLinkDownstreamBandwidthKbps
+ // changed from void to NetworkCapabilities starting from API 28 (Pie)
return reflector(NetworkCapabilitiesReflector.class, realNetworkCapabilities)
- .setLinkDownstreamBandwidthKbps(kbps);
+ .setLinkDownstreamBandwidthKbps((int) kbps);
}
@ForType(NetworkCapabilities.class)
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNoopNativeAllocationRegistry.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNoopNativeAllocationRegistry.java
new file mode 100644
index 000000000..7310b5cac
--- /dev/null
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNoopNativeAllocationRegistry.java
@@ -0,0 +1,26 @@
+package org.robolectric.shadows;
+
+import static android.os.Build.VERSION_CODES.N;
+
+import libcore.util.NativeAllocationRegistry;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+/** Shadow for {@link NativeAllocationRegistry} that is a no-op. */
+@Implements(
+ value = NativeAllocationRegistry.class,
+ minSdk = N,
+ isInAndroidSdk = false,
+ looseSignatures = true)
+public class ShadowNoopNativeAllocationRegistry {
+
+ @Implementation
+ protected Runnable registerNativeAllocation(Object referent, Object allocator) {
+ return () -> {};
+ }
+
+ @Implementation
+ protected Runnable registerNativeAllocation(Object referent, long nativePtr) {
+ return () -> {};
+ }
+}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNumberPicker.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNumberPicker.java
index 8cf21f9b8..3b9889fc7 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNumberPicker.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowNumberPicker.java
@@ -12,61 +12,25 @@ import org.robolectric.util.reflector.ForType;
@Implements(value = NumberPicker.class)
public class ShadowNumberPicker extends ShadowLinearLayout {
@RealObject private NumberPicker realNumberPicker;
- private int value;
- private int minValue;
- private int maxValue;
- private boolean wrapSelectorWheel;
private String[] displayedValues;
private NumberPicker.OnValueChangeListener onValueChangeListener;
@Implementation
- protected void setValue(int value) {
- this.value = value;
- }
-
- @Implementation
- protected int getValue() {
- return value;
- }
-
- @Implementation
protected void setDisplayedValues(String[] displayedValues) {
- this.displayedValues = displayedValues;
+ if (ShadowView.useRealGraphics()) {
+ reflector(NumberPickerReflector.class, realNumberPicker).setDisplayedValues(displayedValues);
+ } else {
+ this.displayedValues = displayedValues;
+ }
}
@Implementation
protected String[] getDisplayedValues() {
- return displayedValues;
- }
-
- @Implementation
- protected void setMinValue(int minValue) {
- this.minValue = minValue;
- }
-
- @Implementation
- protected void setMaxValue(int maxValue) {
- this.maxValue = maxValue;
- }
-
- @Implementation
- protected int getMinValue() {
- return this.minValue;
- }
-
- @Implementation
- protected int getMaxValue() {
- return this.maxValue;
- }
-
- @Implementation
- protected void setWrapSelectorWheel(boolean wrapSelectorWheel) {
- this.wrapSelectorWheel = wrapSelectorWheel;
- }
-
- @Implementation
- protected boolean getWrapSelectorWheel() {
- return wrapSelectorWheel;
+ if (ShadowView.useRealGraphics()) {
+ return reflector(NumberPickerReflector.class, realNumberPicker).getDisplayedValues();
+ } else {
+ return displayedValues;
+ }
}
@Implementation
@@ -84,5 +48,11 @@ public class ShadowNumberPicker extends ShadowLinearLayout {
@Direct
void setOnValueChangedListener(NumberPicker.OnValueChangeListener listener);
+
+ @Direct
+ void setDisplayedValues(String[] displayedValues);
+
+ @Direct
+ String[] getDisplayedValues();
}
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowOutline.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowOutline.java
deleted file mode 100644
index d09466817..000000000
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowOutline.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package org.robolectric.shadows;
-
-import static android.os.Build.VERSION_CODES.LOLLIPOP;
-
-import android.graphics.Outline;
-import android.graphics.Path;
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-
-@Implements(value = Outline.class, minSdk = LOLLIPOP)
-public class ShadowOutline {
-
- @Implementation
- protected void setConvexPath(Path convexPath) {}
-} \ No newline at end of file
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPath.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPath.java
index 889736f05..de99c0123 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPath.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPath.java
@@ -1,163 +1,28 @@
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;
-import static org.robolectric.shadows.ShadowPath.Point.Type.MOVE_TO;
-import android.graphics.Matrix;
import android.graphics.Path;
-import android.graphics.Path.Direction;
import android.graphics.RectF;
-import android.util.Log;
-import java.awt.geom.AffineTransform;
-import java.awt.geom.Arc2D;
-import java.awt.geom.Area;
-import java.awt.geom.Ellipse2D;
-import java.awt.geom.GeneralPath;
-import java.awt.geom.Path2D;
-import java.awt.geom.PathIterator;
-import java.awt.geom.Point2D;
-import java.awt.geom.Rectangle2D;
-import java.awt.geom.RoundRectangle2D;
-import java.util.ArrayList;
import java.util.List;
-import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
-import org.robolectric.annotation.RealObject;
+import org.robolectric.shadows.ShadowPath.Picker;
-/**
- * The shadow only supports straight-line paths.
- */
+/** Base class for {@link ShadowPath} classes. */
@SuppressWarnings({"UnusedDeclaration"})
-@Implements(Path.class)
-public class ShadowPath {
- private static final String TAG = ShadowPath.class.getSimpleName();
- private static final float EPSILON = 1e-4f;
-
- @RealObject private Path realObject;
-
- private List<Point> points = new ArrayList<>();
-
- private float mLastX = 0;
- private float mLastY = 0;
- private Path2D mPath = new Path2D.Double();
- private boolean mCachedIsEmpty = true;
- private Path.FillType mFillType = Path.FillType.WINDING;
- protected boolean isSimplePath;
-
- @Implementation
- protected void __constructor__(Path path) {
- ShadowPath shadowPath = extract(path);
- points = new ArrayList<>(shadowPath.getPoints());
- mPath.append(shadowPath.mPath, /*connect=*/ false);
- mFillType = shadowPath.getFillType();
- }
-
- Path2D getJavaShape() {
- return mPath;
- }
-
- @Implementation
- protected void moveTo(float x, float y) {
- mPath.moveTo(mLastX = x, mLastY = y);
-
- // Legacy recording behavior
- Point p = new Point(x, y, MOVE_TO);
- points.add(p);
- }
-
- @Implementation
- protected void lineTo(float x, float y) {
- if (!hasPoints()) {
- mPath.moveTo(mLastX = 0, mLastY = 0);
- }
- mPath.lineTo(mLastX = x, mLastY = y);
-
- // Legacy recording behavior
- Point point = new Point(x, y, LINE_TO);
- points.add(point);
- }
-
- @Implementation
- protected void quadTo(float x1, float y1, float x2, float y2) {
- isSimplePath = false;
- if (!hasPoints()) {
- moveTo(0, 0);
- }
- mPath.quadTo(x1, y1, mLastX = x2, mLastY = y2);
- }
-
- @Implementation
- protected void cubicTo(float x1, float y1, float x2, float y2, float x3, float y3) {
- if (!hasPoints()) {
- mPath.moveTo(0, 0);
- }
- mPath.curveTo(x1, y1, x2, y2, mLastX = x3, mLastY = y3);
- }
-
- private boolean hasPoints() {
- return !mPath.getPathIterator(null).isDone();
- }
-
- @Implementation
- protected void reset() {
- mPath.reset();
- mLastX = 0;
- mLastY = 0;
-
- // Legacy recording behavior
- points.clear();
- }
-
- @Implementation(minSdk = LOLLIPOP)
- protected float[] approximate(float acceptableError) {
- PathIterator iterator = mPath.getPathIterator(null, acceptableError);
-
- float segment[] = new float[6];
- float totalLength = 0;
- ArrayList<Point2D.Float> points = new ArrayList<Point2D.Float>();
- Point2D.Float previousPoint = null;
- while (!iterator.isDone()) {
- int type = iterator.currentSegment(segment);
- Point2D.Float currentPoint = new Point2D.Float(segment[0], segment[1]);
- // MoveTo shouldn't affect the length
- if (previousPoint != null && type != PathIterator.SEG_MOVETO) {
- totalLength += (float) currentPoint.distance(previousPoint);
- }
- previousPoint = currentPoint;
- points.add(currentPoint);
- iterator.next();
- }
-
- int nPoints = points.size();
- float[] result = new float[nPoints * 3];
- previousPoint = null;
- // Distance that we've covered so far. Used to calculate the fraction of the path that
- // we've covered up to this point.
- float walkedDistance = .0f;
- for (int i = 0; i < nPoints; i++) {
- Point2D.Float point = points.get(i);
- float distance = previousPoint != null ? (float) previousPoint.distance(point) : .0f;
- walkedDistance += distance;
- result[i * 3] = walkedDistance / totalLength;
- result[i * 3 + 1] = point.x;
- result[i * 3 + 2] = point.y;
-
- previousPoint = point;
- }
-
- return result;
- }
+@Implements(value = Path.class, shadowPicker = Picker.class)
+public abstract class ShadowPath {
/**
* @return all the points that have been added to the {@code Path}
*/
- public List<Point> getPoints() {
- return points;
- }
+ public abstract List<Point> getPoints();
+
+ /**
+ * Fills the given {@link RectF} with the path bounds.
+ *
+ * @param bounds the RectF to be filled.
+ */
+ public abstract void fillBounds(RectF bounds);
public static class Point {
private final float x;
@@ -215,400 +80,10 @@ public class ShadowPath {
}
}
- @Implementation
- protected void rewind() {
- // call out to reset since there's nothing to optimize in
- // terms of data structs.
- reset();
- }
-
- @Implementation
- protected void set(Path src) {
- mPath.reset();
-
- ShadowPath shadowSrc = extract(src);
- setFillType(shadowSrc.mFillType);
- mPath.append(shadowSrc.mPath, false /*connect*/);
- }
-
- @Implementation(minSdk = KITKAT)
- protected boolean op(Path path1, Path path2, Path.Op op) {
- Log.w(TAG, "android.graphics.Path#op() not supported yet.");
- return false;
- }
-
- @Implementation(minSdk = LOLLIPOP)
- protected boolean isConvex() {
- Log.w(TAG, "android.graphics.Path#isConvex() not supported yet.");
- return true;
- }
-
- @Implementation
- protected Path.FillType getFillType() {
- return mFillType;
- }
-
- @Implementation
- protected void setFillType(Path.FillType fillType) {
- mFillType = fillType;
- mPath.setWindingRule(getWindingRule(fillType));
- }
-
- /**
- * Returns the Java2D winding rules matching a given Android {@link FillType}.
- *
- * @param type the android fill type
- * @return the matching java2d winding rule.
- */
- private static int getWindingRule(Path.FillType type) {
- switch (type) {
- case WINDING:
- case INVERSE_WINDING:
- return GeneralPath.WIND_NON_ZERO;
- case EVEN_ODD:
- case INVERSE_EVEN_ODD:
- return GeneralPath.WIND_EVEN_ODD;
-
- default:
- assert false;
- return GeneralPath.WIND_NON_ZERO;
- }
- }
-
- @Implementation
- protected boolean isInverseFillType() {
- throw new UnsupportedOperationException("isInverseFillType");
- }
-
- @Implementation
- protected void toggleInverseFillType() {
- throw new UnsupportedOperationException("toggleInverseFillType");
- }
-
- @Implementation
- protected boolean isEmpty() {
- if (!mCachedIsEmpty) {
- return false;
- }
-
- float[] coords = new float[6];
- mCachedIsEmpty = Boolean.TRUE;
- for (PathIterator it = mPath.getPathIterator(null); !it.isDone(); it.next()) {
- // int type = it.currentSegment(coords);
- // if (type != PathIterator.SEG_MOVETO) {
- // Once we know that the path is not empty, we do not need to check again unless
- // Path#reset is called.
- mCachedIsEmpty = false;
- return false;
- // }
- }
-
- return true;
- }
-
- @Implementation
- protected boolean isRect(RectF rect) {
- // create an Area that can test if the path is a rect
- Area area = new Area(mPath);
- if (area.isRectangular()) {
- if (rect != null) {
- fillBounds(rect);
- }
-
- return true;
- }
-
- return false;
- }
-
- @Implementation
- protected void computeBounds(RectF bounds, boolean exact) {
- fillBounds(bounds);
- }
-
- @Implementation
- protected void incReserve(int extraPtCount) {
- throw new UnsupportedOperationException("incReserve");
- }
-
- @Implementation
- protected void rMoveTo(float dx, float dy) {
- dx += mLastX;
- dy += mLastY;
- mPath.moveTo(mLastX = dx, mLastY = dy);
- }
-
- @Implementation
- protected void rLineTo(float dx, float dy) {
- if (!hasPoints()) {
- mPath.moveTo(mLastX = 0, mLastY = 0);
- }
-
- if (Math.abs(dx) < EPSILON && Math.abs(dy) < EPSILON) {
- // The delta is so small that this shouldn't generate a line
- return;
- }
-
- dx += mLastX;
- dy += mLastY;
- mPath.lineTo(mLastX = dx, mLastY = dy);
- }
-
- @Implementation
- protected void rQuadTo(float dx1, float dy1, float dx2, float dy2) {
- if (!hasPoints()) {
- mPath.moveTo(mLastX = 0, mLastY = 0);
- }
- dx1 += mLastX;
- dy1 += mLastY;
- dx2 += mLastX;
- dy2 += mLastY;
- mPath.quadTo(dx1, dy1, mLastX = dx2, mLastY = dy2);
- }
-
- @Implementation
- protected void rCubicTo(float x1, float y1, float x2, float y2, float x3, float y3) {
- if (!hasPoints()) {
- mPath.moveTo(mLastX = 0, mLastY = 0);
- }
- x1 += mLastX;
- y1 += mLastY;
- x2 += mLastX;
- y2 += mLastY;
- x3 += mLastX;
- y3 += mLastY;
- mPath.curveTo(x1, y1, x2, y2, mLastX = x3, mLastY = y3);
- }
-
- @Implementation
- protected void arcTo(RectF oval, float startAngle, float sweepAngle) {
- arcTo(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle, false);
- }
-
- @Implementation
- protected void arcTo(RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo) {
- arcTo(oval.left, oval.top, oval.right, oval.bottom, startAngle, sweepAngle, forceMoveTo);
- }
-
- @Implementation(minSdk = LOLLIPOP)
- protected void arcTo(
- float left,
- float top,
- float right,
- float bottom,
- float startAngle,
- float sweepAngle,
- boolean forceMoveTo) {
- isSimplePath = false;
- Arc2D arc =
- new Arc2D.Float(
- left, top, right - left, bottom - top, -startAngle, -sweepAngle, Arc2D.OPEN);
- mPath.append(arc, true /*connect*/);
- if (hasPoints()) {
- resetLastPointFromPath();
- }
- }
-
- @Implementation
- protected void close() {
- if (!hasPoints()) {
- mPath.moveTo(mLastX = 0, mLastY = 0);
- }
- mPath.closePath();
- }
-
- @Implementation
- protected void addRect(RectF rect, Direction dir) {
- addRect(rect.left, rect.top, rect.right, rect.bottom, dir);
- }
-
- @Implementation
- protected void addRect(float left, float top, float right, float bottom, Path.Direction dir) {
- moveTo(left, top);
-
- switch (dir) {
- case CW:
- lineTo(right, top);
- lineTo(right, bottom);
- lineTo(left, bottom);
- break;
- case CCW:
- lineTo(left, bottom);
- lineTo(right, bottom);
- lineTo(right, top);
- break;
+ /** Shadow picker for {@link Path}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(ShadowLegacyPath.class, ShadowNativePath.class);
}
-
- close();
-
- resetLastPointFromPath();
- }
-
- @Implementation(minSdk = LOLLIPOP)
- protected void addOval(float left, float top, float right, float bottom, Path.Direction dir) {
- mPath.append(new Ellipse2D.Float(left, top, right - left, bottom - top), false);
- }
-
- @Implementation
- protected void addCircle(float x, float y, float radius, Path.Direction dir) {
- mPath.append(new Ellipse2D.Float(x - radius, y - radius, radius * 2, radius * 2), false);
- }
-
- @Implementation(minSdk = LOLLIPOP)
- protected void addArc(
- float left, float top, float right, float bottom, float startAngle, float sweepAngle) {
- mPath.append(
- new Arc2D.Float(
- left, top, right - left, bottom - top, -startAngle, -sweepAngle, Arc2D.OPEN),
- false);
- }
-
- @Implementation(minSdk = JELLY_BEAN)
- 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)
- protected void addRoundRect(RectF rect, float[] radii, Direction dir) {
- if (rect == null) {
- throw new NullPointerException("need rect parameter");
- }
- addRoundRect(rect.left, rect.top, rect.right, rect.bottom, radii, dir);
- }
-
- @Implementation(minSdk = LOLLIPOP)
- protected void addRoundRect(
- float left, float top, float right, float bottom, float rx, float ry, Path.Direction dir) {
- mPath.append(
- new RoundRectangle2D.Float(left, top, right - left, bottom - top, rx * 2, ry * 2), false);
- }
-
- @Implementation(minSdk = LOLLIPOP)
- protected void addRoundRect(
- float left, float top, float right, float bottom, float[] radii, Path.Direction dir) {
- if (radii.length < 8) {
- throw new ArrayIndexOutOfBoundsException("radii[] needs 8 values");
- }
- isSimplePath = false;
-
- float[] cornerDimensions = new float[radii.length];
- for (int i = 0; i < radii.length; i++) {
- cornerDimensions[i] = 2 * radii[i];
- }
- mPath.append(
- new RoundRectangle(left, top, right - left, bottom - top, cornerDimensions), false);
- }
-
- @Implementation
- protected void addPath(Path src, float dx, float dy) {
- isSimplePath = false;
- ShadowPath.addPath(realObject, src, AffineTransform.getTranslateInstance(dx, dy));
- }
-
- @Implementation
- protected void addPath(Path src) {
- isSimplePath = false;
- ShadowPath.addPath(realObject, src, null);
- }
-
- @Implementation
- protected void addPath(Path src, Matrix matrix) {
- if (matrix == null) {
- return;
- }
- ShadowPath shadowSrc = extract(src);
- if (!shadowSrc.isSimplePath) isSimplePath = false;
-
- ShadowMatrix shadowMatrix = extract(matrix);
- ShadowPath.addPath(realObject, src, shadowMatrix.getAffineTransform());
- }
-
- private static void addPath(Path destPath, Path srcPath, AffineTransform transform) {
- if (destPath == null) {
- return;
- }
-
- if (srcPath == null) {
- return;
- }
-
- ShadowPath shadowDestPath = extract(destPath);
- ShadowPath shadowSrcPath = extract(srcPath);
- if (transform != null) {
- shadowDestPath.mPath.append(shadowSrcPath.mPath.getPathIterator(transform), false);
- } else {
- shadowDestPath.mPath.append(shadowSrcPath.mPath, false);
- }
- }
-
- @Implementation
- protected void offset(float dx, float dy, Path dst) {
- if (dst != null) {
- dst.set(realObject);
- } else {
- dst = realObject;
- }
- dst.offset(dx, dy);
- }
-
- @Implementation
- protected void offset(float dx, float dy) {
- GeneralPath newPath = new GeneralPath();
-
- PathIterator iterator = mPath.getPathIterator(new AffineTransform(0, 0, dx, 0, 0, dy));
-
- newPath.append(iterator, false /*connect*/);
- mPath = newPath;
- }
-
- @Implementation
- protected void setLastPoint(float dx, float dy) {
- mLastX = dx;
- mLastY = dy;
- }
-
- @Implementation
- protected void transform(Matrix matrix, Path dst) {
- ShadowMatrix shadowMatrix = extract(matrix);
-
- if (shadowMatrix.hasPerspective()) {
- Log.w(TAG, "android.graphics.Path#transform() only supports affine transformations.");
- }
-
- GeneralPath newPath = new GeneralPath();
-
- PathIterator iterator = mPath.getPathIterator(shadowMatrix.getAffineTransform());
- newPath.append(iterator, false /*connect*/);
-
- if (dst != null) {
- ShadowPath shadowPath = extract(dst);
- shadowPath.mPath = newPath;
- } else {
- mPath = newPath;
- }
- }
-
- @Implementation
- protected void transform(Matrix matrix) {
- transform(matrix, null);
- }
-
- /**
- * Fills the given {@link RectF} with the path bounds.
- *
- * @param bounds the RectF to be filled.
- */
- public void fillBounds(RectF bounds) {
- Rectangle2D rect = mPath.getBounds2D();
- bounds.left = (float) rect.getMinX();
- bounds.right = (float) rect.getMaxX();
- bounds.top = (float) rect.getMinY();
- bounds.bottom = (float) rect.getMaxY();
- }
-
- private void resetLastPointFromPath() {
- Point2D last = mPath.getCurrentPoint();
- mLastX = (float) last.getX();
- mLastY = (float) last.getY();
}
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPathMeasure.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPathMeasure.java
index 5bc2b9732..408778024 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPathMeasure.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPathMeasure.java
@@ -15,7 +15,7 @@ public class ShadowPathMeasure {
@Implementation
protected void __constructor__(Path path, boolean forceClosed) {
if (path != null) {
- ShadowPath shadowPath = (ShadowPath) Shadow.extract(path);
+ ShadowLegacyPath shadowPath = Shadow.extract(path);
mOriginalPathIterator =
new CachedPathIteratorFactory(shadowPath.getJavaShape().getPathIterator(null));
}
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 df4e15b58..50f8adf62 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedLooper.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPausedLooper.java
@@ -430,14 +430,10 @@ public final class ShadowPausedLooper extends ShadowLooper {
setLooperExecutor(this);
isPaused = true;
runLatch.countDown();
- while (true) {
+ while (isPaused) {
try {
Runnable runnable = executionQueue.take();
runnable.run();
- if (runnable instanceof UnPauseRunnable) {
- setLooperExecutor(new HandlerExecutor(new Handler(realLooper)));
- return;
- }
} catch (InterruptedException e) {
// ignore
}
@@ -448,6 +444,7 @@ public final class ShadowPausedLooper extends ShadowLooper {
private class UnPauseRunnable extends ControlRunnable {
@Override
public void run() {
+ setLooperExecutor(new HandlerExecutor(new Handler(realLooper)));
isPaused = false;
runLatch.countDown();
}
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 232132945..9411ff1c8 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPhoneWindow.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPhoneWindow.java
@@ -1,11 +1,13 @@
package org.robolectric.shadows;
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.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;
@@ -17,8 +19,8 @@ import org.robolectric.util.reflector.ForType;
@Implements(className = "com.android.internal.policy.PhoneWindow", isInAndroidSdk = false,
minSdk = M, looseSignatures = true)
public class ShadowPhoneWindow extends ShadowWindow {
- @SuppressWarnings("UnusedDeclaration")
protected @RealObject Window realWindow;
+ protected boolean decorFitsSystemWindows = true;
@Implementation(minSdk = M)
public void setTitle(CharSequence title) {
@@ -37,11 +39,29 @@ public class ShadowPhoneWindow extends ShadowWindow {
return Gravity.CENTER | Gravity.BOTTOM;
}
+ @Implementation(minSdk = R)
+ protected void setDecorFitsSystemWindows(boolean decorFitsSystemWindows) {
+ this.decorFitsSystemWindows = decorFitsSystemWindows;
+ reflector(DirectPhoneWindowReflector.class, realWindow)
+ .setDecorFitsSystemWindows(decorFitsSystemWindows);
+ }
+
+ /**
+ * Returns true with the last value passed to {@link #setDecorFitsSystemWindows(boolean)}, or the
+ * default value (true).
+ */
+ @RequiresApi(R)
+ public boolean getDecorFitsSystemWindows() {
+ return decorFitsSystemWindows;
+ }
+
@ForType(className = "com.android.internal.policy.PhoneWindow", direct = true)
interface DirectPhoneWindowReflector {
void setTitle(CharSequence title);
void setBackgroundDrawable(Drawable drawable);
+
+ void setDecorFitsSystemWindows(boolean decorFitsSystemWindows);
}
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPosix.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPosix.java
index 6cf1dbf86..c85cf8f9c 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPosix.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPosix.java
@@ -11,10 +11,14 @@ import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.util.ReflectionHelpers;
-@Implements(className = "libcore.io.Posix", maxSdk = Build.VERSION_CODES.N_MR1, isInAndroidSdk = false)
+/** Shadow for {@link libcore.io.Posix} */
+@Implements(
+ className = "libcore.io.Posix",
+ maxSdk = Build.VERSION_CODES.N_MR1,
+ isInAndroidSdk = false)
public class ShadowPosix {
@Implementation
- public static void mkdir(String path, int mode) throws ErrnoException {
+ public void mkdir(String path, int mode) throws ErrnoException {
new File(path).mkdirs();
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPreference.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPreference.java
index 93f90b2a2..f3e735522 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPreference.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowPreference.java
@@ -6,7 +6,6 @@ import android.preference.Preference;
import android.preference.PreferenceManager;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
-import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;
@Implements(Preference.class)
@@ -23,8 +22,6 @@ public class ShadowPreference {
@ForType(Preference.class)
interface PreferenceReflector {
-
- @Direct
void onAttachedToHierarchy(PreferenceManager preferenceManager);
}
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRecordingCanvas.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRecordingCanvas.java
index 1048d56d5..2f91d2d52 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRecordingCanvas.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowRecordingCanvas.java
@@ -7,7 +7,7 @@ import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
@Implements(value = RecordingCanvas.class, isInAndroidSdk = false, minSdk = Q)
-public class ShadowRecordingCanvas extends ShadowCanvas {
+public class ShadowRecordingCanvas extends ShadowLegacyCanvas {
@Implementation
protected static long nCreateDisplayListCanvas(long node, int width, int height) {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowScaleGestureDetector.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowScaleGestureDetector.java
index c7f920551..ca5032f55 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowScaleGestureDetector.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowScaleGestureDetector.java
@@ -5,27 +5,32 @@ import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.ReflectorObject;
+import org.robolectric.util.reflector.Direct;
+import org.robolectric.util.reflector.ForType;
@SuppressWarnings({"UnusedDeclaration"})
@Implements(ScaleGestureDetector.class)
public class ShadowScaleGestureDetector {
+ @ReflectorObject ScaleGestureDetectorReflector scaleGestureDetectorReflector;
private MotionEvent onTouchEventMotionEvent;
private ScaleGestureDetector.OnScaleGestureListener listener;
- private float scaleFactor = 1;
- private float focusX;
- private float focusY;
+ private Float scaleFactor;
+ private Float focusX;
+ private Float focusY;
@Implementation
protected void __constructor__(
Context context, ScaleGestureDetector.OnScaleGestureListener listener) {
+ scaleGestureDetectorReflector.__constructor__(context, listener);
this.listener = listener;
}
@Implementation
protected boolean onTouchEvent(MotionEvent event) {
onTouchEventMotionEvent = event;
- return true;
+ return scaleGestureDetectorReflector.onTouchEvent(event);
}
public MotionEvent getOnTouchEventMotionEvent() {
@@ -34,9 +39,9 @@ public class ShadowScaleGestureDetector {
public void reset() {
onTouchEventMotionEvent = null;
- scaleFactor = 1;
- focusX = 0;
- focusY = 0;
+ scaleFactor = null;
+ focusX = null;
+ focusY = null;
}
public ScaleGestureDetector.OnScaleGestureListener getListener() {
@@ -49,7 +54,7 @@ public class ShadowScaleGestureDetector {
@Implementation
protected float getScaleFactor() {
- return scaleFactor;
+ return scaleFactor != null ? scaleFactor : scaleGestureDetectorReflector.getScaleFactor();
}
public void setFocusXY(float focusX, float focusY) {
@@ -59,11 +64,29 @@ public class ShadowScaleGestureDetector {
@Implementation
protected float getFocusX() {
- return focusX;
+ return focusX != null ? focusX : scaleGestureDetectorReflector.getFocusX();
}
@Implementation
protected float getFocusY() {
- return focusY;
+ return focusY != null ? focusY : scaleGestureDetectorReflector.getFocusY();
+ }
+
+ @ForType(ScaleGestureDetector.class)
+ private interface ScaleGestureDetectorReflector {
+ @Direct
+ void __constructor__(Context context, ScaleGestureDetector.OnScaleGestureListener listener);
+
+ @Direct
+ boolean onTouchEvent(MotionEvent event);
+
+ @Direct
+ float getScaleFactor();
+
+ @Direct
+ float getFocusX();
+
+ @Direct
+ float getFocusY();
}
}
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSearchManager.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSearchManager.java
index 204dc4cb9..5c985a5c2 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSearchManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSearchManager.java
@@ -1,17 +1,49 @@
package org.robolectric.shadows;
+import static android.os.Build.VERSION_CODES.M;
+import static org.robolectric.util.reflector.Reflector.reflector;
+
+import android.annotation.SystemApi;
import android.app.SearchManager;
import android.app.SearchableInfo;
import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.RealObject;
+import org.robolectric.util.reflector.Accessor;
+import org.robolectric.util.reflector.ForType;
@Implements(SearchManager.class)
public class ShadowSearchManager {
+ @RealObject private SearchManager searchManager;
+
@Implementation
protected SearchableInfo getSearchableInfo(ComponentName componentName) {
// Prevent Robolectric from calling through
return null;
}
+
+ @Implementation(minSdk = M)
+ @SystemApi
+ protected void launchAssist(Bundle bundle) {
+ Intent intent = new Intent(Intent.ACTION_ASSIST);
+ intent.putExtras(bundle);
+ getContext().sendBroadcast(intent);
+ }
+
+ private Context getContext() {
+ return reflector(ReflectorSearchManager.class, searchManager).getContext();
+ }
+
+ /** Reflector interface for {@link SearchManager}'s internals. */
+ @ForType(SearchManager.class)
+ private interface ReflectorSearchManager {
+
+ @Accessor("mContext")
+ Context getContext();
+ }
}
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 5aa44afd2..c973fe3c7 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSensorManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSensorManager.java
@@ -9,8 +9,10 @@ import android.hardware.Sensor;
import android.hardware.SensorDirectChannel;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
+import android.hardware.SensorEventListener2;
import android.hardware.SensorManager;
import android.os.Handler;
+import android.os.Looper;
import android.os.MemoryFile;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
@@ -146,6 +148,27 @@ public class ShadowSensorManager {
}
}
+ @Implementation(minSdk = KITKAT)
+ 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.
+ new Handler(Looper.getMainLooper())
+ .post(
+ () -> {
+ // Go through each sensor that the listener is registered for, and call
+ // onFlushCompleted on each listener registered for that sensor.
+ for (Sensor sensor : listeners.get(listener)) {
+ for (SensorEventListener registeredListener : getListeners()) {
+ if ((registeredListener instanceof SensorEventListener2)
+ && listeners.containsEntry(registeredListener, sensor)) {
+ ((SensorEventListener2) registeredListener).onFlushCompleted(sensor);
+ }
+ }
+ }
+ });
+ return listeners.containsKey(listener);
+ }
+
public SensorEvent createSensorEvent() {
return ReflectionHelpers.callConstructor(SensorEvent.class);
}
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 43a758f48..552d3101f 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSettings.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSettings.java
@@ -9,6 +9,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.provider.Settings.Secure.LOCATION_MODE_OFF;
+import static org.robolectric.util.reflector.Reflector.reflector;
import android.content.ContentResolver;
import android.content.Context;
@@ -32,8 +33,8 @@ import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.Resetter;
-import org.robolectric.shadow.api.Shadow;
-import org.robolectric.util.ReflectionHelpers.ClassParameter;
+import org.robolectric.util.reflector.ForType;
+import org.robolectric.util.reflector.Static;
@SuppressWarnings({"UnusedDeclaration"})
@Implements(Settings.class)
@@ -267,11 +268,7 @@ public class ShadowSettings {
&& RuntimeEnvironment.getApiLevel() >= KITKAT
&& RuntimeEnvironment.getApiLevel() < P) {
// Map from to underlying location provider storage API to location mode
- return Shadow.directlyOn(
- Settings.Secure.class,
- "getLocationModeForUser",
- ClassParameter.from(ContentResolver.class, cr),
- ClassParameter.from(int.class, 0));
+ return reflector(SettingsSecureReflector.class).getLocationModeForUser(cr, 0);
}
return get(Integer.class, name).orElseThrow(() -> new SettingNotFoundException(name));
@@ -283,11 +280,7 @@ public class ShadowSettings {
&& RuntimeEnvironment.getApiLevel() >= KITKAT
&& RuntimeEnvironment.getApiLevel() < P) {
// Map from to underlying location provider storage API to location mode
- return Shadow.directlyOn(
- Settings.Secure.class,
- "getLocationModeForUser",
- ClassParameter.from(ContentResolver.class, cr),
- ClassParameter.from(int.class, 0));
+ return reflector(SettingsSecureReflector.class).getLocationModeForUser(cr, 0);
}
return get(Integer.class, name).orElse(def);
@@ -576,4 +569,10 @@ public class ShadowSettings {
public static void reset() {
canDrawOverlays = false;
}
+
+ @ForType(Settings.Secure.class)
+ interface SettingsSecureReflector {
+ @Static
+ int getLocationModeForUser(ContentResolver cr, int userId);
+ }
}
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 ff81de5db..529aaa405 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSubscriptionManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSubscriptionManager.java
@@ -6,6 +6,8 @@ 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;
+import static android.os.Build.VERSION_CODES.TIRAMISU;
import android.os.Build.VERSION;
import android.telephony.SubscriptionInfo;
@@ -32,11 +34,20 @@ public class ShadowSubscriptionManager {
public static final int INVALID_PHONE_INDEX =
ReflectionHelpers.getStaticField(SubscriptionManager.class, "INVALID_PHONE_INDEX");
+ private static int activeDataSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
private static int defaultSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
private static int defaultDataSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
private static int defaultSmsSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
private static int defaultVoiceSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ private final Map<Integer, String> phoneNumberMap = new HashMap<>();
+
+ /** Returns value set with {@link #setActiveDataSubscriptionId(int)}. */
+ @Implementation(minSdk = R)
+ protected static int getActiveDataSubscriptionId() {
+ return activeDataSubscriptionId;
+ }
+
/** Returns value set with {@link #setDefaultSubscriptionId(int)}. */
@Implementation(minSdk = N)
protected static int getDefaultSubscriptionId() {
@@ -85,6 +96,11 @@ public class ShadowSubscriptionManager {
return defaultDataSubscriptionId;
}
+ /** Sets the value that will be returned by {@link #getActiveDataSubscriptionId()}. */
+ public static void setActiveDataSubscriptionId(int activeDataSubscriptionId) {
+ ShadowSubscriptionManager.activeDataSubscriptionId = activeDataSubscriptionId;
+ }
+
/** Sets the value that will be returned by {@link #getDefaultSubscriptionId()}. */
public static void setDefaultSubscriptionId(int defaultSubscriptionId) {
ShadowSubscriptionManager.defaultSubscriptionId = defaultSubscriptionId;
@@ -109,8 +125,8 @@ public class ShadowSubscriptionManager {
private static Map<Integer, Integer> phoneIds = new HashMap<>();
/**
- * Cache of {@link SubscriptionInfo} used by {@link #getActiveSubscriptionInfoList}.
- * Managed by {@link #setActiveSubscriptionInfoList}.
+ * Cache of {@link SubscriptionInfo} used by {@link #getActiveSubscriptionInfoList}. Managed by
+ * {@link #setActiveSubscriptionInfoList}.
*/
private List<SubscriptionInfo> subscriptionList = new ArrayList<>();
/**
@@ -212,6 +228,7 @@ public class ShadowSubscriptionManager {
/**
* Sets the active list of {@link SubscriptionInfo}. This call internally triggers {@link
* OnSubscriptionsChangedListener#onSubscriptionsChanged()} to all the listeners.
+ *
* @param list - The subscription info list, can be null.
*/
public void setActiveSubscriptionInfoList(List<SubscriptionInfo> list) {
@@ -299,7 +316,7 @@ public class ShadowSubscriptionManager {
}
/** Clears the local cache of roaming subscription Ids used by {@link #isNetworkRoaming}. */
- public void clearNetworkRoamingStatus(){
+ public void clearNetworkRoamingStatus() {
roamingSimSubscriptionIds.clear();
}
@@ -377,8 +394,25 @@ public class ShadowSubscriptionManager {
}
}
+ /**
+ * Returns the phone number for the given {@code subscriptionId}, or an empty string if not
+ * available.
+ *
+ * <p>The phone number can be set by {@link #setPhoneNumber(int, String)}
+ */
+ @Implementation(minSdk = TIRAMISU)
+ protected String getPhoneNumber(int subscriptionId) {
+ return phoneNumberMap.getOrDefault(subscriptionId, "");
+ }
+
+ /** Sets the phone number returned by {@link #getPhoneNumber(int)}. */
+ public void setPhoneNumber(int subscriptionId, String phoneNumber) {
+ phoneNumberMap.put(subscriptionId, phoneNumber);
+ }
+
@Resetter
public static void reset() {
+ activeDataSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
defaultDataSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
defaultSmsSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
defaultVoiceSubscriptionId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
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 840c626fb..cce1990a0 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemVibrator.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSystemVibrator.java
@@ -162,30 +162,30 @@ public class ShadowSystemVibrator extends ShadowVibrator {
private void recordVibratePredefined(long milliseconds, int effectId) {
vibrating = true;
- this.effectId = effectId;
- this.milliseconds = milliseconds;
+ ShadowVibrator.effectId = effectId;
+ ShadowVibrator.milliseconds = milliseconds;
handler.removeCallbacks(stopVibratingRunnable);
- handler.postDelayed(stopVibratingRunnable, this.milliseconds);
+ handler.postDelayed(stopVibratingRunnable, ShadowVibrator.milliseconds);
}
private void recordVibrate(long milliseconds) {
vibrating = true;
- this.milliseconds = milliseconds;
+ ShadowVibrator.milliseconds = milliseconds;
handler.removeCallbacks(stopVibratingRunnable);
- handler.postDelayed(stopVibratingRunnable, this.milliseconds);
+ handler.postDelayed(stopVibratingRunnable, ShadowVibrator.milliseconds);
}
protected void recordVibratePattern(long[] pattern, int repeat) {
vibrating = true;
- this.pattern = pattern;
- this.repeat = repeat;
+ ShadowVibrator.pattern = pattern;
+ ShadowVibrator.repeat = repeat;
handler.removeCallbacks(stopVibratingRunnable);
if (repeat < 0) {
long endDelayMillis = 0;
for (long t : pattern) {
endDelayMillis += t;
}
- this.milliseconds = endDelayMillis;
+ ShadowVibrator.milliseconds = endDelayMillis;
handler.postDelayed(stopVibratingRunnable, endDelayMillis);
}
}
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 e7499eafc..dc456f3f6 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTelephonyManager.java
@@ -131,6 +131,7 @@ public class ShadowTelephonyManager {
private int carrierIdFromSimMccMnc;
private String subscriberId;
private /*UiccSlotInfo[]*/ Object uiccSlotInfos;
+ private /*UiccCardInfo[]*/ Object uiccCardsInfo;
private String visualVoicemailPackageName = null;
private SignalStrength signalStrength;
private boolean dataEnabled = false;
@@ -163,7 +164,8 @@ public class ShadowTelephonyManager {
callComposerStatus = 0;
}
- public static void setCallComposerStatus(int callComposerStatus) {
+ @Implementation(minSdk = S)
+ protected void setCallComposerStatus(int callComposerStatus) {
ShadowTelephonyManager.callComposerStatus = callComposerStatus;
}
@@ -487,6 +489,18 @@ public class ShadowTelephonyManager {
return uiccSlotInfos;
}
+ /** Sets the UICC cards information returned by {@link #getUiccCardsInfo()}. */
+ public void setUiccCardsInfo(/*UiccCardsInfo[]*/ Object uiccCardsInfo) {
+ this.uiccCardsInfo = uiccCardsInfo;
+ }
+
+ /** Returns the UICC cards information set by {@link #setUiccCardsInfo}. */
+ @Implementation(minSdk = Q)
+ @HiddenApi
+ protected /*UiccSlotInfo[]*/ Object getUiccCardsInfo() {
+ return uiccCardsInfo;
+ }
+
/** Clears {@code slotIndex} to state mapping and resets to default state. */
public void resetSimStates() {
simStates.clear();
@@ -898,6 +912,7 @@ public class ShadowTelephonyManager {
*/
@Implementation(minSdk = O)
protected TelephonyManager createForPhoneAccountHandle(PhoneAccountHandle handle) {
+ checkReadPhoneStatePermission();
return phoneAccountToTelephonyManagers.get(handle);
}
@@ -1075,6 +1090,9 @@ public class ShadowTelephonyManager {
*/
@Implementation(minSdk = Build.VERSION_CODES.Q)
protected boolean isEmergencyNumber(String number) {
+ if (ShadowServiceManager.getService(Context.TELEPHONY_SERVICE) == null) {
+ throw new IllegalStateException("telephony service is null.");
+ }
if (number == null) {
return false;
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTypeface.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTypeface.java
index 58abb5844..5db6c9d60 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTypeface.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowTypeface.java
@@ -1,262 +1,21 @@
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_MR1;
-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.R;
-import static android.os.Build.VERSION_CODES.S;
-import static org.robolectric.RuntimeEnvironment.getApiLevel;
-import static org.robolectric.Shadows.shadowOf;
-
-import android.annotation.SuppressLint;
-import android.content.res.AssetManager;
-import android.graphics.FontFamily;
import android.graphics.Typeface;
-import android.util.ArrayMap;
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.atomic.AtomicLong;
-import org.robolectric.RuntimeEnvironment;
-import org.robolectric.annotation.HiddenApi;
-import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
-import org.robolectric.annotation.RealObject;
-import org.robolectric.annotation.Resetter;
-import org.robolectric.res.Fs;
-import org.robolectric.shadow.api.Shadow;
-import org.robolectric.util.ReflectionHelpers;
-import org.robolectric.util.ReflectionHelpers.ClassParameter;
-
-@Implements(value = Typeface.class, looseSignatures = true)
-@SuppressLint("NewApi")
-public class ShadowTypeface {
- private static final Map<Long, FontDesc> FONTS = Collections.synchronizedMap(new HashMap<>());
- private static final AtomicLong nextFontId = new AtomicLong(1);
- private FontDesc description;
-
- @HiddenApi
- @Implementation(maxSdk = KITKAT)
- protected void __constructor__(int fontId) {
- description = findById((long) fontId);
- }
-
- @HiddenApi
- @Implementation(minSdk = LOLLIPOP)
- protected void __constructor__(long fontId) {
- description = findById(fontId);
- }
-
- @Implementation
- protected static void __staticInitializer__() {
- Shadow.directInitialize(Typeface.class);
- if (RuntimeEnvironment.getApiLevel() > R) {
- Typeface.loadPreinstalledSystemFontMap();
- }
- }
-
- @Implementation(minSdk = P)
- protected static Typeface create(Typeface family, int weight, boolean italic) {
- if (family == null) {
- return createUnderlyingTypeface(null, weight);
- } else {
- ShadowTypeface shadowTypeface = Shadow.extract(family);
- return createUnderlyingTypeface(shadowTypeface.getFontDescription().getFamilyName(), weight);
- }
- }
-
- @Implementation
- protected static Typeface create(String familyName, int style) {
- return createUnderlyingTypeface(familyName, style);
- }
-
- @Implementation
- protected static Typeface create(Typeface family, int style) {
- if (family == null) {
- return createUnderlyingTypeface(null, style);
- } else {
- ShadowTypeface shadowTypeface = Shadow.extract(family);
- return createUnderlyingTypeface(shadowTypeface.getFontDescription().getFamilyName(), style);
- }
- }
+import org.robolectric.shadows.ShadowTypeface.Picker;
- @Implementation
- protected static Typeface createFromAsset(AssetManager mgr, String path) {
- ShadowAssetManager shadowAssetManager = Shadow.extract(mgr);
- Collection<Path> assetDirs = shadowAssetManager.getAllAssetDirs();
- for (Path assetDir : assetDirs) {
- Path assetFile = assetDir.resolve(path);
- if (Files.exists(assetFile)) {
- return createUnderlyingTypeface(path, Typeface.NORMAL);
- }
-
- // maybe path is e.g. "myFont", but we should match "myFont.ttf" too?
- Path[] files;
- try {
- files = Fs.listFiles(assetDir, f -> f.getFileName().toString().startsWith(path));
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- if (files.length != 0) {
- return createUnderlyingTypeface(path, Typeface.NORMAL);
- }
- }
-
- throw new RuntimeException("Font asset not found " + path);
- }
-
- @Implementation(minSdk = O, maxSdk = P)
- protected static Typeface createFromResources(AssetManager mgr, String path, int cookie) {
- return createUnderlyingTypeface(path, Typeface.NORMAL);
- }
-
- @Implementation(minSdk = O)
- protected static Typeface createFromResources(
- Object /* FamilyResourceEntry */ entry,
- Object /* AssetManager */ mgr,
- Object /* String */ path) {
- return createUnderlyingTypeface((String) path, Typeface.NORMAL);
- }
-
- @Implementation
- protected static Typeface createFromFile(File path) {
- String familyName = path.toPath().getFileName().toString();
- return createUnderlyingTypeface(familyName, Typeface.NORMAL);
- }
-
- @Implementation
- protected static Typeface createFromFile(String path) {
- return createFromFile(new File(path));
- }
-
- @Implementation
- protected int getStyle() {
- return description.getStyle();
- }
-
- @Override
- @Implementation
- public boolean equals(Object o) {
- if (o instanceof Typeface) {
- Typeface other = ((Typeface) o);
- return Objects.equals(getFontDescription(), shadowOf(other).getFontDescription());
- }
- return false;
- }
-
- @Override
- @Implementation
- public int hashCode() {
- return getFontDescription().hashCode();
- }
-
- @HiddenApi
- @Implementation(minSdk = LOLLIPOP)
- protected static Typeface createFromFamilies(Object /*FontFamily[]*/ families) {
- return null;
- }
-
- @HiddenApi
- @Implementation(minSdk = LOLLIPOP, maxSdk = N_MR1)
- protected static Typeface createFromFamiliesWithDefault(Object /*FontFamily[]*/ families) {
- return null;
- }
-
- @Implementation(minSdk = O, maxSdk = O_MR1)
- protected static Typeface createFromFamiliesWithDefault(
- Object /*FontFamily[]*/ families, Object /* int */ weight, Object /* int */ italic) {
- return createUnderlyingTypeface("fake-font", Typeface.NORMAL);
- }
-
- @Implementation(minSdk = P)
- protected static Typeface createFromFamiliesWithDefault(
- Object /*FontFamily[]*/ families,
- Object /* String */ fallbackName,
- Object /* int */ weight,
- Object /* int */ italic) {
- return createUnderlyingTypeface((String) fallbackName, Typeface.NORMAL);
- }
-
- @Implementation(minSdk = P, maxSdk = P)
- protected static void buildSystemFallback(
- String xmlPath,
- String fontDir,
- ArrayMap<String, Typeface> fontMap,
- ArrayMap<String, FontFamily[]> fallbackMap) {
- fontMap.put("sans-serif", createUnderlyingTypeface("sans-serif", 0));
- }
-
- /** Avoid spurious error message about /system/etc/fonts.xml */
- @Implementation(minSdk = LOLLIPOP, maxSdk = O_MR1)
- protected static void init() {}
-
- @HiddenApi
- @Implementation(minSdk = Q, maxSdk = R)
- public static void initSystemDefaultTypefaces(
- Object systemFontMap, Object fallbacks, Object aliases) {}
-
- @Resetter
- public static synchronized void reset() {
- FONTS.clear();
- }
-
- protected static Typeface createUnderlyingTypeface(String familyName, int style) {
- long thisFontId = nextFontId.getAndIncrement();
- FONTS.put(thisFontId, new FontDesc(familyName, style));
- if (getApiLevel() >= LOLLIPOP) {
- return ReflectionHelpers.callConstructor(
- Typeface.class, ClassParameter.from(long.class, thisFontId));
- } else {
- return ReflectionHelpers.callConstructor(
- Typeface.class, ClassParameter.from(int.class, (int) thisFontId));
- }
- }
-
- private static synchronized FontDesc findById(long fontId) {
- if (FONTS.containsKey(fontId)) {
- return FONTS.get(fontId);
- }
- throw new RuntimeException("Unknown font id: " + fontId);
- }
-
- @Implementation(minSdk = O, maxSdk = R)
- protected static long nativeCreateFromArray(long[] familyArray, int weight, int italic) {
- // TODO: implement this properly
- long thisFontId = nextFontId.getAndIncrement();
- FONTS.put(thisFontId, new FontDesc(null, weight));
- return thisFontId;
- }
+/** Base class for {@link ShadowTypeface} classes. */
+@Implements(value = Typeface.class, shadowPicker = Picker.class)
+public abstract class ShadowTypeface {
/**
* Returns the font description.
*
* @return Font description.
*/
- public FontDesc getFontDescription() {
- return description;
- }
-
- @Implementation(minSdk = S)
- protected static void nativeForceSetStaticFinalField(String fieldname, Typeface typeface) {
- ReflectionHelpers.setStaticField(Typeface.class, fieldname, typeface);
- }
-
- @Implementation(minSdk = S)
- protected static long nativeCreateFromArray(
- long[] familyArray, long fallbackTypeface, int weight, int italic) {
- return ShadowTypeface.nativeCreateFromArray(familyArray, weight, italic);
- }
+ public abstract FontDesc getFontDescription();
+ /** Contains data about a font. */
public static class FontDesc {
public final String familyName;
public final int style;
@@ -305,15 +64,10 @@ public class ShadowTypeface {
}
}
- /** Shadow for {@link Typeface.Builder} */
- @Implements(value = Typeface.Builder.class, minSdk = Q)
- public static class ShadowBuilder {
- @RealObject Typeface.Builder realBuilder;
-
- @Implementation
- protected Typeface build() {
- String path = ReflectionHelpers.getField(realBuilder, "mPath");
- return createUnderlyingTypeface(path, Typeface.NORMAL);
+ /** Shadow picker for {@link Typeface}. */
+ public static final class Picker extends GraphicsShadowPicker<Object> {
+ public Picker() {
+ super(ShadowLegacyTypeface.class, ShadowNativeTypeface.class);
}
}
}
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 5a177082e..2c283d6f7 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUserManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowUserManager.java
@@ -6,7 +6,6 @@ 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.N_MR1;
-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;
@@ -855,7 +854,7 @@ public class ShadowUserManager {
* <p>This method checks whether the user handle corresponds to a managed profile, and then query
* its state. When quiet, the user is not running.
*/
- @Implementation(minSdk = O)
+ @Implementation(minSdk = N)
protected boolean isQuietModeEnabled(UserHandle userHandle) {
// Return false if this is not a managed profile (this is the OS's behavior).
if (!isManagedProfileWithoutPermission(userHandle)) {
diff --git a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVibrator.java b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVibrator.java
index 3159bf923..b66a0a414 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVibrator.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowVibrator.java
@@ -14,31 +14,32 @@ import java.util.Objects;
import javax.annotation.Nullable;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
@Implements(Vibrator.class)
public class ShadowVibrator {
- boolean vibrating;
- boolean cancelled;
- long milliseconds;
- protected long[] pattern;
- protected final List<VibrationEffectSegment> vibrationEffectSegments = new ArrayList<>();
- protected final List<PrimitiveEffect> primitiveEffects = new ArrayList<>();
- protected final List<Integer> supportedPrimitives = new ArrayList<>();
- @Nullable protected VibrationAttributes vibrationAttributesFromLastVibration;
- @Nullable protected AudioAttributes audioAttributesFromLastVibration;
- int repeat;
- boolean hasVibrator = true;
- boolean hasAmplitudeControl = false;
- int effectId;
+ static boolean vibrating;
+ static boolean cancelled;
+ static long milliseconds;
+ protected static long[] pattern;
+ protected static final List<VibrationEffectSegment> vibrationEffectSegments = new ArrayList<>();
+ protected static final List<PrimitiveEffect> primitiveEffects = new ArrayList<>();
+ protected static final List<Integer> supportedPrimitives = new ArrayList<>();
+ @Nullable protected static VibrationAttributes vibrationAttributesFromLastVibration;
+ @Nullable protected static AudioAttributes audioAttributesFromLastVibration;
+ static int repeat;
+ static boolean hasVibrator = true;
+ static boolean hasAmplitudeControl = false;
+ static int effectId;
/** Controls the return value of {@link Vibrator#hasVibrator()} the default is true. */
public void setHasVibrator(boolean hasVibrator) {
- this.hasVibrator = hasVibrator;
+ ShadowVibrator.hasVibrator = hasVibrator;
}
/** Controls the return value of {@link Vibrator#hasAmplitudeControl()} the default is false. */
public void setHasAmplitudeControl(boolean hasAmplitudeControl) {
- this.hasAmplitudeControl = hasAmplitudeControl;
+ ShadowVibrator.hasAmplitudeControl = hasAmplitudeControl;
}
/**
@@ -119,6 +120,23 @@ public class ShadowVibrator {
return audioAttributesFromLastVibration;
}
+ @Resetter
+ public static void reset() {
+ vibrating = false;
+ cancelled = false;
+ milliseconds = 0;
+ pattern = null;
+ vibrationEffectSegments.clear();
+ primitiveEffects.clear();
+ supportedPrimitives.clear();
+ vibrationAttributesFromLastVibration = null;
+ audioAttributesFromLastVibration = null;
+ repeat = 0;
+ hasVibrator = true;
+ hasAmplitudeControl = false;
+ effectId = 0;
+ }
+
/**
* A data class for exposing {@link VibrationEffect.Composition$PrimitiveEffect}, which is a
* hidden non TestApi class introduced in Android R.
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 921d278c1..cfe2bc7b6 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowViewRootImpl.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowViewRootImpl.java
@@ -21,9 +21,11 @@ import android.view.InsetsState;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewRootImpl;
+import android.view.WindowInsets;
import android.view.WindowManager;
import android.window.ClientWindowFrames;
import java.util.ArrayList;
+import java.util.Optional;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
@@ -47,6 +49,49 @@ public class ShadowViewRootImpl {
@RealObject protected ViewRootImpl realObject;
+ /**
+ * The visibility of the system status bar.
+ *
+ * <p>The value will be read in the intercepted {@link #getWindowInsets(boolean)} method providing
+ * the current state via the returned {@link WindowInsets} instance if it has been set..
+ *
+ * <p>NOTE: This state does not reflect the current state of system UI visibility flags or the
+ * current window insets. Rather it tracks the latest known state provided via {@link
+ * #setIsStatusBarVisible(boolean)}.
+ */
+ private static Optional<Boolean> isStatusBarVisible = Optional.empty();
+
+ /**
+ * The visibility of the system navigation bar.
+ *
+ * <p>The value will be read in the intercepted {@link #getWindowInsets(boolean)} method providing
+ * the current state via the returned {@link WindowInsets} instance if it has been set.
+ *
+ * <p>NOTE: This state does not reflect the current state of system UI visibility flags or the
+ * current window insets. Rather it tracks the latest known state provided via {@link
+ * #setIsNavigationBarVisible(boolean)}.
+ */
+ private static Optional<Boolean> isNavigationBarVisible = Optional.empty();
+
+ /** Allows other shadows to set the state of {@link #isStatusBarVisible}. */
+ protected static void setIsStatusBarVisible(boolean isStatusBarVisible) {
+ ShadowViewRootImpl.isStatusBarVisible = Optional.of(isStatusBarVisible);
+ }
+
+ /** Clears the last known state of {@link #isStatusBarVisible}. */
+ protected static void clearIsStatusBarVisible() {
+ ShadowViewRootImpl.isStatusBarVisible = Optional.empty();
+ }
+
+ /** Allows other shadows to set the state of {@link #isNavigationBarVisible}. */
+ protected static void setIsNavigationBarVisible(boolean isNavigationBarVisible) {
+ ShadowViewRootImpl.isNavigationBarVisible = Optional.of(isNavigationBarVisible);
+ }
+
+ /** Clears the last known state of {@link #isNavigationBarVisible}. */
+ protected static void clearIsNavigationBarVisible() {
+ ShadowViewRootImpl.isNavigationBarVisible = Optional.empty();
+ }
@Implementation(maxSdk = VERSION_CODES.JELLY_BEAN)
protected static IWindowSession getWindowSession(Looper mainLooper) {
@@ -185,6 +230,38 @@ public class ShadowViewRootImpl {
}
}
+ /**
+ * On Android R+ {@link WindowInsets} supports checking visibility of specific inset types.
+ *
+ * <p>For those SDK levels, override the real {@link WindowInsets} with the tracked system bar
+ * visibility status ({@link #isStatusBarVisible}/{@link #isNavigationBarVisible}), if set.
+ *
+ * <p>NOTE: We use state tracking in place of a longer term solution of implementing the insets
+ * calculations and broadcast (via listeners) for now. Once we have insets calculations working we
+ * should remove this mechanism.
+ */
+ @Implementation(minSdk = R)
+ protected WindowInsets getWindowInsets(boolean forceConstruct) {
+ WindowInsets realInsets =
+ reflector(ViewRootImplReflector.class, realObject).getWindowInsets(forceConstruct);
+
+ WindowInsets.Builder overridenInsetsBuilder = new WindowInsets.Builder(realInsets);
+
+ if (isStatusBarVisible.isPresent()) {
+ overridenInsetsBuilder =
+ overridenInsetsBuilder.setVisible(
+ WindowInsets.Type.statusBars(), isStatusBarVisible.get());
+ }
+
+ if (isNavigationBarVisible.isPresent()) {
+ overridenInsetsBuilder =
+ overridenInsetsBuilder.setVisible(
+ WindowInsets.Type.navigationBars(), isNavigationBarVisible.get());
+ }
+
+ return overridenInsetsBuilder.build();
+ }
+
@Resetter
public static void reset() {
ViewRootImplReflector viewRootImplStatic = reflector(ViewRootImplReflector.class);
@@ -192,6 +269,9 @@ public class ShadowViewRootImpl {
viewRootImplStatic.setFirstDrawHandlers(new ArrayList<>());
viewRootImplStatic.setFirstDrawComplete(false);
viewRootImplStatic.setConfigCallbacks(new ArrayList<>());
+
+ clearIsStatusBarVisible();
+ clearIsNavigationBarVisible();
}
public void callWindowFocusChanged(boolean hasFocus) {
@@ -389,5 +469,9 @@ public class ShadowViewRootImpl {
// SDK >= T
void windowFocusChanged(boolean hasFocus);
+
+ // SDK >= M
+ @Direct
+ WindowInsets getWindowInsets(boolean forceConstruct);
}
}
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 87cb0f27e..648b44988 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWallpaperManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWallpaperManager.java
@@ -1,5 +1,9 @@
package org.robolectric.shadows;
+import static android.os.Build.VERSION_CODES.M;
+import static android.os.Build.VERSION_CODES.N;
+import static android.os.Build.VERSION_CODES.TIRAMISU;
+
import android.Manifest.permission;
import android.annotation.FloatRange;
import android.annotation.RequiresPermission;
@@ -61,12 +65,12 @@ public class ShadowWallpaperManager {
* <p>This only caches the resource id in memory. Calling this will override any previously set
* resource and does not differentiate between users.
*/
- @Implementation(maxSdk = VERSION_CODES.M)
+ @Implementation(maxSdk = M)
protected void setResource(int resid) {
setResource(resid, WallpaperManager.FLAG_SYSTEM | WallpaperManager.FLAG_LOCK);
}
- @Implementation(minSdk = VERSION_CODES.N)
+ @Implementation(minSdk = N)
protected int setResource(int resid, int which) {
if ((which & (WallpaperManager.FLAG_SYSTEM | WallpaperManager.FLAG_LOCK)) == 0) {
return 0;
@@ -100,18 +104,21 @@ public class ShadowWallpaperManager {
* @param which either {@link WallpaperManager#FLAG_LOCK} or {WallpaperManager#FLAG_SYSTEM}
* @return 0 if fails to cache. Otherwise, 1.
*/
- @Implementation(minSdk = VERSION_CODES.P)
+ @Implementation(minSdk = N)
protected int setBitmap(Bitmap fullImage, Rect visibleCropHint, boolean allowBackup, int which) {
- if (which == WallpaperManager.FLAG_LOCK) {
+ if ((which & (WallpaperManager.FLAG_SYSTEM | WallpaperManager.FLAG_LOCK)) == 0) {
+ return 0;
+ }
+ if ((which & WallpaperManager.FLAG_LOCK) == WallpaperManager.FLAG_LOCK) {
lockScreenImage = fullImage;
wallpaperInfo = null;
- return 1;
- } else if (which == WallpaperManager.FLAG_SYSTEM) {
+ }
+
+ if ((which & WallpaperManager.FLAG_SYSTEM) == WallpaperManager.FLAG_SYSTEM) {
homeScreenImage = fullImage;
wallpaperInfo = null;
- return 1;
}
- return 0;
+ return 1;
}
/**
@@ -138,7 +145,7 @@ public class ShadowWallpaperManager {
* @return An open, readable file descriptor to the requested wallpaper image file; {@code null}
* if no such wallpaper is configured.
*/
- @Implementation(minSdk = VERSION_CODES.P)
+ @Implementation(minSdk = N)
@Nullable
protected ParcelFileDescriptor getWallpaperFile(int which) {
if (which == WallpaperManager.FLAG_SYSTEM && homeScreenImage != null) {
@@ -149,7 +156,7 @@ public class ShadowWallpaperManager {
return null;
}
- @Implementation(minSdk = VERSION_CODES.N)
+ @Implementation(minSdk = N)
protected boolean isSetWallpaperAllowed() {
return isWallpaperAllowed;
}
@@ -158,7 +165,7 @@ public class ShadowWallpaperManager {
isWallpaperAllowed = allowed;
}
- @Implementation(minSdk = VERSION_CODES.M)
+ @Implementation(minSdk = M)
protected boolean isWallpaperSupported() {
return isWallpaperSupported;
}
@@ -176,17 +183,20 @@ public class ShadowWallpaperManager {
* @param which either {@link WallpaperManager#FLAG_LOCK} or {WallpaperManager#FLAG_SYSTEM}
* @return 0 if fails to cache. Otherwise, 1.
*/
- @Implementation(minSdk = VERSION_CODES.N)
+ @Implementation(minSdk = N)
protected int setStream(
InputStream bitmapData, Rect visibleCropHint, boolean allowBackup, int which) {
- if (which == WallpaperManager.FLAG_LOCK) {
+ if ((which & (WallpaperManager.FLAG_SYSTEM | WallpaperManager.FLAG_LOCK)) == 0) {
+ return 0;
+ }
+ if ((which & WallpaperManager.FLAG_LOCK) == WallpaperManager.FLAG_LOCK) {
lockScreenImage = BitmapFactory.decodeStream(bitmapData);
- return 1;
- } else if (which == WallpaperManager.FLAG_SYSTEM) {
+ }
+
+ if ((which & WallpaperManager.FLAG_SYSTEM) == WallpaperManager.FLAG_SYSTEM) {
homeScreenImage = BitmapFactory.decodeStream(bitmapData);
- return 1;
}
- return 0;
+ return 1;
}
/**
@@ -196,7 +206,7 @@ public class ShadowWallpaperManager {
* previously set static wallpaper.
*/
@SystemApi
- @Implementation(minSdk = VERSION_CODES.M)
+ @Implementation(minSdk = M)
@RequiresPermission(permission.SET_WALLPAPER_COMPONENT)
protected boolean setWallpaperComponent(ComponentName wallpaperService)
throws IOException, XmlPullParserException {
@@ -222,17 +232,17 @@ public class ShadowWallpaperManager {
* Returns the information about the wallpaper if the current wallpaper is a live wallpaper
* component. Otherwise, if the wallpaper is a static image, this returns null.
*/
- @Implementation(minSdk = VERSION_CODES.M)
+ @Implementation
protected WallpaperInfo getWallpaperInfo() {
return wallpaperInfo;
}
- @Implementation(minSdk = VERSION_CODES.TIRAMISU)
+ @Implementation(minSdk = TIRAMISU)
protected void setWallpaperDimAmount(@FloatRange(from = 0f, to = 1f) float dimAmount) {
wallpaperDimAmount = MathUtils.saturate(dimAmount);
}
- @Implementation(minSdk = VERSION_CODES.TIRAMISU)
+ @Implementation(minSdk = TIRAMISU)
@FloatRange(from = 0f, to = 1f)
protected float getWallpaperDimAmount() {
return wallpaperDimAmount;
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 83ca1fb3c..561ed907f 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWifiManager.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWifiManager.java
@@ -65,6 +65,7 @@ public class ShadowWifiManager {
private final ConcurrentHashMap<WifiManager.OnWifiUsabilityStatsListener, Executor>
wifiUsabilityStatsListeners = new ConcurrentHashMap<>();
private final List<WifiUsabilityScore> usabilityScores = new ArrayList<>();
+ private Object networkScorer;
@RealObject WifiManager wifiManager;
private WifiConfiguration apConfig;
@@ -436,6 +437,32 @@ public class ShadowWifiManager {
}
}
+ /**
+ * Implements setWifiConnectedNetworkScorer() with the generic Object input as
+ * WifiConnectedNetworkScorer is a hidden/System API.
+ */
+ @Implementation(minSdk = R)
+ @HiddenApi
+ protected boolean setWifiConnectedNetworkScorer(Object executorObject, Object scorerObject) {
+ if (networkScorer == null) {
+ networkScorer = scorerObject;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Implementation(minSdk = R)
+ @HiddenApi
+ protected void clearWifiConnectedNetworkScorer() {
+ networkScorer = null;
+ }
+
+ /** Returns if wifi connected betwork scorer enabled */
+ public boolean isWifiConnectedNetworkScorerEnabled() {
+ return networkScorer != null;
+ }
+
@Implementation
protected boolean setWifiApConfiguration(WifiConfiguration apConfig) {
this.apConfig = apConfig;
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 80b484cdc..75b0371eb 100644
--- a/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindowManagerGlobal.java
+++ b/shadows/framework/src/main/java/org/robolectric/shadows/ShadowWindowManagerGlobal.java
@@ -2,40 +2,25 @@ 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_MR1;
-import static android.os.Build.VERSION_CODES.M;
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.S_V2;
import static org.robolectric.shadows.ShadowView.useRealGraphics;
import static org.robolectric.util.reflector.Reflector.reflector;
import android.app.Instrumentation;
import android.content.ClipData;
import android.content.Context;
-import android.graphics.Rect;
import android.os.Binder;
-import android.os.Build.VERSION;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.view.DisplayCutout;
-import android.view.IWindow;
import android.view.IWindowManager;
import android.view.IWindowSession;
-import android.view.InputChannel;
-import android.view.InsetsSourceControl;
-import android.view.InsetsState;
-import android.view.InsetsVisibilities;
-import android.view.Surface;
-import android.view.SurfaceControl;
import android.view.View;
-import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import androidx.annotation.Nullable;
+import java.lang.reflect.Proxy;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
@@ -53,44 +38,18 @@ import org.robolectric.util.reflector.Static;
minSdk = JELLY_BEAN_MR1,
looseSignatures = true)
public class ShadowWindowManagerGlobal {
- private static WindowSessionDelegate windowSessionDelegate;
+ private static WindowSessionDelegate windowSessionDelegate = new WindowSessionDelegate();
private static IWindowSession windowSession;
@Resetter
public static void reset() {
reflector(WindowManagerGlobalReflector.class).setDefaultWindowManager(null);
- windowSessionDelegate = null;
+ windowSessionDelegate = new WindowSessionDelegate();
windowSession = null;
}
- private static synchronized WindowSessionDelegate getWindowSessionDelegate() {
- if (windowSessionDelegate == null) {
- int apiLevel = RuntimeEnvironment.getApiLevel();
- if (apiLevel >= S_V2) {
- windowSessionDelegate = new WindowSessionDelegateSV2();
- } else if (apiLevel >= S) {
- windowSessionDelegate = new WindowSessionDelegateS();
- } else if (apiLevel >= R) {
- windowSessionDelegate = new WindowSessionDelegateR();
- } else if (apiLevel >= Q) {
- windowSessionDelegate = new WindowSessionDelegateQ();
- } else if (apiLevel >= P) {
- windowSessionDelegate = new WindowSessionDelegateP();
- } else if (apiLevel >= M) {
- windowSessionDelegate = new WindowSessionDelegateM();
- } else if (apiLevel >= LOLLIPOP_MR1) {
- windowSessionDelegate = new WindowSessionDelegateLMR1();
- } else if (apiLevel >= JELLY_BEAN_MR1) {
- windowSessionDelegate = new WindowSessionDelegateJBMR1();
- } else {
- windowSessionDelegate = new WindowSessionDelegateJB();
- }
- }
- return windowSessionDelegate;
- }
-
public static boolean getInTouchMode() {
- return getWindowSessionDelegate().getInTouchMode();
+ return windowSessionDelegate.getInTouchMode();
}
/**
@@ -98,7 +57,7 @@ public class ShadowWindowManagerGlobal {
* Instrumentation#setInTouchMode(boolean)} to modify this from a test.
*/
static void setInTouchMode(boolean inTouchMode) {
- getWindowSessionDelegate().setInTouchMode(inTouchMode);
+ windowSessionDelegate.setInTouchMode(inTouchMode);
}
/**
@@ -107,21 +66,46 @@ public class ShadowWindowManagerGlobal {
*/
@Nullable
public static ClipData getLastDragClipData() {
- return windowSessionDelegate != null ? windowSessionDelegate.lastDragClipData : null;
+ return windowSessionDelegate.lastDragClipData;
}
/** Clears the data returned by {@link #getLastDragClipData()}. */
public static void clearLastDragClipData() {
- if (windowSessionDelegate != null) {
- windowSessionDelegate.lastDragClipData = null;
- }
+ windowSessionDelegate.lastDragClipData = null;
}
@Implementation(minSdk = JELLY_BEAN_MR2)
protected static synchronized IWindowSession getWindowSession() {
if (windowSession == null) {
+ // Use Proxy.newProxyInstance instead of ReflectionHelpers.createDelegatingProxy as there are
+ // too many variants of 'add', 'addToDisplay', and 'addToDisplayAsUser', some of which have
+ // arg types that don't exist any more.
windowSession =
- ReflectionHelpers.createDelegatingProxy(IWindowSession.class, getWindowSessionDelegate());
+ (IWindowSession)
+ Proxy.newProxyInstance(
+ IWindowSession.class.getClassLoader(),
+ new Class<?>[] {IWindowSession.class},
+ (proxy, method, args) -> {
+ String methodName = method.getName();
+ switch (methodName) {
+ case "add": // SDK 16
+ case "addToDisplay": // SDK 17-29
+ case "addToDisplayAsUser": // SDK 30+
+ return windowSessionDelegate.getAddFlags();
+ case "getInTouchMode":
+ return windowSessionDelegate.getInTouchMode();
+ case "performDrag":
+ return windowSessionDelegate.performDrag(args);
+ case "prepareDrag":
+ return windowSessionDelegate.prepareDrag();
+ case "setInTouchMode":
+ windowSessionDelegate.setInTouchMode((boolean) args[0]);
+ return null;
+ default:
+ return ReflectionHelpers.defaultValueForType(
+ method.getReturnType().getName());
+ }
+ });
}
return windowSession;
}
@@ -143,7 +127,7 @@ public class ShadowWindowManagerGlobal {
if (service == null) {
service = IWindowManager.Stub.asInterface(ServiceManager.getService(Context.WINDOW_SERVICE));
reflector(WindowManagerGlobalReflector.class).setWindowManagerService(service);
- if (VERSION.SDK_INT >= 30) {
+ if (RuntimeEnvironment.getApiLevel() >= R) {
reflector(WindowManagerGlobalReflector.class).setUseBlastAdapter(service.useBLAST());
}
}
@@ -169,7 +153,7 @@ public class ShadowWindowManagerGlobal {
void setUseBlastAdapter(boolean useBlastAdapter);
}
- private abstract static class WindowSessionDelegate {
+ private static class WindowSessionDelegate {
// From WindowManagerGlobal (was WindowManagerImpl in JB).
static final int ADD_FLAG_IN_TOUCH_MODE = 0x1;
static final int ADD_FLAG_APP_VISIBLE = 0x2;
@@ -202,200 +186,20 @@ public class ShadowWindowManagerGlobal {
this.inTouchMode = inTouchMode;
}
- // @Implementation(maxSdk = O_MR1)
- public IBinder prepareDrag(
- IWindow window, int flags, int thumbnailWidth, int thumbnailHeight, Surface outSurface) {
- return new Binder();
- }
-
- // @Implementation(maxSdk = M)
- public boolean performDrag(
- IWindow window,
- IBinder dragToken,
- float touchX,
- float touchY,
- float thumbCenterX,
- float thumbCenterY,
- ClipData data) {
- lastDragClipData = data;
- return true;
- }
-
- // @Implementation(minSdk = N, maxSdk = O_MR1)
- public boolean performDrag(
- IWindow window,
- IBinder dragToken,
- int touchSource,
- float touchX,
- float touchY,
- float thumbCenterX,
- float thumbCenterY,
- ClipData data) {
- lastDragClipData = data;
- return true;
- }
- }
-
- private static class WindowSessionDelegateJB extends WindowSessionDelegate {
- // @Implementation(maxSdk = JELLY_BEAN)
- public int add(
- IWindow window,
- int seq,
- WindowManager.LayoutParams attrs,
- int viewVisibility,
- int layerStackId,
- Rect outContentInsets,
- InputChannel outInputChannel) {
- return getAddFlags();
- }
- }
-
- private static class WindowSessionDelegateJBMR1 extends WindowSessionDelegateJB {
- // @Implementation(minSdk = JELLY_BEAN_MR1, maxSdk = LOLLIPOP)
- public int addToDisplay(
- IWindow window,
- int seq,
- WindowManager.LayoutParams attrs,
- int viewVisibility,
- int layerStackId,
- Rect outContentInsets,
- InputChannel outInputChannel) {
- return getAddFlags();
- }
- }
-
- private static class WindowSessionDelegateLMR1 extends WindowSessionDelegateJBMR1 {
- // @Implementation(sdk = LOLLIPOP_MR1)
- public int addToDisplay(
- IWindow window,
- int seq,
- WindowManager.LayoutParams attrs,
- int viewVisibility,
- int layerStackId,
- Rect outContentInsets,
- Rect outStableInsets,
- InputChannel outInputChannel) {
- return getAddFlags();
- }
- }
-
- private static class WindowSessionDelegateM extends WindowSessionDelegateLMR1 {
- // @Implementation(minSdk = M, maxSdk = O_MR1)
- public int addToDisplay(
- IWindow window,
- int seq,
- WindowManager.LayoutParams attrs,
- int viewVisibility,
- int layerStackId,
- Rect outContentInsets,
- Rect outStableInsets,
- Rect outInsets,
- InputChannel outInputChannel) {
- return getAddFlags();
- }
- }
-
- private static class WindowSessionDelegateP extends WindowSessionDelegateM {
- // @Implementation(sdk = P)
- public int addToDisplay(
- IWindow window,
- int seq,
- WindowManager.LayoutParams attrs,
- int viewVisibility,
- int layerStackId,
- Rect outFrame,
- Rect outContentInsets,
- Rect outStableInsets,
- Rect outOutsets,
- DisplayCutout.ParcelableWrapper displayCutout,
- InputChannel outInputChannel) {
- return getAddFlags();
- }
-
- // @Implementation(minSdk = P)
- public IBinder performDrag(
- IWindow window,
- int flags,
- SurfaceControl surface,
- int touchSource,
- float touchX,
- float touchY,
- float thumbCenterX,
- float thumbCenterY,
- ClipData data) {
- lastDragClipData = data;
+ public IBinder prepareDrag() {
return new Binder();
}
- }
- private static class WindowSessionDelegateQ extends WindowSessionDelegateP {
- // @Implementation(sdk = Q)
- public int addToDisplay(
- IWindow window,
- int seq,
- WindowManager.LayoutParams attrs,
- int viewVisibility,
- int layerStackId,
- Rect outFrame,
- Rect outContentInsets,
- Rect outStableInsets,
- Rect outOutsets,
- DisplayCutout.ParcelableWrapper displayCutout,
- InputChannel outInputChannel,
- InsetsState insetsState) {
- return getAddFlags();
- }
- }
-
- private static class WindowSessionDelegateR extends WindowSessionDelegateQ {
- // @Implementation(sdk = R)
- public int addToDisplayAsUser(
- IWindow window,
- int seq,
- WindowManager.LayoutParams attrs,
- int viewVisibility,
- int layerStackId,
- int userId,
- Rect outFrame,
- Rect outContentInsets,
- Rect outStableInsets,
- DisplayCutout.ParcelableWrapper displayCutout,
- InputChannel outInputChannel,
- InsetsState insetsState,
- InsetsSourceControl[] activeControls) {
- return getAddFlags();
- }
- }
-
- private static class WindowSessionDelegateS extends WindowSessionDelegateR {
- // @Implementation(sdk = S)
- public int addToDisplayAsUser(
- IWindow window,
- WindowManager.LayoutParams attrs,
- int viewVisibility,
- int layerStackId,
- int userId,
- InsetsState requestedVisibility,
- InputChannel outInputChannel,
- InsetsState insetsState,
- InsetsSourceControl[] activeControls) {
- return getAddFlags();
- }
- }
-
- private static class WindowSessionDelegateSV2 extends WindowSessionDelegateS {
- // @Implementation(minSdk = S_V2)
- public int addToDisplayAsUser(
- IWindow window,
- WindowManager.LayoutParams attrs,
- int viewVisibility,
- int displayId,
- int userId,
- InsetsVisibilities requestedVisibilities,
- InputChannel outInputChannel,
- InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls) {
- return getAddFlags();
+ public Object performDrag(Object[] args) {
+ // extract the clipData param
+ for (int i = args.length - 1; i >= 0; i--) {
+ if (args[i] instanceof ClipData) {
+ lastDragClipData = (ClipData) args[i];
+ // In P (SDK 28), the return type changed from boolean to Binder.
+ return RuntimeEnvironment.getApiLevel() >= P ? new Binder() : true;
+ }
+ }
+ throw new AssertionError("Missing ClipData param");
}
}
}
diff --git a/shadows/httpclient/Android.bp b/shadows/httpclient/Android.bp
new file mode 100644
index 000000000..4132f92d9
--- /dev/null
+++ b/shadows/httpclient/Android.bp
@@ -0,0 +1,78 @@
+//#############################################
+// Compile Robolectric shadows httpclient
+//#############################################
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "external_robolectric_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["external_robolectric_license"],
+}
+
+java_library_host {
+ name: "Robolectric_shadows_httpclient_upstream",
+ libs: [
+ "Robolectric_shadows_framework_upstream",
+ "Robolectric_annotations_upstream",
+ "Robolectric_shadowapi_upstream",
+ "Robolectric_utils_upstream",
+ "robolectric-httpclient-4.0.3",
+ "robolectric-httpcore-4.0.1",
+ "robolectric-javax.annotation-api-1.2",
+ "robolectric-host-android_all_upstream",
+ "robolectric-host-org_apache_http_legacy_upstream",
+ ],
+ plugins: ["Robolectric_processor_upstream"],
+ javacflags: ["-Aorg.robolectric.annotation.processing.shadowPackage=org.robolectric.shadows.httpclient"],
+ srcs: ["src/main/java/**/*.java"],
+
+ errorprone: {
+ javacflags: ["-Xep:EqualsNull:WARN"],
+ },
+}
+
+//#############################################
+// Compile Robolectric shadows httpclient tests
+//#############################################
+
+java_test_host {
+ name: "Robolectric_shadows_httpclient_tests_upstream",
+ srcs: ["src/test/java/**/*.java"],
+ java_resource_dirs: ["src/test/resources"],
+ static_libs: [
+ "Robolectric_shadows_httpclient_upstream",
+ "Robolectric_shadows_framework_upstream",
+ "Robolectric_annotations_upstream",
+ "Robolectric_robolectric_upstream",
+ "Robolectric_resources_upstream",
+ "Robolectric_shadowapi_upstream",
+ "Robolectric_sandbox_upstream",
+ "Robolectric_junit_upstream",
+ "Robolectric_utils_upstream",
+ "robolectric-maven-ant-tasks-2.1.3",
+ "bouncycastle-unbundled",
+ "hamcrest",
+ "robolectric-httpclient-4.0.3",
+ "asm-commons-9.2",
+ "robolectric-httpcore-4.0.1",
+ "guava",
+ "asm-tree-9.2",
+ "junit",
+ "truth-prebuilt",
+ "robolectric-ant-1.8.0",
+ "asm-9.2",
+ "jsr305",
+ "grpc-java-netty-shaded",
+ //"grpc-netty-shaded-1.16.1-jar",
+ "robolectric-host-android_all_upstream",
+ "robolectric-host-org_apache_http_legacy_upstream",
+ "robolectric-host-androidx-test-ext-junit_upstream",
+ ],
+ test_suites: ["general-tests"],
+ //tradefed doesn't run these tests.
+ test_options: {
+ unit_test: false,
+ },
+}
diff --git a/shadows/multidex/Android.bp b/shadows/multidex/Android.bp
new file mode 100644
index 000000000..91d3379c0
--- /dev/null
+++ b/shadows/multidex/Android.bp
@@ -0,0 +1,26 @@
+//#############################################
+// Compile Robolectric shadows multidex
+//#############################################
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "external_robolectric-shadows_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-MIT
+ default_applicable_licenses: ["external_robolectric_license"],
+}
+
+java_library_host {
+ name: "Robolectric_shadows_multidex_upstream",
+ libs: [
+ "Robolectric_annotations_upstream",
+ "Robolectric_shadowapi_upstream",
+ "robolectric-host-android-support-multidex_upstream",
+ "robolectric-javax.annotation-api-1.2",
+ "robolectric-host-android_all_upstream",
+ ],
+ plugins: ["Robolectric_processor_upstream"],
+ javacflags: ["-Aorg.robolectric.annotation.processing.shadowPackage=org.robolectric.shadows.multidex"],
+ srcs: ["src/main/java/**/*.java"],
+}
diff --git a/shadows/playservices/Android.bp b/shadows/playservices/Android.bp
new file mode 100644
index 000000000..4b6635e65
--- /dev/null
+++ b/shadows/playservices/Android.bp
@@ -0,0 +1,22 @@
+//#############################################
+// Compile Robolectric shadows playservices
+//#############################################
+
+//java_library_host {
+// name: "Robolectric_shadows_playservices_upstream",
+// libs: [
+// "Robolectric_shadows_framework_upstream",
+// "Robolectric_annotations_upstream",
+// "Robolectric_shadowapi_upstream",
+// "robolectric-javax.annotation-api-1.2",
+// "guava",
+// "robolectric-host-android_all_upstream",
+
+// compileOnly "com.android.support:support-fragment:28.0.0"
+// compileOnly "com.google.android.gms:play-services-base:8.4.0"
+// compileOnly "com.google.android.gms:play-services-basement:8.4.0"
+// ],
+// plugins: ["Robolectric_processor_upstream"],
+// javacflags: ["-Aorg.robolectric.annotation.processing.shadowPackage=org.robolectric.shadows.gms"],
+// srcs: ["src/main/java/**/*.java"],
+//}
diff --git a/utils/Android.bp b/utils/Android.bp
new file mode 100644
index 000000000..bf4009cab
--- /dev/null
+++ b/utils/Android.bp
@@ -0,0 +1,56 @@
+//#############################################
+// Compile Robolectric utils
+//#############################################
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "external_robolectric_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["external_robolectric_license"],
+}
+
+java_library_host {
+ name: "Robolectric_utils_upstream",
+ srcs: ["src/main/java/**/*.java"],
+ common_srcs: ["src/main/java/**/*.kt"],
+ plugins: ["auto_service_plugin"],
+ static_libs: [
+ "robolectric-javax.annotation-api-1.2",
+ "Robolectric_annotations_upstream",
+ "Robolectric_pluginapi_upstream",
+ "error_prone_annotations",
+ "guava",
+ "jsr330",
+ "jsr305",
+ "asm-9.2",
+ "auto_service_annotations",
+ ],
+}
+
+//#############################################
+// Compile Robolectric utils tests
+//#############################################
+
+java_test_host {
+ name: "Robolectric_utils_tests_upstream",
+ srcs: [
+ "src/test/java/**/*.java",
+ "src/test/java/**/*.kt",
+ ],
+ plugins: [
+ "auto_service_plugin",
+ ],
+ static_libs: [
+ "Robolectric_utils_upstream",
+ "hamcrest",
+ "guava",
+ "junit",
+ "error_prone_core",
+ "truth-prebuilt",
+ "asm-9.2",
+ "mockito",
+ ],
+ test_suites: ["general-tests"],
+}
diff --git a/utils/reflector/Android.bp b/utils/reflector/Android.bp
new file mode 100644
index 000000000..800776d50
--- /dev/null
+++ b/utils/reflector/Android.bp
@@ -0,0 +1,42 @@
+//#############################################
+// Compile Robolectric utils
+//#############################################
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "external_robolectric_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["external_robolectric_license"],
+}
+
+java_library_host {
+ name: "Robolectric_utils_reflector_upstream",
+ static_libs: [
+ "Robolectric_utils_upstream",
+ "asm-9.2",
+ "asm-commons-9.2",
+ "asm-tree-9.2",
+ "asm-util-9.2",
+ ],
+ srcs: ["src/main/java/**/*.java"],
+}
+
+//#############################################
+// Compile Robolectric utils tests
+//#############################################
+
+java_test_host {
+ name: "Robolectric_utils_reflector_tests_upstream",
+ srcs: ["src/test/java/**/*.java"],
+ static_libs: [
+ "Robolectric_utils_reflector_upstream",
+ "Robolectric_shadowapi_upstream",
+ "hamcrest",
+ "guava",
+ "junit",
+ "truth-prebuilt",
+ ],
+ test_suites: ["general-tests"],
+}