diff options
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"], +} @@ -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 Binary files differnew file mode 100644 index 000000000..a955442ba --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/AndroidClock.ttf diff --git a/nativeruntime/src/main/resources/fonts/CarroisGothicSC-Regular.ttf b/nativeruntime/src/main/resources/fonts/CarroisGothicSC-Regular.ttf Binary files differnew file mode 100644 index 000000000..d0281c739 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/CarroisGothicSC-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/ComingSoon.ttf b/nativeruntime/src/main/resources/fonts/ComingSoon.ttf Binary files differnew file mode 100644 index 000000000..62a5a0de7 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/ComingSoon.ttf diff --git a/nativeruntime/src/main/resources/fonts/CutiveMono.ttf b/nativeruntime/src/main/resources/fonts/CutiveMono.ttf Binary files differnew file mode 100644 index 000000000..efe0f334e --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/CutiveMono.ttf diff --git a/nativeruntime/src/main/resources/fonts/DancingScript-Bold.ttf b/nativeruntime/src/main/resources/fonts/DancingScript-Bold.ttf Binary files differnew file mode 100644 index 000000000..d502ef871 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/DancingScript-Bold.ttf diff --git a/nativeruntime/src/main/resources/fonts/DancingScript-Regular.ttf b/nativeruntime/src/main/resources/fonts/DancingScript-Regular.ttf Binary files differnew file mode 100644 index 000000000..3fa27af34 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/DancingScript-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/DroidSans-Bold.ttf b/nativeruntime/src/main/resources/fonts/DroidSans-Bold.ttf Binary files differnew file mode 100644 index 000000000..adf2aede6 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/DroidSans-Bold.ttf diff --git a/nativeruntime/src/main/resources/fonts/DroidSans.ttf b/nativeruntime/src/main/resources/fonts/DroidSans.ttf Binary files differnew file mode 100644 index 000000000..adf2aede6 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/DroidSans.ttf diff --git a/nativeruntime/src/main/resources/fonts/DroidSansMono.ttf b/nativeruntime/src/main/resources/fonts/DroidSansMono.ttf Binary files differnew file mode 100644 index 000000000..b7bf5b4aa --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/DroidSansMono.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoColorEmoji.ttf b/nativeruntime/src/main/resources/fonts/NotoColorEmoji.ttf Binary files differnew file mode 100644 index 000000000..a746ca0d4 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoColorEmoji.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoColorEmojiFlags.ttf b/nativeruntime/src/main/resources/fonts/NotoColorEmojiFlags.ttf Binary files differnew file mode 100644 index 000000000..b5120cdd1 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoColorEmojiFlags.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoColorEmojiLegacy.ttf b/nativeruntime/src/main/resources/fonts/NotoColorEmojiLegacy.ttf Binary files differnew file mode 100644 index 000000000..e037903ae --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoColorEmojiLegacy.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoNaskhArabic-Bold.ttf b/nativeruntime/src/main/resources/fonts/NotoNaskhArabic-Bold.ttf Binary files differnew file mode 100644 index 000000000..ca75a3e48 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoNaskhArabic-Bold.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoNaskhArabic-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoNaskhArabic-Regular.ttf Binary files differnew file mode 100644 index 000000000..cac2af239 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoNaskhArabic-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoNaskhArabicUI-Bold.ttf b/nativeruntime/src/main/resources/fonts/NotoNaskhArabicUI-Bold.ttf Binary files differnew file mode 100644 index 000000000..f51061077 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoNaskhArabicUI-Bold.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoNaskhArabicUI-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoNaskhArabicUI-Regular.ttf Binary files differnew file mode 100644 index 000000000..138b3751a --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoNaskhArabicUI-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansAdlam-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansAdlam-VF.ttf Binary files differnew file mode 100644 index 000000000..11207b096 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansAdlam-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansAhom-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansAhom-Regular.otf Binary files differnew file mode 100644 index 000000000..2edf45869 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansAhom-Regular.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansAnatolianHieroglyphs-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansAnatolianHieroglyphs-Regular.otf Binary files differnew file mode 100644 index 000000000..7c1e87aee --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansAnatolianHieroglyphs-Regular.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansArmenian-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansArmenian-VF.ttf Binary files differnew file mode 100644 index 000000000..769660a8c --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansArmenian-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansAvestan-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansAvestan-Regular.ttf Binary files differnew file mode 100644 index 000000000..31eeb5aa7 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansAvestan-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansBalinese-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansBalinese-Regular.ttf Binary files differnew file mode 100644 index 000000000..a15509bb9 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansBalinese-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansBamum-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansBamum-Regular.ttf Binary files differnew file mode 100644 index 000000000..f2de18714 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansBamum-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansBassaVah-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansBassaVah-Regular.otf Binary files differnew file mode 100644 index 000000000..0b7b7b820 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansBassaVah-Regular.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansBatak-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansBatak-Regular.ttf Binary files differnew file mode 100644 index 000000000..882ad961b --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansBatak-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansBengali-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansBengali-VF.ttf Binary files differnew file mode 100644 index 000000000..a03477940 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansBengali-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansBengaliUI-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansBengaliUI-VF.ttf Binary files differnew file mode 100644 index 000000000..a2bd0c344 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansBengaliUI-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansBhaiksuki-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansBhaiksuki-Regular.otf Binary files differnew file mode 100644 index 000000000..dcf386217 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansBhaiksuki-Regular.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansBrahmi-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansBrahmi-Regular.ttf Binary files differnew file mode 100644 index 000000000..eb98a6557 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansBrahmi-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansBuginese-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansBuginese-Regular.ttf Binary files differnew file mode 100644 index 000000000..fd59da691 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansBuginese-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansBuhid-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansBuhid-Regular.ttf Binary files differnew file mode 100644 index 000000000..756218ffa --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansBuhid-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansCJK-Regular.ttc b/nativeruntime/src/main/resources/fonts/NotoSansCJK-Regular.ttc Binary files differnew file mode 100644 index 000000000..31ab08455 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansCJK-Regular.ttc diff --git a/nativeruntime/src/main/resources/fonts/NotoSansCanadianAboriginal-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansCanadianAboriginal-Regular.ttf Binary files differnew file mode 100644 index 000000000..c036d94eb --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansCanadianAboriginal-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansCarian-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansCarian-Regular.ttf Binary files differnew file mode 100644 index 000000000..8abada192 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansCarian-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansChakma-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansChakma-Regular.otf Binary files differnew file mode 100644 index 000000000..d9690ca6b --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansChakma-Regular.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansCham-Bold.ttf b/nativeruntime/src/main/resources/fonts/NotoSansCham-Bold.ttf Binary files differnew file mode 100644 index 000000000..5f98099b1 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansCham-Bold.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansCham-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansCham-Regular.ttf Binary files differnew file mode 100644 index 000000000..be897d445 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansCham-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansCherokee-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansCherokee-Regular.ttf Binary files differnew file mode 100644 index 000000000..1dfef8bb7 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansCherokee-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansCoptic-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansCoptic-Regular.ttf Binary files differnew file mode 100644 index 000000000..d327c7964 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansCoptic-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansCuneiform-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansCuneiform-Regular.ttf Binary files differnew file mode 100644 index 000000000..37a4e9e1a --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansCuneiform-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansCypriot-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansCypriot-Regular.ttf Binary files differnew file mode 100644 index 000000000..bc4083e07 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansCypriot-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansDeseret-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansDeseret-Regular.ttf Binary files differnew file mode 100644 index 000000000..c043a5434 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansDeseret-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansDevanagari-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansDevanagari-VF.ttf Binary files differnew file mode 100644 index 000000000..f871a48a2 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansDevanagari-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansDevanagariUI-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansDevanagariUI-VF.ttf Binary files differnew file mode 100644 index 000000000..1c023cf5f --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansDevanagariUI-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansEgyptianHieroglyphs-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansEgyptianHieroglyphs-Regular.ttf Binary files differnew file mode 100644 index 000000000..62ee89793 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansEgyptianHieroglyphs-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansElbasan-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansElbasan-Regular.otf Binary files differnew file mode 100644 index 000000000..1c28397c9 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansElbasan-Regular.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansEthiopic-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansEthiopic-VF.ttf Binary files differnew file mode 100644 index 000000000..e2f46a97c --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansEthiopic-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansGeorgian-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansGeorgian-VF.ttf Binary files differnew file mode 100644 index 000000000..235d6af87 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansGeorgian-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansGlagolitic-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansGlagolitic-Regular.ttf Binary files differnew file mode 100644 index 000000000..86237aa8a --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansGlagolitic-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansGothic-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansGothic-Regular.ttf Binary files differnew file mode 100644 index 000000000..d653ee107 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansGothic-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansGrantha-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansGrantha-Regular.ttf Binary files differnew file mode 100644 index 000000000..3039d7383 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansGrantha-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansGujarati-Bold.ttf b/nativeruntime/src/main/resources/fonts/NotoSansGujarati-Bold.ttf Binary files differnew file mode 100644 index 000000000..7b6f05f2e --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansGujarati-Bold.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansGujarati-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansGujarati-Regular.ttf Binary files differnew file mode 100644 index 000000000..7af1329de --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansGujarati-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansGujaratiUI-Bold.ttf b/nativeruntime/src/main/resources/fonts/NotoSansGujaratiUI-Bold.ttf Binary files differnew file mode 100644 index 000000000..d60a3483d --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansGujaratiUI-Bold.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansGujaratiUI-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansGujaratiUI-Regular.ttf Binary files differnew file mode 100644 index 000000000..1bb407d86 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansGujaratiUI-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansGunjalaGondi-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansGunjalaGondi-Regular.otf Binary files differnew file mode 100644 index 000000000..4cc0fa3af --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansGunjalaGondi-Regular.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansGurmukhi-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansGurmukhi-VF.ttf Binary files differnew file mode 100644 index 000000000..ba48e40e2 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansGurmukhi-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansGurmukhiUI-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansGurmukhiUI-VF.ttf Binary files differnew file mode 100644 index 000000000..930289fed --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansGurmukhiUI-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansHanifiRohingya-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansHanifiRohingya-Regular.otf Binary files differnew file mode 100644 index 000000000..f3dc231c3 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansHanifiRohingya-Regular.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansHanunoo-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansHanunoo-Regular.ttf Binary files differnew file mode 100644 index 000000000..480af3494 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansHanunoo-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansHatran-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansHatran-Regular.otf Binary files differnew file mode 100644 index 000000000..125909571 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansHatran-Regular.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansHebrew-Bold.ttf b/nativeruntime/src/main/resources/fonts/NotoSansHebrew-Bold.ttf Binary files differnew file mode 100644 index 000000000..64844fec2 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansHebrew-Bold.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansHebrew-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansHebrew-Regular.ttf Binary files differnew file mode 100644 index 000000000..c161ce529 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansHebrew-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansImperialAramaic-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansImperialAramaic-Regular.ttf Binary files differnew file mode 100644 index 000000000..10802e324 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansImperialAramaic-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansInscriptionalPahlavi-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansInscriptionalPahlavi-Regular.ttf Binary files differnew file mode 100644 index 000000000..44d529650 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansInscriptionalPahlavi-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansInscriptionalParthian-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansInscriptionalParthian-Regular.ttf Binary files differnew file mode 100644 index 000000000..bca05aa7d --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansInscriptionalParthian-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansJavanese-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansJavanese-Regular.otf Binary files differnew file mode 100644 index 000000000..e1aa66580 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansJavanese-Regular.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansKaithi-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansKaithi-Regular.ttf Binary files differnew file mode 100644 index 000000000..22b8c4761 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansKaithi-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansKannada-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansKannada-VF.ttf Binary files differnew file mode 100644 index 000000000..7447a8965 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansKannada-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansKannadaUI-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansKannadaUI-VF.ttf Binary files differnew file mode 100644 index 000000000..30b730136 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansKannadaUI-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansKayahLi-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansKayahLi-Regular.ttf Binary files differnew file mode 100644 index 000000000..75b6b8013 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansKayahLi-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansKharoshthi-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansKharoshthi-Regular.ttf Binary files differnew file mode 100644 index 000000000..1f4a9a667 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansKharoshthi-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansKhmer-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansKhmer-VF.ttf Binary files differnew file mode 100644 index 000000000..1ba1f1d78 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansKhmer-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansKhmerUI-Bold.ttf b/nativeruntime/src/main/resources/fonts/NotoSansKhmerUI-Bold.ttf Binary files differnew file mode 100644 index 000000000..592414c3f --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansKhmerUI-Bold.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansKhmerUI-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansKhmerUI-Regular.ttf Binary files differnew file mode 100644 index 000000000..7a11a8153 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansKhmerUI-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansKhojki-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansKhojki-Regular.otf Binary files differnew file mode 100644 index 000000000..99146ac5f --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansKhojki-Regular.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansLao-Bold.ttf b/nativeruntime/src/main/resources/fonts/NotoSansLao-Bold.ttf Binary files differnew file mode 100644 index 000000000..4f50a70e6 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansLao-Bold.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansLao-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansLao-Regular.ttf Binary files differnew file mode 100644 index 000000000..ff6c6a443 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansLao-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansLaoUI-Bold.ttf b/nativeruntime/src/main/resources/fonts/NotoSansLaoUI-Bold.ttf Binary files differnew file mode 100644 index 000000000..4ca61cf23 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansLaoUI-Bold.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansLaoUI-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansLaoUI-Regular.ttf Binary files differnew file mode 100644 index 000000000..61d755df3 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansLaoUI-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansLepcha-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansLepcha-Regular.ttf Binary files differnew file mode 100644 index 000000000..b86dc5982 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansLepcha-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansLimbu-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansLimbu-Regular.ttf Binary files differnew file mode 100644 index 000000000..b9eca0839 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansLimbu-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansLinearA-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansLinearA-Regular.otf Binary files differnew file mode 100644 index 000000000..0f791af41 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansLinearA-Regular.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansLinearB-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansLinearB-Regular.ttf Binary files differnew file mode 100644 index 000000000..b7415078e --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansLinearB-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansLisu-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansLisu-Regular.ttf Binary files differnew file mode 100644 index 000000000..12405b4a5 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansLisu-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansLycian-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansLycian-Regular.ttf Binary files differnew file mode 100644 index 000000000..30310c20e --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansLycian-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansLydian-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansLydian-Regular.ttf Binary files differnew file mode 100644 index 000000000..fc5bf5fd1 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansLydian-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansMalayalam-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansMalayalam-VF.ttf Binary files differnew file mode 100644 index 000000000..8221617a5 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansMalayalam-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansMalayalamUI-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansMalayalamUI-VF.ttf Binary files differnew file mode 100644 index 000000000..cf38b96f7 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansMalayalamUI-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansMandaic-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansMandaic-Regular.ttf Binary files differnew file mode 100644 index 000000000..1a533e1b3 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansMandaic-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansManichaean-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansManichaean-Regular.otf Binary files differnew file mode 100644 index 000000000..8065acb03 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansManichaean-Regular.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansMarchen-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansMarchen-Regular.otf Binary files differnew file mode 100644 index 000000000..983e62281 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansMarchen-Regular.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansMasaramGondi-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansMasaramGondi-Regular.otf Binary files differnew file mode 100644 index 000000000..8833d019d --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansMasaramGondi-Regular.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansMedefaidrin-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansMedefaidrin-VF.ttf Binary files differnew file mode 100644 index 000000000..7dda52d3b --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansMedefaidrin-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansMeeteiMayek-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansMeeteiMayek-Regular.ttf Binary files differnew file mode 100644 index 000000000..3059a6c3a --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansMeeteiMayek-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansMeroitic-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansMeroitic-Regular.otf Binary files differnew file mode 100644 index 000000000..e0f70d55f --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansMeroitic-Regular.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansMiao-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansMiao-Regular.otf Binary files differnew file mode 100644 index 000000000..2facdee8b --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansMiao-Regular.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansModi-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansModi-Regular.ttf Binary files differnew file mode 100644 index 000000000..dd4e340b6 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansModi-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansMongolian-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansMongolian-Regular.ttf Binary files differnew file mode 100644 index 000000000..760d7e0d3 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansMongolian-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansMro-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansMro-Regular.otf Binary files differnew file mode 100644 index 000000000..78f715b51 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansMro-Regular.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansMultani-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansMultani-Regular.otf Binary files differnew file mode 100644 index 000000000..7804a00e7 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansMultani-Regular.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansMyanmar-Bold.otf b/nativeruntime/src/main/resources/fonts/NotoSansMyanmar-Bold.otf Binary files differnew file mode 100644 index 000000000..ddec24133 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansMyanmar-Bold.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansMyanmar-Medium.otf b/nativeruntime/src/main/resources/fonts/NotoSansMyanmar-Medium.otf Binary files differnew file mode 100644 index 000000000..c6c03db26 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansMyanmar-Medium.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansMyanmar-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansMyanmar-Regular.otf Binary files differnew file mode 100644 index 000000000..f2f7a6764 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansMyanmar-Regular.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansMyanmarUI-Bold.otf b/nativeruntime/src/main/resources/fonts/NotoSansMyanmarUI-Bold.otf Binary files differnew file mode 100644 index 000000000..6742efd9a --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansMyanmarUI-Bold.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansMyanmarUI-Medium.otf b/nativeruntime/src/main/resources/fonts/NotoSansMyanmarUI-Medium.otf Binary files differnew file mode 100644 index 000000000..af579deaa --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansMyanmarUI-Medium.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansMyanmarUI-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansMyanmarUI-Regular.otf Binary files differnew file mode 100644 index 000000000..7d912b84b --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansMyanmarUI-Regular.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansNKo-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansNKo-Regular.ttf Binary files differnew file mode 100644 index 000000000..091416be4 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansNKo-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansNabataean-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansNabataean-Regular.otf Binary files differnew file mode 100644 index 000000000..a757da5a0 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansNabataean-Regular.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansNewTaiLue-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansNewTaiLue-Regular.ttf Binary files differnew file mode 100644 index 000000000..bc79a7930 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansNewTaiLue-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansNewa-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansNewa-Regular.otf Binary files differnew file mode 100644 index 000000000..4728d2e04 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansNewa-Regular.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansOgham-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansOgham-Regular.ttf Binary files differnew file mode 100644 index 000000000..8943189c5 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansOgham-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansOlChiki-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansOlChiki-Regular.ttf Binary files differnew file mode 100644 index 000000000..332664043 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansOlChiki-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansOldItalic-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansOldItalic-Regular.ttf Binary files differnew file mode 100644 index 000000000..4a63a5e72 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansOldItalic-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansOldNorthArabian-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansOldNorthArabian-Regular.otf Binary files differnew file mode 100644 index 000000000..f884ae06a --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansOldNorthArabian-Regular.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansOldPermic-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansOldPermic-Regular.otf Binary files differnew file mode 100644 index 000000000..5a0e71e62 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansOldPermic-Regular.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansOldPersian-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansOldPersian-Regular.ttf Binary files differnew file mode 100644 index 000000000..21166f443 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansOldPersian-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansOldSouthArabian-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansOldSouthArabian-Regular.ttf Binary files differnew file mode 100644 index 000000000..5037bd996 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansOldSouthArabian-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansOldTurkic-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansOldTurkic-Regular.ttf Binary files differnew file mode 100644 index 000000000..1652ad157 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansOldTurkic-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansOriya-Bold.ttf b/nativeruntime/src/main/resources/fonts/NotoSansOriya-Bold.ttf Binary files differnew file mode 100644 index 000000000..ab6e759a5 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansOriya-Bold.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansOriya-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansOriya-Regular.ttf Binary files differnew file mode 100644 index 000000000..92abe640c --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansOriya-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansOriyaUI-Bold.ttf b/nativeruntime/src/main/resources/fonts/NotoSansOriyaUI-Bold.ttf Binary files differnew file mode 100644 index 000000000..1b32205eb --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansOriyaUI-Bold.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansOriyaUI-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansOriyaUI-Regular.ttf Binary files differnew file mode 100644 index 000000000..5738e1d55 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansOriyaUI-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansOsage-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansOsage-Regular.ttf Binary files differnew file mode 100644 index 000000000..28f1d9403 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansOsage-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansOsmanya-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansOsmanya-Regular.ttf Binary files differnew file mode 100644 index 000000000..dd0b982db --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansOsmanya-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansPahawhHmong-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansPahawhHmong-Regular.otf Binary files differnew file mode 100644 index 000000000..26a7440ee --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansPahawhHmong-Regular.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansPalmyrene-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansPalmyrene-Regular.otf Binary files differnew file mode 100644 index 000000000..3ccbf6f35 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansPalmyrene-Regular.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansPauCinHau-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansPauCinHau-Regular.otf Binary files differnew file mode 100644 index 000000000..a5c7d921d --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansPauCinHau-Regular.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansPhagsPa-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansPhagsPa-Regular.ttf Binary files differnew file mode 100644 index 000000000..bd59e328f --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansPhagsPa-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansPhoenician-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansPhoenician-Regular.ttf Binary files differnew file mode 100644 index 000000000..345a9770a --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansPhoenician-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansRejang-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansRejang-Regular.ttf Binary files differnew file mode 100644 index 000000000..3d95fd3d9 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansRejang-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansRunic-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansRunic-Regular.ttf Binary files differnew file mode 100644 index 000000000..64a18b17d --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansRunic-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansSamaritan-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansSamaritan-Regular.ttf Binary files differnew file mode 100644 index 000000000..9fe427c84 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansSamaritan-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansSaurashtra-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansSaurashtra-Regular.ttf Binary files differnew file mode 100644 index 000000000..6ca7590ae --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansSaurashtra-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansSharada-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansSharada-Regular.otf Binary files differnew file mode 100644 index 000000000..2c865a21f --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansSharada-Regular.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansShavian-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansShavian-Regular.ttf Binary files differnew file mode 100644 index 000000000..265c5231b --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansShavian-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansSinhala-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansSinhala-VF.ttf Binary files differnew file mode 100644 index 000000000..cee6ff2e6 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansSinhala-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansSinhalaUI-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansSinhalaUI-VF.ttf Binary files differnew file mode 100644 index 000000000..681e12104 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansSinhalaUI-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansSoraSompeng-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansSoraSompeng-Regular.otf Binary files differnew file mode 100644 index 000000000..1240506da --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansSoraSompeng-Regular.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansSoyombo-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansSoyombo-VF.ttf Binary files differnew file mode 100644 index 000000000..cba7dc0dc --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansSoyombo-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansSundanese-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansSundanese-Regular.ttf Binary files differnew file mode 100644 index 000000000..5c19cd703 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansSundanese-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansSylotiNagri-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansSylotiNagri-Regular.ttf Binary files differnew file mode 100644 index 000000000..164c217fa --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansSylotiNagri-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansSymbols-Regular-Subsetted.ttf b/nativeruntime/src/main/resources/fonts/NotoSansSymbols-Regular-Subsetted.ttf Binary files differnew file mode 100644 index 000000000..b98dcccf4 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansSymbols-Regular-Subsetted.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansSymbols-Regular-Subsetted2.ttf b/nativeruntime/src/main/resources/fonts/NotoSansSymbols-Regular-Subsetted2.ttf Binary files differnew file mode 100644 index 000000000..1b5d2a36a --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansSymbols-Regular-Subsetted2.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansSyriacEastern-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansSyriacEastern-Regular.ttf Binary files differnew file mode 100644 index 000000000..25ee338b9 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansSyriacEastern-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansSyriacEstrangela-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansSyriacEstrangela-Regular.ttf Binary files differnew file mode 100644 index 000000000..07a2e043e --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansSyriacEstrangela-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansSyriacWestern-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansSyriacWestern-Regular.ttf Binary files differnew file mode 100644 index 000000000..f0f9de146 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansSyriacWestern-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansTagalog-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansTagalog-Regular.ttf Binary files differnew file mode 100644 index 000000000..0aff41104 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansTagalog-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansTagbanwa-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansTagbanwa-Regular.ttf Binary files differnew file mode 100644 index 000000000..286cf4c19 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansTagbanwa-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansTaiLe-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansTaiLe-Regular.ttf Binary files differnew file mode 100644 index 000000000..88ea3fd76 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansTaiLe-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansTaiTham-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansTaiTham-Regular.ttf Binary files differnew file mode 100644 index 000000000..faa8ddf6c --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansTaiTham-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansTaiViet-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansTaiViet-Regular.ttf Binary files differnew file mode 100644 index 000000000..9208fc776 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansTaiViet-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansTakri-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansTakri-VF.ttf Binary files differnew file mode 100644 index 000000000..73f5fbeb8 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansTakri-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansTamil-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansTamil-VF.ttf Binary files differnew file mode 100644 index 000000000..da4ee1c78 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansTamil-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansTamilUI-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansTamilUI-VF.ttf Binary files differnew file mode 100644 index 000000000..3cd3c8ecb --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansTamilUI-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansTelugu-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansTelugu-VF.ttf Binary files differnew file mode 100644 index 000000000..591b80db0 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansTelugu-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansTeluguUI-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSansTeluguUI-VF.ttf Binary files differnew file mode 100644 index 000000000..b3385e70d --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansTeluguUI-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansThaana-Bold.ttf b/nativeruntime/src/main/resources/fonts/NotoSansThaana-Bold.ttf Binary files differnew file mode 100644 index 000000000..986195695 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansThaana-Bold.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansThaana-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansThaana-Regular.ttf Binary files differnew file mode 100644 index 000000000..8fcf561a5 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansThaana-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansThai-Bold.ttf b/nativeruntime/src/main/resources/fonts/NotoSansThai-Bold.ttf Binary files differnew file mode 100644 index 000000000..3b5ed2875 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansThai-Bold.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansThai-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansThai-Regular.ttf Binary files differnew file mode 100644 index 000000000..552c35617 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansThai-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansThaiUI-Bold.ttf b/nativeruntime/src/main/resources/fonts/NotoSansThaiUI-Bold.ttf Binary files differnew file mode 100644 index 000000000..3bc9a7051 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansThaiUI-Bold.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansThaiUI-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansThaiUI-Regular.ttf Binary files differnew file mode 100644 index 000000000..9fe222d4e --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansThaiUI-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansTifinagh-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansTifinagh-Regular.otf Binary files differnew file mode 100644 index 000000000..c8b87c8ee --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansTifinagh-Regular.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansUgaritic-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansUgaritic-Regular.ttf Binary files differnew file mode 100644 index 000000000..dec137d5e --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansUgaritic-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansVai-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansVai-Regular.ttf Binary files differnew file mode 100644 index 000000000..3ec5fccfa --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansVai-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansWancho-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansWancho-Regular.otf Binary files differnew file mode 100644 index 000000000..af941de19 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansWancho-Regular.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansWarangCiti-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSansWarangCiti-Regular.otf Binary files differnew file mode 100644 index 000000000..2a3b78a99 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansWarangCiti-Regular.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSansYi-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSansYi-Regular.ttf Binary files differnew file mode 100644 index 000000000..5bd81f9cb --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSansYi-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSerif-Bold.ttf b/nativeruntime/src/main/resources/fonts/NotoSerif-Bold.ttf Binary files differnew file mode 100644 index 000000000..2ffdf8c1c --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSerif-Bold.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSerif-BoldItalic.ttf b/nativeruntime/src/main/resources/fonts/NotoSerif-BoldItalic.ttf Binary files differnew file mode 100644 index 000000000..4a317f818 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSerif-BoldItalic.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSerif-Italic.ttf b/nativeruntime/src/main/resources/fonts/NotoSerif-Italic.ttf Binary files differnew file mode 100644 index 000000000..968b46bca --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSerif-Italic.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSerif-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSerif-Regular.ttf Binary files differnew file mode 100644 index 000000000..9d9a347f6 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSerif-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifArmenian-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifArmenian-VF.ttf Binary files differnew file mode 100644 index 000000000..5525d52d7 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSerifArmenian-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifBengali-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifBengali-VF.ttf Binary files differnew file mode 100644 index 000000000..24c0774ba --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSerifBengali-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifCJK-Regular.ttc b/nativeruntime/src/main/resources/fonts/NotoSerifCJK-Regular.ttc Binary files differnew file mode 100644 index 000000000..2820fc99b --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSerifCJK-Regular.ttc diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifDevanagari-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifDevanagari-VF.ttf Binary files differnew file mode 100644 index 000000000..bf013a7d6 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSerifDevanagari-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifDogra-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifDogra-Regular.ttf Binary files differnew file mode 100644 index 000000000..ca5eba924 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSerifDogra-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifEthiopic-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifEthiopic-VF.ttf Binary files differnew file mode 100644 index 000000000..10b5e2698 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSerifEthiopic-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifGeorgian-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifGeorgian-VF.ttf Binary files differnew file mode 100644 index 000000000..afc8e9c1f --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSerifGeorgian-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifGujarati-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifGujarati-VF.ttf Binary files differnew file mode 100644 index 000000000..586150185 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSerifGujarati-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifGurmukhi-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifGurmukhi-VF.ttf Binary files differnew file mode 100644 index 000000000..6c104cc09 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSerifGurmukhi-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifHebrew-Bold.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifHebrew-Bold.ttf Binary files differnew file mode 100644 index 000000000..cdec7467b --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSerifHebrew-Bold.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifHebrew-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifHebrew-Regular.ttf Binary files differnew file mode 100644 index 000000000..8b69dde90 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSerifHebrew-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifKannada-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifKannada-VF.ttf Binary files differnew file mode 100644 index 000000000..c523ad723 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSerifKannada-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifKhmer-Bold.otf b/nativeruntime/src/main/resources/fonts/NotoSerifKhmer-Bold.otf Binary files differnew file mode 100644 index 000000000..47294159e --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSerifKhmer-Bold.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifKhmer-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSerifKhmer-Regular.otf Binary files differnew file mode 100644 index 000000000..204e3432a --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSerifKhmer-Regular.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifLao-Bold.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifLao-Bold.ttf Binary files differnew file mode 100644 index 000000000..1a2b775af --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSerifLao-Bold.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifLao-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifLao-Regular.ttf Binary files differnew file mode 100644 index 000000000..d6a4ae25d --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSerifLao-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifMalayalam-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifMalayalam-VF.ttf Binary files differnew file mode 100644 index 000000000..e270f5d73 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSerifMalayalam-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifMyanmar-Bold.otf b/nativeruntime/src/main/resources/fonts/NotoSerifMyanmar-Bold.otf Binary files differnew file mode 100644 index 000000000..ba21a2798 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSerifMyanmar-Bold.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifMyanmar-Regular.otf b/nativeruntime/src/main/resources/fonts/NotoSerifMyanmar-Regular.otf Binary files differnew file mode 100644 index 000000000..ec4ed4438 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSerifMyanmar-Regular.otf diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifNyiakengPuachueHmong-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifNyiakengPuachueHmong-VF.ttf Binary files differnew file mode 100644 index 000000000..e87a20286 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSerifNyiakengPuachueHmong-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifSinhala-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifSinhala-VF.ttf Binary files differnew file mode 100644 index 000000000..9109b833e --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSerifSinhala-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifTamil-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifTamil-VF.ttf Binary files differnew file mode 100644 index 000000000..91acc2b7f --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSerifTamil-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifTelugu-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifTelugu-VF.ttf Binary files differnew file mode 100644 index 000000000..16a187b10 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSerifTelugu-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifThai-Bold.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifThai-Bold.ttf Binary files differnew file mode 100644 index 000000000..10d8afd45 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSerifThai-Bold.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifThai-Regular.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifThai-Regular.ttf Binary files differnew file mode 100644 index 000000000..e4b1c14a9 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSerifThai-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifTibetan-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifTibetan-VF.ttf Binary files differnew file mode 100644 index 000000000..60903833d --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSerifTibetan-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/NotoSerifYezidi-VF.ttf b/nativeruntime/src/main/resources/fonts/NotoSerifYezidi-VF.ttf Binary files differnew file mode 100644 index 000000000..fabcbbb0f --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/NotoSerifYezidi-VF.ttf diff --git a/nativeruntime/src/main/resources/fonts/Roboto-Regular.ttf b/nativeruntime/src/main/resources/fonts/Roboto-Regular.ttf Binary files differnew file mode 100644 index 000000000..adf2aede6 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/Roboto-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/RobotoStatic-Regular.ttf b/nativeruntime/src/main/resources/fonts/RobotoStatic-Regular.ttf Binary files differnew file mode 100644 index 000000000..b624812dc --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/RobotoStatic-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/SourceSansPro-Bold.ttf b/nativeruntime/src/main/resources/fonts/SourceSansPro-Bold.ttf Binary files differnew file mode 100644 index 000000000..f6986468b --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/SourceSansPro-Bold.ttf diff --git a/nativeruntime/src/main/resources/fonts/SourceSansPro-BoldItalic.ttf b/nativeruntime/src/main/resources/fonts/SourceSansPro-BoldItalic.ttf Binary files differnew file mode 100644 index 000000000..5c00b64fa --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/SourceSansPro-BoldItalic.ttf diff --git a/nativeruntime/src/main/resources/fonts/SourceSansPro-Italic.ttf b/nativeruntime/src/main/resources/fonts/SourceSansPro-Italic.ttf Binary files differnew file mode 100644 index 000000000..82e876201 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/SourceSansPro-Italic.ttf diff --git a/nativeruntime/src/main/resources/fonts/SourceSansPro-Regular.ttf b/nativeruntime/src/main/resources/fonts/SourceSansPro-Regular.ttf Binary files differnew file mode 100644 index 000000000..278ad8aa0 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/SourceSansPro-Regular.ttf diff --git a/nativeruntime/src/main/resources/fonts/SourceSansPro-SemiBold.ttf b/nativeruntime/src/main/resources/fonts/SourceSansPro-SemiBold.ttf Binary files differnew file mode 100644 index 000000000..ac3e0d19a --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/SourceSansPro-SemiBold.ttf diff --git a/nativeruntime/src/main/resources/fonts/SourceSansPro-SemiBoldItalic.ttf b/nativeruntime/src/main/resources/fonts/SourceSansPro-SemiBoldItalic.ttf Binary files differnew file mode 100644 index 000000000..b0737bb31 --- /dev/null +++ b/nativeruntime/src/main/resources/fonts/SourceSansPro-SemiBoldItalic.ttf 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 Binary files differnew file mode 100644 index 000000000..5b805c5bd --- /dev/null +++ b/nativeruntime/src/main/resources/icu/icudt68l.dat 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"], +} |